comfyui-frontend-package 1.37.9__py3-none-any.whl → 1.37.11__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 (188) hide show
  1. comfyui_frontend_package/static/assets/{AboutPanel-BudnQURa.js → AboutPanel-B-lCq9HV.js} +2 -2
  2. comfyui_frontend_package/static/assets/{AboutPanel-BudnQURa.js.map → AboutPanel-B-lCq9HV.js.map} +1 -1
  3. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-CHjtFZRu.js → AudioPreviewPlayer-CXgr12AM.js} +2 -2
  4. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-CHjtFZRu.js.map → AudioPreviewPlayer-CXgr12AM.js.map} +1 -1
  5. comfyui_frontend_package/static/assets/{ComfyQueueButton-QhN0FmOY.js → ComfyQueueButton-C6ttLtL6.js} +2 -2
  6. comfyui_frontend_package/static/assets/{ComfyQueueButton-QhN0FmOY.js.map → ComfyQueueButton-C6ttLtL6.js.map} +1 -1
  7. comfyui_frontend_package/static/assets/{ExtensionPanel-D2sITMri.js → ExtensionPanel-uFlj7Yfm.js} +2 -2
  8. comfyui_frontend_package/static/assets/{ExtensionPanel-D2sITMri.js.map → ExtensionPanel-uFlj7Yfm.js.map} +1 -1
  9. comfyui_frontend_package/static/assets/GraphView-0U0T8pwU.js +15 -0
  10. comfyui_frontend_package/static/assets/GraphView-0U0T8pwU.js.map +1 -0
  11. comfyui_frontend_package/static/assets/GraphView-2T90vQj0.css +1 -0
  12. comfyui_frontend_package/static/assets/{KeybindingPanel-Dm6H1RN2.js → KeybindingPanel-DtxoTOoX.js} +2 -2
  13. comfyui_frontend_package/static/assets/{KeybindingPanel-Dm6H1RN2.js.map → KeybindingPanel-DtxoTOoX.js.map} +1 -1
  14. comfyui_frontend_package/static/assets/LazyImage.vue_vue_type_script_setup_true_lang-BSzd4gKh.js +2 -0
  15. comfyui_frontend_package/static/assets/LazyImage.vue_vue_type_script_setup_true_lang-BSzd4gKh.js.map +1 -0
  16. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-Be7L1Af3.js → LegacyCreditsPanel-CO0JRKfg.js} +2 -2
  17. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-Be7L1Af3.js.map → LegacyCreditsPanel-CO0JRKfg.js.map} +1 -1
  18. comfyui_frontend_package/static/assets/{Load3D-BAJ3yM4D.js → Load3D-NG6IeknA.js} +2 -2
  19. comfyui_frontend_package/static/assets/Load3D-NG6IeknA.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-DYT5Ecka.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-DYT5Ecka.js.map} +1 -1
  22. comfyui_frontend_package/static/assets/{Media3DTop-DdNgRlQW.js → Media3DTop-iLfJefgN.js} +2 -2
  23. comfyui_frontend_package/static/assets/{Media3DTop-DdNgRlQW.js.map → Media3DTop-iLfJefgN.js.map} +1 -1
  24. comfyui_frontend_package/static/assets/{ServerConfigPanel-LtRqEOzu.js → ServerConfigPanel-B8Qy2naY.js} +2 -2
  25. comfyui_frontend_package/static/assets/{ServerConfigPanel-LtRqEOzu.js.map → ServerConfigPanel-B8Qy2naY.js.map} +1 -1
  26. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-C1UryadQ.css +1 -0
  27. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-CIYbxFB5.js +2 -0
  28. comfyui_frontend_package/static/assets/{SubscriptionRequiredDialogContent-CDaF1jns.js.map → SubscriptionRequiredDialogContent-CIYbxFB5.js.map} +1 -1
  29. comfyui_frontend_package/static/assets/{UserAvatar.vue_vue_type_script_setup_true_lang-48WRCWXz.js → UserAvatar.vue_vue_type_script_setup_true_lang-COE-v4cY.js} +2 -2
  30. comfyui_frontend_package/static/assets/{UserAvatar.vue_vue_type_script_setup_true_lang-48WRCWXz.js.map → UserAvatar.vue_vue_type_script_setup_true_lang-COE-v4cY.js.map} +1 -1
  31. comfyui_frontend_package/static/assets/{UserPanel-_xK7KdbS.js → UserPanel-D5s95p9f.js} +2 -2
  32. comfyui_frontend_package/static/assets/{UserPanel-_xK7KdbS.js.map → UserPanel-D5s95p9f.js.map} +1 -1
  33. comfyui_frontend_package/static/assets/{UserSelectView-B_VOz5XU.js → UserSelectView-TWmqiRNC.js} +2 -2
  34. comfyui_frontend_package/static/assets/{UserSelectView-B_VOz5XU.js.map → UserSelectView-TWmqiRNC.js.map} +1 -1
  35. comfyui_frontend_package/static/assets/{ValueControlPopover-BPJIx3_y.js → ValueControlPopover-oFjRn7nT.js} +2 -2
  36. comfyui_frontend_package/static/assets/{ValueControlPopover-BPJIx3_y.js.map → ValueControlPopover-oFjRn7nT.js.map} +1 -1
  37. comfyui_frontend_package/static/assets/{WidgetAudioUI-5nP5sugf.js → WidgetAudioUI-DYG-X1jd.js} +2 -2
  38. comfyui_frontend_package/static/assets/{WidgetAudioUI-5nP5sugf.js.map → WidgetAudioUI-DYG-X1jd.js.map} +1 -1
  39. comfyui_frontend_package/static/assets/{WidgetButton-Cd9G_fIM.js → WidgetButton-pJqn_pIo.js} +2 -2
  40. comfyui_frontend_package/static/assets/{WidgetButton-Cd9G_fIM.js.map → WidgetButton-pJqn_pIo.js.map} +1 -1
  41. comfyui_frontend_package/static/assets/{WidgetChart-Dz4GcJwO.js → WidgetChart-BntGUEQZ.js} +2 -2
  42. comfyui_frontend_package/static/assets/{WidgetChart-Dz4GcJwO.js.map → WidgetChart-BntGUEQZ.js.map} +1 -1
  43. comfyui_frontend_package/static/assets/{WidgetColorPicker-x6uZPjSf.js → WidgetColorPicker-JIRhb2dg.js} +2 -2
  44. comfyui_frontend_package/static/assets/{WidgetColorPicker-x6uZPjSf.js.map → WidgetColorPicker-JIRhb2dg.js.map} +1 -1
  45. comfyui_frontend_package/static/assets/{WidgetGalleria-DHPc3hwg.js → WidgetGalleria-CtUTrKmF.js} +2 -2
  46. comfyui_frontend_package/static/assets/{WidgetGalleria-DHPc3hwg.js.map → WidgetGalleria-CtUTrKmF.js.map} +1 -1
  47. comfyui_frontend_package/static/assets/WidgetInputNumber-DPITONSH.js +2 -0
  48. comfyui_frontend_package/static/assets/WidgetInputNumber-DPITONSH.js.map +1 -0
  49. comfyui_frontend_package/static/assets/{WidgetInputNumber.vue_vue_type_script_setup_true_lang-vY96bQKz.js → WidgetInputNumber.vue_vue_type_script_setup_true_lang-Cb9Vjdsr.js} +2 -2
  50. comfyui_frontend_package/static/assets/{WidgetInputNumber.vue_vue_type_script_setup_true_lang-vY96bQKz.js.map → WidgetInputNumber.vue_vue_type_script_setup_true_lang-Cb9Vjdsr.js.map} +1 -1
  51. comfyui_frontend_package/static/assets/{WidgetInputText-BbfUME5C.js → WidgetInputText-C9e7Utok.js} +2 -2
  52. comfyui_frontend_package/static/assets/{WidgetInputText-BbfUME5C.js.map → WidgetInputText-C9e7Utok.js.map} +1 -1
  53. comfyui_frontend_package/static/assets/{WidgetLayoutField.vue_vue_type_script_setup_true_lang-7bIn_5fI.js → WidgetLayoutField.vue_vue_type_script_setup_true_lang-B2h9BxrZ.js} +2 -2
  54. 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-B2h9BxrZ.js.map} +1 -1
  55. comfyui_frontend_package/static/assets/WidgetLegacy-DxWXa1q8.js +2 -0
  56. comfyui_frontend_package/static/assets/WidgetLegacy-DxWXa1q8.js.map +1 -0
  57. comfyui_frontend_package/static/assets/WidgetMarkdown-BbdYbwjR.js +2 -0
  58. comfyui_frontend_package/static/assets/{WidgetMarkdown-voa7Ohgp.js.map → WidgetMarkdown-BbdYbwjR.js.map} +1 -1
  59. comfyui_frontend_package/static/assets/WidgetRecordAudio-DQzt1Okr.js +2 -0
  60. comfyui_frontend_package/static/assets/WidgetRecordAudio-DQzt1Okr.js.map +1 -0
  61. comfyui_frontend_package/static/assets/WidgetSelect-BCeT8JsM.js +2 -0
  62. comfyui_frontend_package/static/assets/WidgetSelect-BCeT8JsM.js.map +1 -0
  63. comfyui_frontend_package/static/assets/{WidgetSelect.vue_vue_type_script_setup_true_lang-CT0J0vij.js → WidgetSelect.vue_vue_type_script_setup_true_lang-DnNAGP6n.js} +2 -2
  64. comfyui_frontend_package/static/assets/{WidgetSelect.vue_vue_type_script_setup_true_lang-CT0J0vij.js.map → WidgetSelect.vue_vue_type_script_setup_true_lang-DnNAGP6n.js.map} +1 -1
  65. comfyui_frontend_package/static/assets/{WidgetTextarea-SHMM72Cj.js → WidgetTextarea-DtdgdYZv.js} +2 -2
  66. comfyui_frontend_package/static/assets/{WidgetTextarea-SHMM72Cj.js.map → WidgetTextarea-DtdgdYZv.js.map} +1 -1
  67. comfyui_frontend_package/static/assets/{WidgetToggleSwitch-BmT1MQCn.js → WidgetToggleSwitch-BZnc8nGp.js} +2 -2
  68. comfyui_frontend_package/static/assets/{WidgetToggleSwitch-BmT1MQCn.js.map → WidgetToggleSwitch-BZnc8nGp.js.map} +1 -1
  69. comfyui_frontend_package/static/assets/{WidgetWithControl.vue_vue_type_script_setup_true_lang-CTEGWC4h.js → WidgetWithControl.vue_vue_type_script_setup_true_lang-39lKeCWN.js} +3 -3
  70. comfyui_frontend_package/static/assets/{WidgetWithControl.vue_vue_type_script_setup_true_lang-CTEGWC4h.js.map → WidgetWithControl.vue_vue_type_script_setup_true_lang-39lKeCWN.js.map} +1 -1
  71. comfyui_frontend_package/static/assets/{audioService-BcbrJBnn.js → audioService-CxmGtl4K.js} +2 -2
  72. comfyui_frontend_package/static/assets/{audioService-BcbrJBnn.js.map → audioService-CxmGtl4K.js.map} +1 -1
  73. comfyui_frontend_package/static/assets/{audioUtils-CJCA0wCG.js → audioUtils-B4fAuqez.js} +2 -2
  74. comfyui_frontend_package/static/assets/{audioUtils-CJCA0wCG.js.map → audioUtils-B4fAuqez.js.map} +1 -1
  75. comfyui_frontend_package/static/assets/commands-DHxq3nWN.js +2 -0
  76. comfyui_frontend_package/static/assets/commands-DHxq3nWN.js.map +1 -0
  77. comfyui_frontend_package/static/assets/{index-kQCr6fgp.js → index-B7ehZHKU.js} +2 -2
  78. comfyui_frontend_package/static/assets/{index-kQCr6fgp.js.map → index-B7ehZHKU.js.map} +1 -1
  79. comfyui_frontend_package/static/assets/{index-DvCHS_f2.js → index-BFHZsBkN.js} +25 -25
  80. comfyui_frontend_package/static/assets/index-BFHZsBkN.js.map +1 -0
  81. comfyui_frontend_package/static/assets/index-DJa4hQi7.css +1 -0
  82. comfyui_frontend_package/static/assets/index-tImAQKwZ.js +5 -0
  83. comfyui_frontend_package/static/assets/index-tImAQKwZ.js.map +1 -0
  84. comfyui_frontend_package/static/assets/{keybindingService-D3pQ4cJG.js → keybindingService-B_cUhEnt.js} +2 -2
  85. comfyui_frontend_package/static/assets/{keybindingService-D3pQ4cJG.js.map → keybindingService-B_cUhEnt.js.map} +1 -1
  86. comfyui_frontend_package/static/assets/{main-BFC5CmrY.js → main-B3vd7GL1.js} +3 -3
  87. comfyui_frontend_package/static/assets/main-B3vd7GL1.js.map +1 -0
  88. comfyui_frontend_package/static/assets/{main-M4YqFlAB.js → main-BKCc2TkD.js} +3 -3
  89. comfyui_frontend_package/static/assets/main-BKCc2TkD.js.map +1 -0
  90. comfyui_frontend_package/static/assets/{main-C0iYASPd.js → main-BNvzUx74.js} +3 -3
  91. comfyui_frontend_package/static/assets/main-BNvzUx74.js.map +1 -0
  92. comfyui_frontend_package/static/assets/main-BPu7av80.js +17 -0
  93. comfyui_frontend_package/static/assets/main-BPu7av80.js.map +1 -0
  94. comfyui_frontend_package/static/assets/{main-Dd3BY3MO.js → main-BQ4z0Xxc.js} +2 -2
  95. comfyui_frontend_package/static/assets/main-BQ4z0Xxc.js.map +1 -0
  96. comfyui_frontend_package/static/assets/{main-BI-3llij.js → main-BoyaoV4J.js} +2 -2
  97. comfyui_frontend_package/static/assets/main-BoyaoV4J.js.map +1 -0
  98. comfyui_frontend_package/static/assets/{main-DB30B6rY.js → main-CJ1ZmTbz.js} +3 -3
  99. comfyui_frontend_package/static/assets/main-CJ1ZmTbz.js.map +1 -0
  100. comfyui_frontend_package/static/assets/{main-D8hMMX5u.js → main-DxByB8u3.js} +2 -2
  101. comfyui_frontend_package/static/assets/main-DxByB8u3.js.map +1 -0
  102. comfyui_frontend_package/static/assets/{main-BdVq5c-Q.js → main-Dyk43SJi.js} +2 -2
  103. comfyui_frontend_package/static/assets/main-Dyk43SJi.js.map +1 -0
  104. comfyui_frontend_package/static/assets/{main-Cte3FX_T.js → main-QMLeyFJQ.js} +3 -3
  105. comfyui_frontend_package/static/assets/main-QMLeyFJQ.js.map +1 -0
  106. comfyui_frontend_package/static/assets/{main-BPVbVwRz.js → main-f7MqrMkx.js} +3 -3
  107. comfyui_frontend_package/static/assets/main-f7MqrMkx.js.map +1 -0
  108. comfyui_frontend_package/static/assets/nodeDefs-CqPqVYqF.js +54 -0
  109. comfyui_frontend_package/static/assets/nodeDefs-CqPqVYqF.js.map +1 -0
  110. comfyui_frontend_package/static/assets/settings-BDcsAddA.js +6 -0
  111. comfyui_frontend_package/static/assets/settings-BDcsAddA.js.map +1 -0
  112. comfyui_frontend_package/static/assets/settings-BscQZApb.js +6 -0
  113. comfyui_frontend_package/static/assets/settings-BscQZApb.js.map +1 -0
  114. comfyui_frontend_package/static/assets/settings-BxGvSZQt.js +6 -0
  115. comfyui_frontend_package/static/assets/settings-BxGvSZQt.js.map +1 -0
  116. comfyui_frontend_package/static/assets/settings-CRbIyGOx.js +6 -0
  117. comfyui_frontend_package/static/assets/settings-CRbIyGOx.js.map +1 -0
  118. comfyui_frontend_package/static/assets/settings-C_M95LcR.js +6 -0
  119. comfyui_frontend_package/static/assets/settings-C_M95LcR.js.map +1 -0
  120. comfyui_frontend_package/static/assets/{settings-C83z84hb.js → settings-CfAtcjYG.js} +3 -3
  121. comfyui_frontend_package/static/assets/settings-CfAtcjYG.js.map +1 -0
  122. comfyui_frontend_package/static/assets/settings-D1FGfDLO.js +6 -0
  123. comfyui_frontend_package/static/assets/settings-D1FGfDLO.js.map +1 -0
  124. comfyui_frontend_package/static/assets/settings-DIhoSYUg.js +6 -0
  125. comfyui_frontend_package/static/assets/settings-DIhoSYUg.js.map +1 -0
  126. comfyui_frontend_package/static/assets/settings-DR935Ify.js +6 -0
  127. comfyui_frontend_package/static/assets/settings-DR935Ify.js.map +1 -0
  128. comfyui_frontend_package/static/assets/settings-DwzAhg99.js +6 -0
  129. comfyui_frontend_package/static/assets/settings-DwzAhg99.js.map +1 -0
  130. comfyui_frontend_package/static/assets/settings-ydiuzS6H.js +6 -0
  131. comfyui_frontend_package/static/assets/settings-ydiuzS6H.js.map +1 -0
  132. comfyui_frontend_package/static/assets/{vendor-primevue-Bp7MMXLP.js → vendor-primevue-DeKsC2uk.js} +2 -2
  133. comfyui_frontend_package/static/assets/{vendor-primevue-Bp7MMXLP.js.map → vendor-primevue-DeKsC2uk.js.map} +1 -1
  134. comfyui_frontend_package/static/index.html +1 -1
  135. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.11.dist-info}/METADATA +1 -1
  136. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.11.dist-info}/RECORD +138 -130
  137. comfyui_frontend_package/static/assets/GraphView-BZLpyVMl.css +0 -1
  138. comfyui_frontend_package/static/assets/GraphView-C35zkdNN.js +0 -15
  139. comfyui_frontend_package/static/assets/GraphView-C35zkdNN.js.map +0 -1
  140. comfyui_frontend_package/static/assets/LazyImage.vue_vue_type_script_setup_true_lang-C58MdJKR.js +0 -2
  141. comfyui_frontend_package/static/assets/LazyImage.vue_vue_type_script_setup_true_lang-C58MdJKR.js.map +0 -1
  142. comfyui_frontend_package/static/assets/Load3D-BAJ3yM4D.js.map +0 -1
  143. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-BJ80blC4.css +0 -1
  144. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-CDaF1jns.js +0 -2
  145. comfyui_frontend_package/static/assets/WidgetInputNumber-Cnhf4Ann.js +0 -2
  146. comfyui_frontend_package/static/assets/WidgetInputNumber-Cnhf4Ann.js.map +0 -1
  147. comfyui_frontend_package/static/assets/WidgetLegacy-AkIJRd8v.js +0 -2
  148. comfyui_frontend_package/static/assets/WidgetLegacy-AkIJRd8v.js.map +0 -1
  149. comfyui_frontend_package/static/assets/WidgetMarkdown-voa7Ohgp.js +0 -2
  150. comfyui_frontend_package/static/assets/WidgetRecordAudio-DelweXYm.js +0 -2
  151. comfyui_frontend_package/static/assets/WidgetRecordAudio-DelweXYm.js.map +0 -1
  152. comfyui_frontend_package/static/assets/WidgetSelect-XG4R8CnZ.js +0 -2
  153. comfyui_frontend_package/static/assets/WidgetSelect-XG4R8CnZ.js.map +0 -1
  154. comfyui_frontend_package/static/assets/index-BB_8e12C.css +0 -1
  155. comfyui_frontend_package/static/assets/index-Cm0D-ExL.js +0 -5
  156. comfyui_frontend_package/static/assets/index-Cm0D-ExL.js.map +0 -1
  157. comfyui_frontend_package/static/assets/index-DvCHS_f2.js.map +0 -1
  158. comfyui_frontend_package/static/assets/main-BFC5CmrY.js.map +0 -1
  159. comfyui_frontend_package/static/assets/main-BI-3llij.js.map +0 -1
  160. comfyui_frontend_package/static/assets/main-BPVbVwRz.js.map +0 -1
  161. comfyui_frontend_package/static/assets/main-BdVq5c-Q.js.map +0 -1
  162. comfyui_frontend_package/static/assets/main-C0iYASPd.js.map +0 -1
  163. comfyui_frontend_package/static/assets/main-Cte3FX_T.js.map +0 -1
  164. comfyui_frontend_package/static/assets/main-D8hMMX5u.js.map +0 -1
  165. comfyui_frontend_package/static/assets/main-DB30B6rY.js.map +0 -1
  166. comfyui_frontend_package/static/assets/main-Dd3BY3MO.js.map +0 -1
  167. comfyui_frontend_package/static/assets/main-M4YqFlAB.js.map +0 -1
  168. comfyui_frontend_package/static/assets/settings-2GCqRrJG.js +0 -6
  169. comfyui_frontend_package/static/assets/settings-2GCqRrJG.js.map +0 -1
  170. comfyui_frontend_package/static/assets/settings-BwT6YspU.js +0 -6
  171. comfyui_frontend_package/static/assets/settings-BwT6YspU.js.map +0 -1
  172. comfyui_frontend_package/static/assets/settings-C83z84hb.js.map +0 -1
  173. comfyui_frontend_package/static/assets/settings-CpGAolL8.js +0 -6
  174. comfyui_frontend_package/static/assets/settings-CpGAolL8.js.map +0 -1
  175. comfyui_frontend_package/static/assets/settings-DHYEklkf.js +0 -6
  176. comfyui_frontend_package/static/assets/settings-DHYEklkf.js.map +0 -1
  177. comfyui_frontend_package/static/assets/settings-DLFPCNJr.js +0 -6
  178. comfyui_frontend_package/static/assets/settings-DLFPCNJr.js.map +0 -1
  179. comfyui_frontend_package/static/assets/settings-JVJwzdtf.js +0 -6
  180. comfyui_frontend_package/static/assets/settings-JVJwzdtf.js.map +0 -1
  181. comfyui_frontend_package/static/assets/settings-Tg5NBf0t.js +0 -6
  182. comfyui_frontend_package/static/assets/settings-Tg5NBf0t.js.map +0 -1
  183. comfyui_frontend_package/static/assets/settings-_tes1LYa.js +0 -6
  184. comfyui_frontend_package/static/assets/settings-_tes1LYa.js.map +0 -1
  185. comfyui_frontend_package/static/assets/settings-cav_bQJj.js +0 -6
  186. comfyui_frontend_package/static/assets/settings-cav_bQJj.js.map +0 -1
  187. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.11.dist-info}/WHEEL +0 -0
  188. {comfyui_frontend_package-1.37.9.dist-info → comfyui_frontend_package-1.37.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"mappings":"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","names":["TemplateIncludeOnDistributionEnum","getCategoryIcon","__name","categoryId","generateCategoryId","categoryGroup","categoryTitle","useWorkflowTemplatesStore","defineStore","customTemplates","shallowRef","coreTemplates","englishTemplates","isLoaded","ref","knownTemplateNames","getTemplateByName","name","enhancedTemplates","template","categoryFilters","addLocalizedFieldsToTemplate","st","normalizeI18nKey","localizeTemplateList","templates","localizeTemplateCategory","templateCategory","createAllCategory","coreTemplatesWithSourceModule","category","customTemplatesWithSourceModule","moduleName","groupedTemplates","computed","allTemplates","t","enhancedTemplate","filterTemplatesByCategory","filter","navGroupedTemplates","items","essentialCats","cat","essentialCat","categoryIcon","categoryGroups","group","groupName","word","extensionItems","loadWorkflowTemplates","api","locale","i18n","coreResult","englishResult","isCloud","coreNames","customNames","error","getEnglishMetadata","templateName","OnCloseKey","BREAKPOINTS","PANEL_SIZES","slots","useSlots","closeDialog","inject","notMobile","useBreakpoints","isLeftPanelOpen","isRightPanelOpen","mobileMenuOpen","hasRightPanel","watch","isDesktop","showLeftPanel","toggleLeftPanel","toggleRightPanel","emit","__emit","isCollapsed","__props","value","toggleCollapse","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_createElementVNode","_renderSlot","_ctx","collapsedGroups","getFirstItemId","firstEntry","activeItem","usePopoverSizing","options","minWidth","maxWidth","style","selectedItems","_useModel","searchQuery","useI18n","selectedCount","popoverStyle","attrs","useAttrs","originalOptions","fuseOptions","results","useFuse","filteredOptions","searchResults","result","item","useAssetFilterOptions","assets","availableFileFormats","extensions","toValue","asset","extension","uniqWith","a","b","format","availableBaseModels","models","baseModel","model","sortOptions","ownershipOptions","fileFormats","baseModels","sortBy","ownership","hasMutableAssets","handleFilterChange","option","isOpen","popover","hide","__expose","promptTextReal","confirmTextX","cancelTextX","disabled","showConfirmDialog","dialogStore","useDialogStore","headerProps","props","footerProps","ConfirmHeader","ConfirmBody","ConfirmFooter","settingStore","useSettingStore","flags","useFeatureFlags","toastStore","useToastStore","dropdownMenuButton","useTemplateRef","titleId","useId","descId","isEditing","newNameRef","displayName","showAssetOptions","tooltipDelay","isLoading","useImage","confirmDeletion","assetName","promptText","optionsDisabled","confirmDialog","cn","assetService","resolve","err","startAssetRename","assetRename","newName","assetsWithKey","gridStyle","getAssetDescription","getAssetBaseModel","filterByCategory","tag","filterByFileFormats","formats","formatSet","filterByBaseModels","modelSet","filterByOwnership","useAssetBrowser","assetsSource","selectedCategory","filters","transformAssetForDisplay","typeTag","description","badges","badgeLabel","stats","d","availableCategories","categories","uniqueCategories","contentTitle","categoryFilteredAssets","fuseResults","searchFiltered","filteredAssets","sortedAssets","updateFilters","newFilters","ACRONYM_TAGS","formatCategoryLabel","raw","segment","upper","lower","assetStore","useAssetsStore","modelToNodeStore","useModelToNodeStore","breakpoints","breakpointsTailwind","provide","cacheKey","fetchedAssets","isStoreLoading","refreshAssets","isUploadButtonEnabled","showUploadDialog","useModelUpload","primaryCategoryTag","tagFromAssets","mapped","activeCategoryTag","displayTitle","label","shouldShowLeftPanel","handleClose","handleAssetSelectAndEmit","dialogComponentProps","useAssetBrowserDialog","dialogKey","show","handleAssetSelected","AssetBrowserModal","browse","clickableClasses","togglePopover","event","variant","menuBackgroundStyle","labelClasses","textClasses","iconColorClass","iconClass","dotClasses","popoverPt","POLL_INTERVAL_MS","MAX_POLL_DURATION_MS","subscribe","isActiveSubscription","fetchStatus","showSubscriptionDialog","useSubscription","telemetry","useTelemetry","isPolling","pollInterval","isAwaitingStripeSubscription","startPollingSubscriptionStatus","startTime","poll","stopPolling","awaiting","isActive","handleSubscribe","onBeforeUnmount","DIALOG_KEY","useSubscriptionDialog","dialogService","useDialogService","defineAsyncComponent","__vitePreload","toast","useToast","newMessages","message","messagesToRemove","requested","updateToastPosition","styleElement","createStyleElement","rect","nextTick","_runWhenIdle","runWhenGlobalIdle","safeGlobal","_targetWindow","runner","_timeout","disposed","end","targetWindow","timeout","handle","workspaceState","useWorkspaceStore","exitFocusMode","watchEffect","app","workflowStore","useWorkflowStore","handleBeforeUnload","onMounted","workspaceStore","rightSidePanelStore","useRightSidePanelStore","sidebarTabStore","useSidebarTabStore","sidebarLocation","unifiedWidth","focusMode","storeToRefs","activeSidebarTabId","activeSidebarTab","bottomPanelVisible","useBottomPanelStore","rightSidePanelVisible","sidebarPanelVisible","sidebarStateKey","onResizestart","splitterRefreshKey","firstPanelStyle","lastPanelStyle","buildTooltipConfig","ComfyRunButton","settingsStore","commandStore","useCommandStore","isExecutionIdle","useExecutionStore","position","visible","tabContainer","panelRef","dragHandleRef","isDocked","useLocalStorage","storedPosition","x","y","isDragging","useDraggable","minY","watchDebounced","newX","newY","setInitialPosition","screenWidth","screenHeight","menuWidth","menuHeight","clamp","captureLastDragState","comfyRunButtonResolved","newVisible","useEventListener","lastDragState","newIsDragging","distanceLeft","distanceRight","distanceTop","distanceBottom","closestEdge","min","curr","verticalRatio","horizontalRatio","isMouseOverDropZone","onMouseEnterDropZone","onMouseLeaveDropZone","dragging","cancelJobTooltipConfig","cancelCurrentJob","actionbarClass","panelClass","useWorkflowActionsMenu","startRename","isRoot","includeDelete","workflow","workflowService","useWorkflowService","bookmarkStore","useWorkflowBookmarkStore","subgraphStore","useSubgraphStore","targetWorkflow","ensureWorkflowActive","wf","isBlueprint","addItem","icon","command","separator","isNodeMissingDefinition","node","nodeDefsByName","nodeName","collectMissingNodes","graph","lookup","unref","collectAllNodes","graphHasMissingNodes","nodeDefStore","useNodeDefStore","hasMissingNodes","menu","itemLabel","itemInputRef","wrapperRef","rename","initialName","ComfyWorkflow","appendJsonExt","navigationStore","useSubgraphNavigationStore","tooltipText","menuItems","handleClick","inputBlur","doRename","useOverflowObserver","element","isOverflowing","disposeFns","checkOverflowFn","checkOverflow","debounce","useMutationObserver","useResizeObserver","readonly","fn","MIN_WIDTH","ITEM_GAP","ITEM_PADDING","ICON_WIDTH","breadcrumbRef","rootItemRef","setItemRef","el","workflowName","collapseTabs","overflowingTabs","isInSubgraph","home","canvas","useCanvasStore","subgraph","title","rootGraph","forEachSubgraphNode","activeItemKey","handleMenuClick","handleBackClick","breadcrumbElement","overflowObserver","itemsWithIcon","separators","separatorWidth","itemsWidth","separatorsWidth","gapsWidth","totalWidth","containerWidth","onUpdated","cancelJobTooltip","clearQueueTooltip","morePopoverRef","moreTooltipConfig","onMoreClick","onClearHistoryFromMenu","jobItemPopoverRef","open","onEntry","entry","filterPopoverRef","sortPopoverRef","filterTooltipConfig","sortTooltipConfig","visibleJobTabs","jobTabs","tab","onFilterClick","selectWorkflowFilter","onSortClick","selectSortMode","tabLabel","sortLabel","mode","extractExecutionError","task","messages","record","detail","useJobErrorReporting","taskForJob","copyToClipboard","dialog","errorMessageValue","formatElapsedTime","ms","totalSec","minutes","seconds","pickRecentDurations","queueStore","useQueueEstimates","executionStore","jobState","firstSeenTs","jobsAhead","nowTs","runningWorkflowCount","showParallelQueuedStats","recentDurations","runningRemainingRangeSeconds","durations","sorted","avg","sum","p75","running","now","remaining","timestamp","startTs","elapsed","minLo","range","minHi","estimateRangeSeconds","ahead","runningCount","batches","estimateRemainingRangeSeconds","baseTs","lo","hi","timeElapsedValue","copyAriaLabel","useQueueStore","workflowValue","wid","activeId","jobIdValue","useCopyToClipboard","copyJobId","pid","findIn","arr","isInitializing","jobStateFromTask","queuedAtValue","formatClockTime","currentQueueIndex","idx","queuePositionValue","n","timer","onUnmounted","formatEta","hiS","loS","loM","hiM","estimatedStartInValue","estimatedFinishInValue","baseRows","extraRows","rows","endTs","execMs","generatedOnValue","totalGenTimeValue","failedAfterValue","copyErrorMessage","reportJobError","imgRef","width","height","onImgLoad","progressBarContainerClass","progressBarPrimaryClass","progressBarSecondaryClass","hasProgressPercent","hasAnyProgressPercent","progressPercentStyle","useProgressBarBackground","cancelTooltipConfig","deleteTooltipConfig","rowRef","showDetails","onRowEnter","isPreviewVisible","onRowLeave","onPopoverEnter","onPopoverLeave","previewHideTimer","previewShowTimer","clearPreviewHideTimer","clearPreviewShowTimer","canShowPreview","scheduleShowPreview","scheduleHidePreview","onIconEnter","onIconLeave","onPreviewEnter","onPreviewLeave","popoverPosition","updatePopoverPosition","gap","isAnyPopoverVisible","isHovered","iconForJobState","shouldSpin","computedShowClear","emitDetailsLeave","onCancelClick","onDeleteClick","onContextMenu","emitCancelItem","emitDeleteItem","activeDetailsId","hideTimer","showTimer","clearHideTimer","clearShowTimer","onDetailsEnter","jobId","onDetailsLeave","resetActiveDetails","groups","currentMenuItem","jobContextMenuRef","jobMenuEntries","useJobMenu","onCancelItemEvent","onDeleteItemEvent","onMenuItem","onJobMenuAction","wrapWithErrorHandlingAsync","useErrorHandling","isClearing","clearHistory","onConfirm","onCancel","useCompletionSummary","lastActiveStartTs","_summary","dismissTimer","clearDismissTimer","startDismissTimer","clearSummary","active","prev","start","finished","ts","completedCount","failedCount","imagePreviews","state","preview","useResultGallery","getFilteredTasks","galleryActiveIndex","galleryItems","activeUrl","o","assetsStore","assetSelectionStore","useAssetSelectionStore","totalPercentFormatted","currentNodePercentFormatted","totalProgressStyle","currentNodeProgressStyle","useQueueProgress","isOverlayHovered","internalExpanded","isExpanded","completionSummary","hasCompletionSummary","queuedCount","isExecuting","hasActiveJob","activeJobsCount","overlayState","showBackground","isVisible","containerClass","bottomRowClass","headerTitle","concurrentWorkflowCount","showConcurrentIndicator","selectedJobTab","selectedWorkflowFilter","selectedSortMode","hasFailedJobs","filteredTasks","groupedJobItems","currentNodeName","useJobList","displayedJobGroups","onCancelItem","promptId","onDeleteItem","openResultGallery","setExpanded","expanded","openExpandedFromEmpty","viewAllJobs","onSummaryClick","openAssetsSidebar","focusAssetInSidebar","assetId","existingAsset","inspectJobAsset","cancelQueuedWorkflows","interruptAll","promptIds","id","showClearHistoryDialog","QueueClearHistoryDialog","useActionBarButtonStore","extensionStore","useExtensionStore","e","actionBarButtonStore","isMobile","buildDocsUrl","docsPaths","useExternalLink","userDisplayName","userEmail","userPhotoUrl","handleSignOut","useCurrentUser","authActions","useFirebaseAuthActions","authStore","useFirebaseAuthStore","subscriptionTierName","subscriptionTier","subscriptionDialog","formattedBalance","cents","formatCreditsFromCents","canUpgrade","tier","handleOpenUserSettings","handleOpenPlansAndPricing","handleOpenPlanAndCreditsSettings","handleTopUp","handleOpenPartnerNodesInfo","handleLogout","handleSubscribed","isLoggedIn","photoURL","closePopover","handleSignIn","apiNodesOverviewUrl","popoverRef","hideTimeout","showTimeout","showPopover","cancelHidePopover","hidePopover","releaseApiClient","axios","getComfyApiBaseUrl","useReleaseService","url","handleApiError","context","routeSpecificErrors","axiosError","status","data","executeApiRequest","apiCall","errorContext","isAbortError","params","signal","endpoint","useReleaseStore","releases","releaseService","systemStatsStore","useSystemStatsStore","currentVersion","releaseVersion","releaseStatus","releaseTimestamp","showVersionUpdates","recentRelease","recentReleases","THREE_DAYS_MS","compareVersions","currentVer","valid","compare","isNewVersionAvailable","isLatestVersion","hasMediumOrHighAttention","attention","shouldShowToast","isElectron","shouldShowRedDot","version","shouldShowPopup","handleSkipRelease","handleShowChangelog","handleWhatsNewSeen","fetchReleases","until","fetchedReleases","stringToLocale","initialize","managerState","useManagerState","toastErrorHandler","queueUIStore","useQueueUIStore","isQueueOverlayExpanded","releaseStore","showReleaseRedDot","shouldShowConflictRedDot","useConflictAcknowledgment","isTopMenuHovered","isIntegratedTabBar","queueHistoryTooltipConfig","customNodesManagerTooltipConfig","isRightSidePanelOpen","rightSidePanelTooltipConfig","legacyCommandsContainerRef","toggleQueueOverlay","openCustomNodeManager","ManagerTab","toastError","mountCustomExtension","bottomPanelStore","isShortcutsTabActive","activeTabId","shouldCapitalizeTab","tabId","getTabDisplayTitle","openKeybindingSettings","closeBottomPanel","useAbsolutePosition","useTransform","lgCanvas","canvasPosToClientPos","updateCanvasPosition","useCanvasPositionConversion","computeStyle","pos","size","scale","left","top","config","intersect","x1","y1","x2","y2","useDomClipping","margin","calculateClipPath","elementRect","canvasRect","isSelected","selectedArea","offset","intersection","clipX","clipY","clipWidth","clipHeight","canvasElement","clipPath","widget","widgetElement","positionStyle","updatePosition","clippingStyle","updateClipPath","canvasStore","enableDomClipping","updateDomClipping","selectedNode","renderArea","selectedAreaConfig","useElementBounding","widgetState","_","__","oldVisible","isDOMWidget","tooltip","mountElementIfVisible","domWidgetStore","useDomWidgetStore","widgetStates","updateWidgets","lowQuality","currentGraph","isInCorrectGraph","whenever","useChainCallback","useZoomControls","isModalVisible","showModal","hideModal","toggleModal","hasActivePopup","useMinimapGraph","onGraphChanged","nodeStatesCache","linksCache","lastNodeCount","updateFlags","layoutStoreVersion","layoutStore","originalCallbacksMap","handleGraphChangedThrottled","useThrottleFn","setupEventListeners","g","originalCallbacks","cleanupEventListeners","oldGraph","structureChanged","positionChanged","connectionChanged","dataSource","MinimapDataSourceFactory","currentNodeCount","nodes","nodeId","currentState","currentNodeIds","currentLinks","useMinimapInteraction","containerRef","bounds","centerViewOn","containerRect","updateContainerRect","handlePointerDown","target","handlePointerMove","offsetX","offsetY","worldX","worldY","releasePointer","c","ds","delta","newScale","useMinimapRenderer","canvasRef","settings","needsFullRedraw","needsBoundsUpdate","renderMinimap","ctx","renderMinimapToCanvas","updateBounds","updateViewport","useMinimapSettings","colorPaletteStore","useColorPaletteStore","nodeColors","showLinks","showGroups","renderBypass","renderError","isLightTheme","containerStyles","panelStyles","useMinimapViewport","viewportTransform","canvasDimensions","updateCanvasDimensions","canvasEl","dpr","calculateGraphBounds","sourceBounds","enforceMinimumBounds","calculateScale","calculateMinimapScale","viewportWidth","viewportHeight","centerOffsetX","centerOffsetY","startViewportSync","stopViewportSync","useRafFn","useMinimap","canvasRefMaybe","containerRefMaybe","minimapRef","initialized","updateOption","key","renderer","viewport","interaction","graphManager","pauseChangeDetection","resumeChangeDetection","init","destroy","newCanvas","oldCanvas","newGraph","toggle","setMinimapRef","viewportStyles","transform","buttonRef","isCanvasReadOnly","currentModeIcon","unlockCommandText","lockCommandText","setMode","minimap","formatKeySequence","interval","applyZoom","val","inputValue","executeCommand","startRepeat","cmd","stopRepeat","filteredMinimapStyles","zoomInCommandText","zoomOutCommandText","zoomToFitCommandText","zoomInputContainer","newVal","canvasInteractions","useCanvasInteractions","stringifiedMinimapStyles","buttonGroupKeys","buttonKeys","additionalButtonStyles","buttonStyles","buttonGroupStyles","acc","linkHidden","LiteGraph","fitViewCommandText","minimapCommandText","zoomButtonClass","minimapButtonClass","fitViewTooltip","shortcut","minimapTooltip","linkVisibilityTooltip","linkVisibilityAriaLabel","linkVisibleClass","onMinimapToggleClick","onLinkVisibilityToggleClick","idleTimeout","tooltipRef","hideTooltip","showTooltip","comfyApp","onIdle","ctor","nodeDef","inputSlot","isOverNodeInput","inputName","translatedTooltip","outputSlot","isOverNodeOutput","toggleBypass","toLightThemeColor","color","adjustColor","showColorPicker","NO_COLOR_OPTION","colorOptions","LGraphCanvas","selectedColorOption","applyColor","colorOption","colorName","canvasColorOption","isColorable","currentColorOption","currentColor","localizedCurrentColorName","updateColorSelectionFromNode","newSelectedItems","getItemsColorOption","isSingleSubgraph","hasAnySelection","useSelectionState","isUnpackVisible","isConvertVisible","isDeletable","selectedNodes","buttonHovered","selectedOutputNodes","isLGraphNode","isOutputNode","outputNodeStokeStyle","handleMouseEnter","handleMouseLeave","onInfoClick","open3DViewer","isSingleImageNode","openMaskEditor","isRefreshableWidget","useRefreshableSelection","graphStore","refreshableWidgets","isRefreshable","refreshSelected","SubgraphNode","moreOptionsOpen","forceCloseMoreOptionsSignal","restoreMoreOptionsSignal","moreOptionsRestorePending","moreOptionsWasOpenBeforeDrag","moreOptionsSelectionSignature","buildSelectionSignature","store","isLGraphGroup","currentSelectionMatchesSignature","useSelectionToolboxPosition","toolboxRef","getSelectableItems","useSelectedLiteGraphItems","shouldRenderVueNodes","useVueFeatureFlags","worldPosition","canvasLeft","canvasTop","litegraphDragging","vueNodeDragging","updateSelectionBounds","selectableItems","allBounds","layout","LGraphNode","LGraphGroup","unionBounds","computeUnionBounds","updateTransform","screenX","screenY","startSync","stopSync","changed","v","handleDragStateChange","handleDragStart","handleDragEnd","selectionMatches","shouldRestore","resetMoreOptionsState","HARD_BLACKLIST","CORE_MENU_ITEMS","normalizeLabel","isDuplicateItem","existingItems","normalizedLabel","equivalents","existingNormalized","values","isCoreMenuItem","removeDuplicateMenuOptions","itemsByLabel","itemsWithoutLabel","opt","seenLabels","duplicates","vueItem","MENU_ORDER","getMenuItemOrder","index","buildStructuredMenu","deduplicated","coreItemsMap","deleteItem","orderedCoreItems","coreLabels","getSectionNumber","lastSection","itemIndex","currentSection","convertContextMenuToOptions","applyStructuring","convertSubmenuToOptions","capturedSubmenu","captureDynamicSubmenu","capturedItems","capturedOptions","OriginalContextMenu","mockEvent","mockMenu","subOption","stripHtmlTags","html","DOMPurify","useCanvasRefresh","useNodeCustomization","canvasRefresh","shapeOptions","RenderShape","shapeOption","firstColorableItem","currentBgColor","currentShape","useGroupMenuOptions","groupContext","padding","bump","shape","groupNodes","allSame","i","createModeAction","LGraphEventMode","useImageMenuOptions","openImage","img","copyImage","blob","saveImage","downloadFile","useSelectedNodeActions","getSelectedNodes","toggleSelectedNodesMode","optimalSize","filterOutputNodes","useNodeMenuOptions","applyShape","adjustNodeSize","toggleNodeCollapse","toggleNodePin","toggleNodeBypass","runBranch","shapeSubmenu","colorSubmenu","showNodeHelp","states","useFrameNodes","titleEditorStore","useTitleEditorStore","hasMultipleSelection","canFrame","useNodeArrangement","alignOptions","distributeOptions","alignOption","newPositions","alignNodes","distributeOption","distributeNodes","useSelectionOperations","currentTitle","newTitle","titledItem","baseTitle","useSelectionMenuOptions","copySelection","duplicateSelection","deleteSelection","renameSelection","applyAlign","applyDistribute","convertToSubgraph","unpackSubgraph","addSubgraphToLibrary","useSubgraphOperations","frameNodes","alignSubmenu","align","distributeSubmenu","distribute","hasSubgraphs","convertOption","BadgeVariant","convertToGroupNodes","nodeOptionsInstance","toggleNodeOptions","showNodeOptions","registerNodeOptionsInstance","instance","markAsVueOptions","useMoreOptionsMenu","hasSubgraphsComputed","hasImageNode","hasOutputNodesSelected","computeSelectionFlags","getImageMenuOptions","getNodeInfoOption","getNodeVisualOptions","getPinOption","getBypassOption","getRunBranchOption","getFitGroupToNodesOption","getGroupColorOptions","getGroupModeOptions","getBasicSelectionOptions","getSubgraphOptions","getMultipleNodesOptions","hasMultipleNodes","optionsVersion","menuOptions","selectedGroups","hasSubgraphsSelected","litegraphOptions","rawItems","basicOps","pin","bypass","groupModes","visualOptions","markedVueOptions","merged","menuOptionsWithSubmenu","getCurrentShape","handleSubmenuClick","isShapeSelected","isColorSubmenu","contextMenu","colorPickerMenu","lastScale","lastOffsetX","lastOffsetY","updateMenuPosition","menuEl","contextMenuInstance","isColorOption","convertToMenuItem","isColor","sub","showColorPopover","handleColorSelect","onMenuShow","onMenuHide","extensionService","useExtensionService","extensionToolboxCommands","commandIds","commandId","isSingleNode","hasAny3DNodeSelected","showInfoButton","showConvertToSubgraph","showFrameNodes","showSubgraphButtons","showBypass","showLoad3DViewer","showMaskEditor","showDelete","showRefresh","showExecute","showAnyPrimaryActions","showAnyControlActions","showInput","editedTitle","inputPositionStyle","inputFontStyle","inputStyle","previousCanvasDraggable","onEdit","newValue","trimmedTitle","nodeHelpStore","useNodeHelpStore","nodeInfo","info","isQuerying","debouncedSearchQuery","refDebounced","updateKeyRef","toRef","onCleanup","isCleanup","cleanupFn","cb","CONTROL_OPTIONS","isControlOption","normalizeControlOption","widgetWithVueTrack","customRef","track","trigger","useReactiveWidgetValue","getControlWidget","cagWidget","w","getNodeType","isProxyWidget","normalizeWidgetValue","safeWidgetMapper","slotMetadata","spec","slotInfo","borderStyle","callback","isValidWidgetValue","useGraphNodeManager","createNode","deleteNode","setSource","useLayoutMutations","vueNodeData","reactive","nodeRefs","refreshNodeSlots","nodeRef","currentData","input","extractVueNodeData","subgraphId","reactiveWidgets","shallowReactive","reactiveInputs","safeWidgets","reactiveComputed","nodeType","apiNode","getNode","syncWithGraph","currentNodes","handleNodeAdded","originalCallback","initializeVueNodeLayout","nodePosition","nodeSize","LayoutSource","handleNodeRemoved","createCleanupFunction","originalOnNodeAdded","originalOnNodeRemoved","originalOnTrigger","cleanup","triggerHandlers","propertyEvent","slotErrorsEvent","slotLinksEvent","NodeSlotType","isCollapse","getWidgetComponent","getComponent","WidgetLegacy","onWidgetValueChange","isEmpty","displayLabel","widgetsSectionDataList","widgets","searchedWidgetsSectionDataList","searcher","query","words","type","getOptionValue","valueField","getOptionLabel","labelField","optionValue","handleSelect","targetNodes","nodeState","triggerRef","isPinned","getColorValue","nodeColorEntries","nodeColor","theColorOptions","getIcon","draggableList","draggableItems","proxyWidgets","activeNode","parseProxyWidgets","activeWidgets","mapWidgets","wNode","widgetItemToProperty","interiorWidgets","updatePreviews","useLitegraphService","interiorNodes","nodeWidgets","candidateWidgets","widgetItem","matchesPropertyItem","filteredCandidates","recommendedWidgets","isRecommendedWidget","filteredActive","toKey","demote","subgraphNode","demoteWidget","promote","promoteWidget","showAll","toAdd","hideAll","propertyItem","matchesWidgetItem","showRecommended","setDraggableState","DraggableList","reorderedItems","oldPosition","newIndex","newPosition","aw","pruneDisconnected","activeTab","isEditingSubgraph","panelIcon","hasSelection","isSubgraphNode","isSingleNodeSelected","selectionCount","panelTitle","closePanel","tabs","list","handleTitleEdit","handleTitleCancel","useSearchBoxStore","useMouse","newSearchBoxEnabled","setPopoverRef","toggleVisible","_sfc_main","AutoComplete","inputEl","oldVal","showCategory","showIdName","showNodeFrequency","nodeFrequencyStore","useNodeFrequencyStore","nodeFrequency","nodeBookmarkStore","useNodeBookmarkStore","isBookmarked","enableNodePreview","autoCompletePlus","nodeSearchFilterVisible","inputId","suggestions","hoveredSuggestion","currentQuery","placeholder","debouncedTrackSearch","search","queryIsEmpty","onAddNode","inputElement","reFocusInput","onAddFilter","filterAndValue","onRemoveFilter","setHoverSuggestion","triggerEvent","listenerController","disconnectOnReset","searchBoxStore","litegraphService","dismissable","getNewNodeLocation","nodeFilters","addFilter","removeFilter","f","toRaw","clearFilters","addNode","showSearchBox","showNewSearchBox","getFirstLink","firstLink","dataType","showContextMenu","fromSlot","toType","commonOptions","cancelResetOnContextClose","afterRerouteId","connectionOptions","createEvent","reset","handleDroppedOnCanvas","canvasEventHandler","linkReleaseAction","linkReleaseActionShift","preventDefault","cancelNextReset","LinkReleaseTriggerAction","useHelpCenterStore","triggerLocation","location","useHelpCenter","triggerFrom","helpCenterStore","isHelpCenterVisible","conflictDetection","useConflictDetection","showNodeConflictDialog","markConflictsAsSeen","toggleHelpCenter","closeHelpCenter","handleWhatsNewDismissed","showConflictModal","isDismissed","latestRelease","shouldShow","changelogUrl","changelogBaseUrl","versionAnchor","formatVersionAnchor","formattedContent","contentWithoutImages","trimmedContent","renderMarkdownToHtml","fallbackContent","startAutoHide","dismissToast","clearAutoHide","handleSkip","handleLearnMore","handleUpdate","markdown","imageMatch","image","contentWithoutImage","reorderedContent","closePopup","TIME_UNITS","SUBMENU_CONFIG","staticUrls","openedAt","isSubmenuVisible","submenuRef","submenuStyle","hoverTimeout","hasReleases","shouldShowManagerRedDot","isNewManagerUI","moreItems","openExternalLink","openDevTools","onReinstall","hasVisibleMoreItems","moreMenuItem","PuzzleIcon","onUpdateComfyUI","clearHoverTimeout","calculateSubmenuPosition","button","submenuWidth","estimatedHeight","submenuHeight","formatReleaseDate","dateString","date","diffTime","timeUnits","unit","onMenuItemHover","moreButton","onMenuItemLeave","onSubmenuHover","onSubmenuLeave","electronAPI","updateComfyUI","rebootComfyUI","useComfyManagerService","onReleaseClick","release","attributes","containerClasses","structureClasses","roundedClasses","variantClasses","aspectRatio","topStyle","defaultSlotClasses","slotClasses","baseClasses","variantStyles","chipClasses","contentRef","SLIDER_START_POSITION","isVideoType","sliderPosition","elementX","elementWidth","isOutside","useMouseInElement","outside","baseImageClass","overlayImageClass","sizeClasses","opacityClasses","useLazyPagination","itemsPerPage","initialPage","currentPage","loadedPages","itemsArray","itemData","paginatedItems","loadedPageNumbers","endIndex","hasMoreItems","loadedPagesArray","totalPages","loadNextPage","nextPage","newLoadedPages","length","useTemplateRankingStore","largestUsageScore","normalizeUsageScore","usage","computeFreshness","dateStr","daysSinceAdded","searchRank","internal","freshness","defaultFuseOptions","useTemplateFiltering","rankingStore","selectedModels","selectedUseCases","selectedRunsOn","templatesArray","templateData","fuse","Fuse","availableModels","availableUseCases","tagSet","availableRunsOn","filteredBySearch","filteredByModels","selectedModel","filteredByUseCases","selectedTag","filteredByRunsOn","isExternalAPI","isComfyUI","getVramMetric","sortedTemplates","scoreA","nameA","nameB","dateA","vramA","vramB","sizeA","sizeB","filteredTemplates","resetFilters","removeModelFilter","m","removeUseCaseFilter","removeRunsOnFilter","runsOn","r","filteredCount","totalCount","debouncedTrackFilterChange","loadFuseOptions","fetchedOptions","useTemplateWorkflows","workflowTemplatesStore","selectedTemplate","loadingTemplateId","isTemplatesLoaded","allTemplateGroups","loadTemplates","selectFirstTemplateCategory","firstCategory","selectTemplateCategory","getTemplateThumbnailUrl","sourceModule","basePath","indexSuffix","getTemplateTitle","fallback","getTemplateDescription","loadWorkflowTemplate","json","actualSourceModule","fetchTemplateJson","createGridStyle","columns","sessionStartTime","templateWasSelected","distributions","onClose","getEffectiveSourceModule","getBaseThumbnailSrc","sm","getOverlayThumbnailSrc","openTutorial","navItems","selectedNavItem","navigationFilteredTemplates","coordinateNavAndSort","source","isPopularNav","isPopularSort","selectedModelObjects","selectedUseCaseObjects","useCase","selectedRunsOnObjects","loadingTemplate","hoveredTemplate","cardRefs","templateListKey","modelSearchText","modelOptions","useCaseOptions","runsOnOptions","modelFilterLabel","useCaseFilterLabel","runsOnFilterLabel","loadTrigger","shouldUsePagination","paginatedTemplates","isLoadingMore","hasMoreTemplates","resetPagination","displayTemplates","useIntersectionObserver","onLoadWorkflow","pageTitle","navItem","useAsyncState","isTemplateVisibleOnDistribution","useWorkflowTemplateSelectorDialog","WorkflowTemplateSelectorDialog","whileMouseDown","elementOrEvent","iteration","intervalId","dispose","disposeGlobal","disposeLocal","menuItemStore","useMenuItemStore","colorPaletteService","useColorPaletteService","menuRef","nodes2Enabled","onLogoMenuClick","translateMenuItem","translatedLabel","showSettings","defaultPanel","SettingDialogHeader","SettingDialogContent","showManageExtensions","themeMenuItems","palette","extraMenuItems","translatedItems","helpIndex","helpItem","isLastItem","isZoomCommand","handleZoomMouseDown","handleItemClick","hasActiveStateSiblings","handleNodes2ToggleClick","onNodes2ToggleChange","overlayValue","shouldShowBadge","computedTooltip","toggleConsole","getCommand","showSettingsDialog","isShortcutsPanelVisible","toggleShortcutsPanel","userStore","useUserStore","logout","isSmall","openTemplates","ENTER_OVERFLOW_MARGIN","EXIT_OVERFLOW_MARGIN","sideToolbarRef","topToolbarRef","bottomToolbarRef","sidebarStyle","isConnected","selectedTab","onTabClick","keybindingStore","useKeybindingStore","getTabTooltipSuffix","keybinding","groupClasses","containerHeight","topHeight","bottomHeight","contentHeight","useTopbarBadgeStore","isXl","isLg","displayMode","topbarBadgeStore","textShadow","subIconStyle","POPOVER_WIDTH","thumbnailUrl","isActiveTab","toRefs","positionRef","middle","popoverWidth","halfWidth","shift","newPos","workflowTabRef","workflowThumbnail","useWorkflowThumbnail","autoSaveSetting","autoSaveDelay","shouldShowStatusIndicator","closeWorkflows","onCloseWorkflow","tabGetter","usePragmaticDraggable","usePragmaticDroppable","fromIndex","toIndex","rightClickedTab","showOverflowArrows","leftArrowEnabled","rightArrowEnabled","workflowToOption","selectedWorkflow","onWorkflowChange","rightClickedWorkflow","baseMenuItems","contextMenuItems","handleWheel","scrollElement","scrollAmount","scrollContent","scroll","direction","ensureActiveTabVisible","containerElement","activeTabElement","stopArrivedWatch","stopOverflowWatch","_prev","scrollState","useScroll","atLeft","atRight","isOverflow","useLayoutSync","unsubscribe","change","liteNode","useVueNodeLifecycleIndividual","layoutMutations","nodeManager","initializeNodeManager","activeGraph","manager","removeNodeTitleHeight","reroute","parent","linkIds","link","disposeNodeManagerAndSyncs","enabled","ensureCorrectLayoutScale","vueMode","oldVueMode","useVueNodeLifecycle","createSharedComposable","MESHY_CREDIT_PRICE_USD","meshyCreditsToUsd","credits","DEFAULT_NUMBER_OPTIONS","formatCreditsValue","usd","formatCreditsFromUsd","makePrefix","approximate","makeSuffix","suffix","appendNote","note","formatCreditsLabel","formatCreditsRangeLabel","minUsd","maxUsd","max","rangeValue","formatCreditsListLabel","usdValues","safePricingExecution","calculateRunwayDurationPrice","durationWidget","duration","cost","makeOmniProDurationCalculator","pricePerSecond","klingMotionControlPricingCalculator","modeWidget","pixversePricingCalculator","qualityWidget","motionModeWidget","quality","motionMode","byteDanceVideoPricingCalculator","modelWidget","resolutionWidget","generateAudioWidget","resolution","generateAudio","priceByModel","modelKey","resKey","baseRange","min10s","max10s","audioMultiplier","minCost","maxCost","ltxvPricingCalculator","modelTable","pps","klingVideoWithAudioPricingCalculator","SORA_SIZES","ALL_SIZES","validateSora2Selection","modelRaw","sizeRaw","perSecForSora2","formatRunPrice","perSec","sora2PricingCalculator","getWidgetValue","validationError","calculateTripo3DGenerationPrice","getWidget","getString","defaultValue","getBool","modelVersionRaw","hasStyle","hasTexture","hasPbr","quad","textureQualityRaw","geometryQualityRaw","isHdTexture","isDetailedGeometry","withTexture","baseCredits","dollars","calculateMeshyImageToModelPrice","shouldTextureWidget","calculateMeshyMultiImageToModelPrice","apiNodeCosts","widthW","heightW","h","imagesInput","hasRefs","MP","outMP","outputCost","minTotal","maxTotal","numImagesWidget","turboWidget","numImages","basePrice","renderingSpeedWidget","characterInput","hasCharacter","renderingSpeed","totalCost","modeValue","durationValue","modelValue","modality","nWidget","effectSceneWidget","effectScene","sizeWidget","price","aspectRatioWidget","lengthWidget","textureQualityWidget","getNumber","hasAdvancedParam","pricePerImage","resolutionStr","rate","inputMin","inputMax","outputPrice","is1080p","audioWidget","audioValue","hasAudio","useNodePricing","priceConfig","componentIconSvg","usePriceBadge","updateSubgraphCredits","isCreditsBadge","newBadges","collectCreditsBadges","getCreditsBadge","visited","badge","LGraphBadge","useComputedWithWidgetWatch","widgetNames","triggerCanvasRedraw","widgetValues","widgetsToObserve","currentValues","indexesToObserve","_type","computeFn","computedWithControl","useNodeBadge","priceBadge","nodeSourceBadgeMode","nodeIdBadgeMode","nodeLifeCycleBadgeMode","showApiPricingBadge","badgeTextVisible","badgeMode","NodeBadgeMode","nodePricing","BadgePosition","s","hasDynamicPricing","creditsBadge","createBadge","relevantWidgetNames","useCanvasDrop","args","loc","dndData","basePos","useSharedCanvasPositionConversion","ComfyNodeDefImpl","ComfyModelDef","nodeAtPos","targetProvider","targetGraphNode","providers","provider","useContextMenuTranslation","legacyMenuCompat","getCanvasMenuOptions","getCanvasCenterMenuOptions","res","newApiItems","legacyItems","nodeMenuFn","getNodeMenuOptionsWithExtensions","translateMenus","reInput","reWidget","cvt","tinp","twgt","te","extraInfo","matchInput","match","matchWidget","ContextMenu","clipboardHTMLWrapper","useCopy","shouldIgnoreCopyPaste","serializedData","base64Data","useGlobalLitegraph","LGraph","LLink","DragAndScale","useLitegraphSettings","canvasInfoEnabled","zoomSpeed","linkRenderMode","minFontSizeForLOD","linkMarkerShape","maximumFps","dragZoomEnabled","liveSelection","CanvasPointer","navigationMode","leftMouseBehavior","mouseWheelScroll","CORE_SETTINGS","oldValue","LinkMarkerShape","useWorkflowAutoSave","autoSaveTimeout","isSaving","needsAutoSave","scheduleAutoSave","delay","activeWorkflow","newSetting","useTemplateUrlLoader","route","useRoute","router","useRouter","templateWorkflows","TEMPLATE_NAMESPACE","PRESERVED_QUERY_NAMESPACES","SUPPORTED_MODES","isValidParameter","param","isSupportedMode","cleanupUrlParams","newQuery","templateParam","sourceParam","modeParam","clearPreservedQuery","useWorkflowPersistence","templateUrlLoader","ensureTemplateQueryFromIntent","hydratePreservedQuery","mergedQuery","mergePreservedQueryIntoQuery","workflowPersistenceEnabled","persistCurrentWorkflow","ourKeys","loadWorkflowFromStorage","loadPreviousWorkflowFromStorage","getStorageValue","clientId","sessionWorkflow","localWorkflow","loadDefaultWorkflow","initializeWorkflow","loadTemplateFromUrlIfPresent","activeWorkflowKey","setStorageValue","tryOnScopeDispose","openWorkflows","restoreState","paths","activeIndex","storedWorkflows","storedActiveIndex","useTransformSettling","settleDelay","passive","isTransforming","markTransformActive","markTransformSettled","useDebounceFn","useTransformStateIndividual","camera","transformStyle","syncWithCanvas","canvasToScreen","point","screenToCanvas","getNodeScreenBounds","topLeft","calculateAdjustedMargin","baseMargin","isNodeTooSmall","getExpandedViewportBounds","marginX","marginY","testViewportIntersection","screenPos","nodeRight","nodeBottom","isNodeInViewport","nodePos","adjustedMargin","getViewportBounds","bottomRight","useTransformState","isInteracting","handlePointerUp","handlePointerCancel","showOptionsPanel","toggleOptionsPanel","isMultiSelectKey","useNodeEventHandlersIndividual","bringNodeToFront","useNodeZIndex","shouldHandleNodePointerEvents","handleNodeSelect","multiSelect","selectedItemsCount","preserveExistingSelection","handleNodeCollapse","collapsed","handleNodeTitleUpdate","handleNodeRightClick","toggleNodeSelectionAfterPointerUp","useNodeEventHandlers","useNodeSnap","gridSize","alwaysSnap","shouldSnap","applySnapToPosition","posArray","snapPoint","applySnapToSize","gridSizeValue","sizeArray","useShiftKeySync","shiftKeyState","syncShiftState","isShiftPressed","trackShiftKey","initialEvent","handleKeyEvent","stopKeydown","stopKeyup","useNodeDrag","useNodeDragIndividual","mutations","selectedNodeIds","transformState","dragStartPos","dragStartMouse","otherSelectedNodesStartPositions","rafId","stopShiftSync","lastCanvasDelta","startDrag","isDraggedNodeInSelection","nodeLayout","handleDrag","pointerId","mouseDelta","canvasOrigin","canvasWithDelta","canvasDelta","otherNodeId","startPos","newOtherPosition","frameDelta","endDrag","boundsUpdates","currentLayout","currentPos","snappedPos","useNodePointerInteractions","nodeIdRef","forwardEventToCanvas","forwardMiddlePointerIfNeeded","isMiddlePointerInput","hasDraggingStarted","startPosition","DRAG_THRESHOLD","onPointerdown","safeDragStart","onPointermove","lmbDown","dx","dy","cleanupDragState","safeDragEnd","onPointerup","wasDragging","onPointercancel","onContextmenu","onScopeDispose","trackingConfigs","updates","nodeUpdates","resizeObserver","entries","conv","updatesByType","nodesNeedingSlotResync","elementType","elementId","borderBox","cx","cy","topLeftCanvas","syncNodeSlotLayoutsFromDOM","useVueElementTracking","appIdentifierMaybe","trackingType","appIdentifier","getCurrentInstance","useNodeExecutionState","nodeLocatorIdMaybe","locatorId","nodeLocationProgressStates","isIdle","progressState","executing","progress","progressPercentage","prog","executionState","useNodeLayout","nodeIdMaybe","layoutRef","zIndex","moveNodeTo","useNodePreviewState","nodePreviewImages","useNodeOutputStore","previewUrls","urls","hasPreview","latestPreviewUrl","shouldShowPreviewImg","useNodeResize","resizeCallback","isResizing","resizeStartPointer","resizeStartSize","nodeElement","startSize","moveEvent","deltaX","deltaY","newSize","upEvent","stopMoveListen","stopUpListen","actualDimensions","imageError","handleImageLoad","handleImageError","actionButtonClass","nodeOutputStore","currentIndex","isFocused","videoError","showLoader","videoWrapperEl","currentVideoUrl","hasMultipleVideos","newUrls","handleVideoLoad","video","handleVideoError","handleDownload","handleRemove","setCurrentIndex","handleFocusIn","handleFocusOut","getNavigationDotClass","handleKeyDown","getVideoFilename","currentImageEl","imageWrapperEl","startDelayedLoader","stopDelayedLoader","useTimeoutFn","currentImageUrl","hasMultipleImages","imageAltText","setupNodeForMaskEditor","handleEditMask","getImageFilename","hasMedia","onErrorCaptured","baseResizeHandleClasses","MIN_NODE_WIDTH","progressClasses","nodeLocatorId","getLocatorIdFromNodeData","hasExecutionError","hasAnyError","displayHeader","TitleMode","bypassed","muted","nodeBodyBackgroundColor","applyLightThemeColor","nodeOpacity","globalOpacity","hasInputs","nonWidgetedInputs","hasOutputs","pointerHandlers","remainingPointerHandlers","nodeOnPointerdown","lgraphNode","newNode","handleContextMenu","initSizeStyles","nodeContainerRef","startResize","clampedWidth","handleResizePointerDown","from","to","currentWidth","currentHeight","hasCustomContent","nodeMedia","borderClass","isTransparent","outlineClass","cursorClass","shapeClass","beforeShapeClass","handleCollapse","handleHeaderTitleUpdate","handleEnterSubgraph","litegraphNode","getNodeByLocatorId","nodeOutputs","nodeOutputLocatorId","newOutputs","hasVideoInput","isDraggingOver","handleDragOver","canDrop","handleDragLeave","handleDrop","pendingCallbacks","isNewUserDetermined","isNewUserCached","newUserService","checkIsNewUser","isNewUserSettings","hasNoWorkflow","hasNoPreviousWorkflow","registerInitCallback","initializeIfNewUser","isNewUser","selectionRect","pointer","dragging_rectangle","rectangleStyle","nodeSearchboxPopoverRef","betaMenuEnabled","workflowTabsPosition","canvasMenuEnabled","tooltipEnabled","selectionToolboxEnabled","showUI","minimapEnabled","vueNodeLifecycle","handleVueNodeLifecycleReset","allNodes","spellcheckEnabled","textarea","forEachNode","IS_CONTROL_WIDGET","updateControlWidgetLabel","l","currentPaletteId","lastNodeErrors","slot","nodeErrors","inputIndex","loadCustomNodesI18n","i18nData","mergeCustomNodesI18n","comfyAppReady","workflowPersistence","usePaste","ChangeTracker","UnauthorizedError","migrateToLitegraphReroute","workflowJSON","migratedWorkflowJSON","migrateLegacyRerouteNodes","DEFAULT_TITLE","TITLE_SUFFIX","useBrowserTabTitle","executionText","newMenuEnabled","isAutoSaveEnabled","isActiveWorkflowModified","isActiveWorkflowPersisted","shouldShowUnsavedIndicator","isUnsavedText","workflowNameText","nodeExecutionTitle","runningNodes","workflowTitle","useTitle","frameworkOptions","projectOptions","tempNavigation","searchText","selectedFrameworks","selectedProjects","selectedSort","useModelSelectorDialog","SampleModelSelector","createModelNodeFromAsset","validatedAsset","assetItemSchema","errorMessage","validAsset","userMetadata","filename","MODELS_TAG","MISSING_TAG","targetGraph","ZENDESK_FIELDS","SUPPORT_BASE_URL","buildSupportUrl","searchParams","moveSelectedNodesVersionAdded","useCoreCommands","firebaseAuthActions","maskEditorStore","useMaskEditorStore","getTracker","isQueuePanelV2Enabled","toggleQueuePanelV2","moveSelectedNodes","positionUpdater","newPath","getAllNonIoNodesInSubgraph","current","lastLinksRenderMode","currentMode","metadata","batchCount","useQueueSettingsStore","executionIds","getExecutionIdsForSelectedNodes","previousDarkTheme","DEFAULT_DARK_COLOR_PALETTE","previousLightTheme","DEFAULT_LIGHT_COLOR_PALETTE","theme","resolvedUserInfo","supportUrl","ManagerUIState","tryToggleWidgetPromotion","currentValue","useProgressFavicon","defaultFavicon","favicon","useFavicon","totalFrames","frame","LatentPreviewMethod","LogLevel","HashFunction","CudaMalloc","FloatingPointPrecision","CrossAttentionMethod","VramManagement","SERVER_CONFIG_ITEMS","badgeClasses","sev","progressPercent","isCompleted","isFailed","isRunning","isPending","assetDownloadStore","useAssetDownloadStore","activeFilter","filterOptions","setFilter","downloadJobs","completedJobs","failedJobs","isInProgress","currentJobName","job","filteredJobs","activeFilterLabel","DISMISSAL_DURATION_MS","useVersionCompatibilityStore","frontendVersion","backendVersion","requiredFrontendVersion","isFrontendOutdated","gt","isFrontendNewer","hasVersionMismatch","versionKey","dismissalStorage","useStorage","dismissedUntil","warningsDisabled","shouldShowWarning","warningMessage","checkVersionCompatibility","dismissWarning","dismissUntil","useFrontendVersionMismatchWarning","immediate","versionCompatibilityStore","hasShownWarning","showWarning","detailMessage","fullMessage","setupAutoQueueHandler","queueCountStore","useQueuePendingTaskCountStore","queueSettingsStore","graphHasChanged","internalCount","nodeDatas","nodeToNodeData","mapper","safeWidget","batchCountWidget","runButtonClick","openFeedback","comfyManagerStore","useComfyManagerStore","runFullConflictAnalysis","activeTabIndex","focusedLogs","isRestarting","isRestartCompleted","isTaskInProgress","log","taskQueue","completedTasksCount","totalTasksCount","completedTasks","queuedTasks","currentTaskName","collapsedPanels","togglePanel","sectionsContainerRef","scrollY","lastPanelRef","isUserScrolling","lastPanelLogs","isAtBottom","scrollLastPanelToBottom","scrollContentToBottom","resetUserScrolling","handleScroll","onLogsAdded","closeToast","handleRestart","originalToastSetting","graphCanvasContainerRef","linearMode","newTheme","DARK_THEME_CLASS","newTasks","oldTasks","oldRunningTaskIds","fontSize","loadLocale","useNewMenu","coreCommands","useKeybindingService","queuePendingTaskCountStore","onStatus","onExecutionSuccess","reconnectingMessage","onReconnecting","onReconnected","wrapWithErrorHandling","onGraphReady","useServerConfigStore","useModelStore"],"ignoreList":[],"sources":["../../src/platform/workflow/templates/types/template.ts","../../src/utils/categoryUtil.ts","../../src/platform/workflow/templates/repositories/workflowTemplatesStore.ts","../../src/types/widgetTypes.ts","../../src/components/widget/layout/BaseModalLayout.vue","../../src/components/widget/nav/NavTitle.vue","../../src/components/widget/panel/PanelHeader.vue","../../src/components/widget/panel/LeftSidePanel.vue","../../src/composables/usePopoverSizing.ts","../../src/components/input/MultiSelect.vue","../../src/platform/assets/composables/useAssetFilterOptions.ts","../../src/platform/assets/components/AssetFilterBar.vue","../../src/components/button/MoreButton.vue","../../src/components/dialog/confirm/ConfirmBody.vue","../../src/components/dialog/confirm/ConfirmFooter.vue","../../src/components/dialog/confirm/confirmDialog.ts","../../src/platform/assets/components/AssetCard.vue","../../src/platform/assets/components/AssetGrid.vue","../../src/platform/assets/utils/assetMetadataUtils.ts","../../src/platform/assets/composables/useAssetBrowser.ts","../../src/platform/assets/utils/categoryLabel.ts","../../src/platform/assets/components/AssetBrowserModal.vue","../../src/platform/assets/composables/useAssetBrowserDialog.ts","../../src/components/topbar/TopbarBadge.vue","../../src/platform/cloud/subscription/components/SubscribeButton.vue","../../src/platform/cloud/subscription/composables/useSubscriptionDialog.ts","../../src/components/toast/GlobalToast.vue","../../src/base/common/async.ts","../../src/components/MenuHamburger.vue","../../src/components/dialog/UnloadWindowConfirmDialog.vue","../../src/components/LiteGraphCanvasSplitterOverlay.vue","../../src/composables/useTooltipConfig.ts","../../src/components/actionbar/ComfyRunButton/index.ts","../../src/components/actionbar/ComfyActionbar.vue","../../src/composables/useWorkflowActionsMenu.ts","../../src/workbench/extensions/manager/utils/graphHasMissingNodes.ts","../../src/components/breadcrumb/SubgraphBreadcrumbItem.vue","../../src/composables/element/useOverflowObserver.ts","../../src/components/breadcrumb/SubgraphBreadcrumb.vue","../../src/components/queue/QueueOverlayActive.vue","../../src/components/queue/CompletionSummaryBanner.vue","../../src/components/queue/QueueOverlayEmpty.vue","../../src/components/queue/QueueOverlayHeader.vue","../../src/components/queue/job/JobContextMenu.vue","../../src/components/queue/job/JobFiltersBar.vue","../../src/components/queue/job/useJobErrorReporting.ts","../../src/components/queue/job/useQueueEstimates.ts","../../src/components/queue/job/JobDetailsPopover.vue","../../src/components/queue/job/QueueAssetPreview.vue","../../src/components/queue/job/QueueJobItem.vue","../../src/components/queue/job/JobGroupsList.vue","../../src/components/queue/QueueOverlayExpanded.vue","../../src/components/queue/dialogs/QueueClearHistoryDialog.vue","../../src/composables/queue/useCompletionSummary.ts","../../src/composables/queue/useResultGallery.ts","../../src/components/queue/QueueProgressOverlay.vue","../../src/stores/actionBarButtonStore.ts","../../src/components/topbar/ActionBarButtons.vue","../../src/components/topbar/CurrentUserPopover.vue","../../src/components/topbar/CurrentUserButton.vue","../../src/components/topbar/LoginButton.vue","../../src/platform/updates/common/releaseService.ts","../../src/platform/updates/common/releaseStore.ts","../../src/components/TopMenuSection.vue","../../src/components/common/ExtensionSlot.vue","../../src/components/bottomPanel/BottomPanel.vue","../../src/composables/element/useAbsolutePosition.ts","../../src/composables/element/useDomClipping.ts","../../src/components/graph/widgets/DomWidget.vue","../../src/components/graph/DomWidgets.vue","../../src/composables/useZoomControls.ts","../../src/renderer/extensions/minimap/composables/useMinimapGraph.ts","../../src/renderer/extensions/minimap/composables/useMinimapInteraction.ts","../../src/renderer/extensions/minimap/composables/useMinimapRenderer.ts","../../src/renderer/extensions/minimap/composables/useMinimapSettings.ts","../../src/renderer/extensions/minimap/composables/useMinimapViewport.ts","../../src/renderer/extensions/minimap/composables/useMinimap.ts","../../src/components/graph/CanvasModeSelector.vue","../../src/components/graph/modals/ZoomControlsModal.vue","../../src/components/graph/GraphCanvasMenu.vue","../../src/components/graph/NodeTooltip.vue","../../src/components/graph/selectionToolbox/BypassButton.vue","../../src/components/graph/selectionToolbox/ColorPickerButton.vue","../../src/components/graph/selectionToolbox/ConfigureSubgraph.vue","../../src/components/graph/selectionToolbox/ConvertToSubgraphButton.vue","../../src/components/graph/selectionToolbox/DeleteButton.vue","../../src/components/graph/selectionToolbox/ExecuteButton.vue","../../src/components/graph/selectionToolbox/ExtensionCommandButton.vue","../../src/components/graph/selectionToolbox/InfoButton.vue","../../src/components/graph/selectionToolbox/Load3DViewerButton.vue","../../src/components/graph/selectionToolbox/MaskEditorButton.vue","../../src/composables/useRefreshableSelection.ts","../../src/components/graph/selectionToolbox/RefreshSelectionButton.vue","../../src/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue","../../src/composables/canvas/useSelectionToolboxPosition.ts","../../src/composables/graph/contextMenuConverter.ts","../../src/composables/graph/useCanvasRefresh.ts","../../src/composables/graph/useNodeCustomization.ts","../../src/composables/graph/useGroupMenuOptions.ts","../../src/composables/graph/useImageMenuOptions.ts","../../src/composables/graph/useSelectedNodeActions.ts","../../src/composables/graph/useNodeMenuOptions.ts","../../src/composables/graph/useFrameNodes.ts","../../src/composables/graph/useNodeArrangement.ts","../../src/composables/graph/useSelectionOperations.ts","../../src/composables/graph/useSelectionMenuOptions.ts","../../src/composables/graph/useMoreOptionsMenu.ts","../../src/components/graph/selectionToolbox/ColorPickerMenu.vue","../../src/components/graph/NodeContextMenu.vue","../../src/components/graph/selectionToolbox/FrameNodes.vue","../../src/components/graph/selectionToolbox/NodeOptionsButton.vue","../../src/components/graph/selectionToolbox/VerticalDivider.vue","../../src/components/graph/SelectionToolbox.vue","../../src/components/graph/TitleEditor.vue","../../src/components/rightSidePanel/info/TabInfo.vue","../../src/components/rightSidePanel/layout/SidePanelSearch.vue","../../src/types/simplifiedWidget.ts","../../src/composables/graph/useGraphNodeManager.ts","../../src/components/rightSidePanel/layout/PropertiesAccordionItem.vue","../../src/components/rightSidePanel/parameters/SectionWidgets.vue","../../src/components/rightSidePanel/parameters/TabParameters.vue","../../src/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.vue","../../src/components/rightSidePanel/settings/TabSettings.vue","../../src/components/rightSidePanel/subgraph/SubgraphNodeWidget.vue","../../src/components/rightSidePanel/subgraph/SubgraphEditor.vue","../../src/components/rightSidePanel/RightSidePanel.vue","../../src/stores/workspace/searchBoxStore.ts","../../src/components/primevueOverride/AutoCompletePlus.vue","../../src/components/searchbox/NodeSearchItem.vue","../../src/components/searchbox/NodeSearchBox.vue","../../src/components/searchbox/NodeSearchBoxPopover.vue","../../src/stores/helpCenterStore.ts","../../src/composables/useHelpCenter.ts","../../src/platform/updates/components/ReleaseNotificationToast.vue","../../src/platform/updates/components/WhatsNewPopup.vue","../../src/components/icons/PuzzleIcon.vue","../../src/components/helpcenter/HelpCenterMenuContent.vue","../../src/components/helpcenter/HelpCenterPopups.vue","../../src/components/icons/ComfyLogo.vue","../../src/components/card/CardBottom.vue","../../src/components/card/CardContainer.vue","../../src/components/card/CardTop.vue","../../src/components/chip/SquareChip.vue","../../src/components/templates/thumbnails/BaseThumbnail.vue","../../src/components/templates/thumbnails/CompareSliderThumbnail.vue","../../src/components/templates/thumbnails/DefaultThumbnail.vue","../../src/components/templates/thumbnails/HoverDissolveThumbnail.vue","../../src/composables/useLazyPagination.ts","../../src/stores/templateRankingStore.ts","../../src/composables/useTemplateFiltering.ts","../../src/platform/workflow/templates/composables/useTemplateWorkflows.ts","../../src/utils/gridUtil.ts","../../src/components/custom/widget/WorkflowTemplateSelectorDialog.vue","../../src/composables/useWorkflowTemplateSelectorDialog.ts","../../src/utils/mouseDownUtil.ts","../../src/components/sidebar/ComfyMenuButton.vue","../../src/components/sidebar/SidebarIcon.vue","../../src/components/sidebar/SidebarBottomPanelToggleButton.vue","../../src/components/sidebar/SidebarSettingsButton.vue","../../src/components/sidebar/SidebarShortcutsToggleButton.vue","../../src/components/sidebar/SidebarHelpCenterIcon.vue","../../src/components/sidebar/SidebarLogoutIcon.vue","../../src/components/sidebar/SidebarTemplatesButton.vue","../../src/components/sidebar/SideToolbar.vue","../../src/stores/topbarBadgeStore.ts","../../src/components/topbar/TopbarBadges.vue","../../src/components/common/OverlayIcon.vue","../../src/components/topbar/TopMenuHelpButton.vue","../../src/components/topbar/WorkflowTabPopover.vue","../../src/components/topbar/WorkflowTab.vue","../../src/components/topbar/WorkflowOverflowMenu.vue","../../src/components/topbar/WorkflowTabs.vue","../../src/renderer/core/layout/sync/useLayoutSync.ts","../../src/composables/graph/useVueNodeLifecycle.ts","../../src/composables/node/useNodePricing.ts","../../src/composables/node/usePriceBadge.ts","../../src/composables/node/useWatchWidget.ts","../../src/composables/node/useNodeBadge.ts","../../src/composables/useCanvasDrop.ts","../../src/composables/useContextMenuTranslation.ts","../../src/composables/useCopy.ts","../../src/composables/useGlobalLitegraph.ts","../../src/platform/settings/composables/useLitegraphSettings.ts","../../src/platform/settings/constants/coreSettings.ts","../../src/platform/workflow/persistence/composables/useWorkflowAutoSave.ts","../../src/platform/workflow/templates/composables/useTemplateUrlLoader.ts","../../src/platform/workflow/persistence/composables/useWorkflowPersistence.ts","../../src/renderer/core/layout/transform/useTransformSettling.ts","../../src/renderer/core/layout/transform/useTransformState.ts","../../src/renderer/core/layout/transform/TransformPane.vue","../../src/renderer/extensions/minimap/MiniMap.vue","../../src/renderer/extensions/vueNodes/utils/selectionUtils.ts","../../src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts","../../src/renderer/extensions/vueNodes/composables/useNodeSnap.ts","../../src/renderer/extensions/vueNodes/composables/useShiftKeySync.ts","../../src/renderer/extensions/vueNodes/layout/useNodeDrag.ts","../../src/renderer/extensions/vueNodes/composables/useNodePointerInteractions.ts","../../src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts","../../src/renderer/extensions/vueNodes/execution/useNodeExecutionState.ts","../../src/renderer/extensions/vueNodes/layout/useNodeLayout.ts","../../src/renderer/extensions/vueNodes/preview/useNodePreviewState.ts","../../src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts","../../src/renderer/extensions/vueNodes/components/LivePreview.vue","../../src/renderer/extensions/vueNodes/VideoPreview.vue","../../src/renderer/extensions/vueNodes/components/ImagePreview.vue","../../src/renderer/extensions/vueNodes/components/NodeContent.vue","../../src/renderer/extensions/vueNodes/components/LGraphNode.vue","../../src/services/newUserService.ts","../../src/components/graph/SelectionRectangle.vue","../../src/components/graph/GraphCanvas.vue","../../src/components/toast/RerouteMigrationToast.vue","../../src/composables/useBrowserTabTitle.ts","../../src/components/widget/panel/RightSidePanel.vue","../../src/components/widget/SampleModelSelector.vue","../../src/composables/useModelSelectorDialog.ts","../../src/platform/assets/utils/createModelNodeFromAsset.ts","../../src/platform/support/config.ts","../../src/composables/useCoreCommands.ts","../../src/composables/useProgressFavicon.ts","../../src/types/serverArgs.ts","../../src/constants/serverConfig.ts","../../src/components/honeyToast/HoneyToast.vue","../../src/components/common/StatusBadge.vue","../../src/components/toast/ProgressToastItem.vue","../../src/platform/assets/components/ModelImportProgressDialog.vue","../../src/platform/updates/common/versionCompatibilityStore.ts","../../src/platform/updates/common/useFrontendVersionMismatchWarning.ts","../../src/services/autoQueueService.ts","../../src/views/LinearView.vue","../../src/workbench/extensions/manager/components/ManagerProgressToast.vue","../../src/views/GraphView.vue"],"sourcesContent":["export interface TemplateInfo {\n name: string\n /**\n * Optional title which is used as the fallback if the name is not in the locales dictionary.\n */\n title?: string\n tutorialUrl?: string\n mediaType: string\n mediaSubtype: string\n thumbnailVariant?: string\n description: string\n localizedTitle?: string\n localizedDescription?: string\n isEssential?: boolean\n sourceModule?: string\n tags?: string[]\n models?: string[]\n date?: string\n useCase?: string\n license?: string\n /**\n * Estimated VRAM requirement in bytes.\n */\n vram?: number\n size?: number\n /**\n * Whether this template uses open source models. When false, indicates partner/API node templates.\n */\n openSource?: boolean\n /**\n * Array of custom node package IDs required for this template (from Custom Node Registry).\n * Templates with this field will be hidden on local installations temporarily.\n */\n requiresCustomNodes?: string[]\n /**\n * Manual ranking boost/demotion for \"Recommended\" sort. Scale 1-10, default 5.\n * Higher values promote the template, lower values demote it.\n */\n searchRank?: number\n /**\n * Usage score based on real world usage statistics.\n * Used for popular templates sort and for \"Recommended\" sort boost.\n */\n usage?: number\n /**\n * Manage template's visibility across different distributions by specifying which distributions it should be included on.\n * If not specified, the template will be included on all distributions.\n */\n includeOnDistributions?: TemplateIncludeOnDistributionEnum[]\n}\n\nexport enum TemplateIncludeOnDistributionEnum {\n Cloud = 'cloud',\n Local = 'local',\n Desktop = 'desktop',\n Mac = 'mac',\n Windows = 'windows'\n}\n\nexport interface WorkflowTemplates {\n moduleName: string\n templates: TemplateInfo[]\n title: string\n localizedTitle?: string\n category?: string\n type?: string\n icon?: string\n isEssential?: boolean\n}\n\nexport interface TemplateGroup {\n label: string\n icon?: string\n modules: WorkflowTemplates[]\n}\n","/**\n * Maps category IDs to their corresponding Lucide icon classes\n */\nexport const getCategoryIcon = (categoryId: string): string => {\n const iconMap: Record<string, string> = {\n // Main categories\n all: 'icon-[lucide--list]',\n 'getting-started': 'icon-[lucide--graduation-cap]',\n\n // Generation types\n 'generation-image': 'icon-[lucide--image]',\n image: 'icon-[lucide--image]',\n 'generation-video': 'icon-[lucide--film]',\n video: 'icon-[lucide--film]',\n 'generation-3d': 'icon-[lucide--box]',\n '3d': 'icon-[lucide--box]',\n 'generation-audio': 'icon-[lucide--volume-2]',\n audio: 'icon-[lucide--volume-2]',\n 'generation-llm': 'icon-[lucide--message-square-text]',\n\n // API and models\n 'api-nodes': 'icon-[lucide--hand-coins]',\n 'closed-models': 'icon-[lucide--hand-coins]',\n\n // LLMs and AI\n llm: 'icon-[lucide--message-square-text]',\n llms: 'icon-[lucide--message-square-text]',\n 'llm-api': 'icon-[lucide--message-square-text]',\n\n // Performance and hardware\n 'small-models': 'icon-[lucide--zap]',\n performance: 'icon-[lucide--zap]',\n 'mac-compatible': 'icon-[lucide--command]',\n 'runs-on-mac': 'icon-[lucide--command]',\n\n // Training\n 'lora-training': 'icon-[lucide--dumbbell]',\n training: 'icon-[lucide--dumbbell]',\n\n // Extensions and tools\n extensions: 'icon-[lucide--puzzle]',\n tools: 'icon-[lucide--wrench]',\n\n // Fallbacks for common patterns\n upscaling: 'icon-[lucide--maximize-2]',\n controlnet: 'icon-[lucide--sliders-horizontal]',\n 'area-composition': 'icon-[lucide--layout-grid]'\n }\n\n // Return mapped icon or fallback to folder\n return iconMap[categoryId.toLowerCase()] || 'icon-[lucide--folder]'\n}\n\n/**\n * Generates a unique category ID from a category group and title\n */\nexport function generateCategoryId(\n categoryGroup: string,\n categoryTitle: string\n) {\n return `${categoryGroup.toLowerCase().replace(/\\s+/g, '-')}-${categoryTitle.toLowerCase().replace(/\\s+/g, '-')}`\n}\n","import { defineStore } from 'pinia'\nimport { computed, ref, shallowRef } from 'vue'\n\nimport { i18n, st } from '@/i18n'\nimport { isCloud } from '@/platform/distribution/types'\nimport { api } from '@/scripts/api'\nimport type { NavGroupData, NavItemData } from '@/types/navTypes'\nimport { generateCategoryId, getCategoryIcon } from '@/utils/categoryUtil'\nimport { normalizeI18nKey } from '@/utils/formatUtil'\n\nimport type {\n TemplateGroup,\n TemplateInfo,\n WorkflowTemplates\n} from '../types/template'\n\n// Enhanced template interface for easier filtering\ninterface EnhancedTemplate extends TemplateInfo {\n sourceModule: string\n category?: string\n categoryType?: string\n categoryGroup?: string // 'GENERATION TYPE' or 'CLOSED SOURCE MODELS'\n isEssential?: boolean\n isPartnerNode?: boolean // Computed from OpenSource === false\n searchableText?: string\n}\n\nexport const useWorkflowTemplatesStore = defineStore(\n 'workflowTemplates',\n () => {\n const customTemplates = shallowRef<{ [moduleName: string]: string[] }>({})\n const coreTemplates = shallowRef<WorkflowTemplates[]>([])\n const englishTemplates = shallowRef<WorkflowTemplates[]>([])\n const isLoaded = ref(false)\n const knownTemplateNames = ref(new Set<string>())\n\n const getTemplateByName = (name: string): EnhancedTemplate | undefined => {\n return enhancedTemplates.value.find((template) => template.name === name)\n }\n\n // Store filter mappings for dynamic categories\n type FilterData = {\n category?: string\n categoryGroup?: string\n }\n\n const categoryFilters = ref(new Map<string, FilterData>())\n\n /**\n * Add localization fields to a template.\n */\n const addLocalizedFieldsToTemplate = (\n template: TemplateInfo,\n categoryTitle: string\n ) => ({\n ...template,\n localizedTitle: st(\n `templateWorkflows.template.${normalizeI18nKey(categoryTitle)}.${normalizeI18nKey(template.name)}`,\n template.title ?? template.name\n ),\n localizedDescription: st(\n `templateWorkflows.templateDescription.${normalizeI18nKey(categoryTitle)}.${normalizeI18nKey(template.name)}`,\n template.description\n )\n })\n\n /**\n * Add localization fields to all templates in a list of templates.\n */\n const localizeTemplateList = (\n templates: TemplateInfo[],\n categoryTitle: string\n ) =>\n templates.map((template) =>\n addLocalizedFieldsToTemplate(template, categoryTitle)\n )\n\n /**\n * Add localization fields to a template category and all its constituent templates.\n */\n const localizeTemplateCategory = (templateCategory: WorkflowTemplates) => ({\n ...templateCategory,\n localizedTitle: st(\n `templateWorkflows.category.${normalizeI18nKey(templateCategory.title)}`,\n templateCategory.title ?? templateCategory.moduleName\n ),\n templates: localizeTemplateList(\n templateCategory.templates,\n templateCategory.title\n )\n })\n\n // Create an \"All\" category that combines all templates\n const createAllCategory = () => {\n // First, get core templates with source module added\n const coreTemplatesWithSourceModule = coreTemplates.value.flatMap(\n (category) =>\n // For each template in each category, add the sourceModule and pass through any localized fields\n category.templates.map((template) => {\n // Get localized template with its original category title for i18n lookup\n const localizedTemplate = addLocalizedFieldsToTemplate(\n template,\n category.title\n )\n return {\n ...localizedTemplate,\n sourceModule: category.moduleName\n }\n })\n )\n\n // Now handle custom templates\n const customTemplatesWithSourceModule = Object.entries(\n customTemplates.value\n ).flatMap(([moduleName, templates]) =>\n templates.map((name) => ({\n name,\n mediaType: 'image',\n mediaSubtype: 'jpg',\n description: name,\n sourceModule: moduleName\n }))\n )\n\n return {\n moduleName: 'all',\n title: 'All',\n localizedTitle: st('templateWorkflows.category.All', 'All Templates'),\n templates: [\n ...coreTemplatesWithSourceModule,\n ...customTemplatesWithSourceModule\n ]\n }\n }\n\n /**\n * Original grouped templates for backward compatibility\n */\n const groupedTemplates = computed<TemplateGroup[]>(() => {\n // Get regular categories\n const allTemplates = [\n ...coreTemplates.value.map(localizeTemplateCategory),\n ...Object.entries(customTemplates.value).map(\n ([moduleName, templates]) => ({\n moduleName,\n title: moduleName,\n localizedTitle: st(\n `templateWorkflows.category.${normalizeI18nKey(moduleName)}`,\n moduleName\n ),\n templates: templates.map((name) => ({\n name,\n mediaType: 'image',\n mediaSubtype: 'jpg',\n description: name\n }))\n })\n )\n ]\n\n // Group templates by their main category\n const groupedByCategory = [\n {\n label: st(\n 'templateWorkflows.category.ComfyUI Examples',\n 'ComfyUI Examples'\n ),\n modules: [\n createAllCategory(),\n ...allTemplates.filter((t) => t.moduleName === 'default')\n ]\n },\n ...(Object.keys(customTemplates.value).length > 0\n ? [\n {\n label: st(\n 'templateWorkflows.category.Custom Nodes',\n 'Custom Nodes'\n ),\n modules: allTemplates.filter((t) => t.moduleName !== 'default')\n }\n ]\n : [])\n ]\n\n return groupedByCategory\n })\n\n /**\n * Enhanced templates with proper categorization for filtering\n */\n const enhancedTemplates = computed<EnhancedTemplate[]>(() => {\n const allTemplates: EnhancedTemplate[] = []\n\n // Process core templates\n coreTemplates.value.forEach((category) => {\n category.templates.forEach((template) => {\n const enhancedTemplate: EnhancedTemplate = {\n ...template,\n sourceModule: category.moduleName,\n category: category.title,\n categoryType: category.type,\n categoryGroup: category.category,\n isEssential: category.isEssential,\n isPartnerNode: template.openSource === false,\n searchableText: [\n template.title || template.name,\n template.description || '',\n category.title,\n ...(template.tags || []),\n ...(template.models || [])\n ].join(' ')\n }\n\n allTemplates.push(enhancedTemplate)\n })\n })\n\n // Process custom templates\n Object.entries(customTemplates.value).forEach(\n ([moduleName, templates]) => {\n templates.forEach((name) => {\n const enhancedTemplate: EnhancedTemplate = {\n name,\n title: name,\n description: name,\n mediaType: 'image',\n mediaSubtype: 'jpg',\n sourceModule: moduleName,\n category: 'Extensions',\n categoryType: 'extension',\n searchableText: `${name} ${moduleName} extension`\n }\n allTemplates.push(enhancedTemplate)\n })\n }\n )\n\n // TODO: Temporary filtering of custom node templates on local installations\n // Future: Add UX that allows local users to opt-in to templates with custom nodes,\n // potentially conditional on whether they have those specific custom nodes installed.\n // This would provide better template discovery while respecting local user workflows.\n const filteredTemplates = isCloud\n ? allTemplates\n : allTemplates.filter(\n (template) => !template.requiresCustomNodes?.length\n )\n\n return filteredTemplates\n })\n\n /**\n * Filter templates by category ID using stored filter mappings\n */\n const filterTemplatesByCategory = (categoryId: string) => {\n if (categoryId === 'all') {\n return enhancedTemplates.value\n }\n\n if (categoryId.startsWith('basics-')) {\n // Filter for templates from categories marked as essential\n return enhancedTemplates.value.filter(\n (t) =>\n t.isEssential &&\n t.category?.toLowerCase().replace(/\\s+/g, '-') ===\n categoryId.replace('basics-', '')\n )\n }\n\n if (categoryId === 'popular') {\n return enhancedTemplates.value\n }\n\n if (categoryId === 'partner-nodes') {\n // Filter for templates where OpenSource === false\n return enhancedTemplates.value.filter((t) => t.isPartnerNode)\n }\n\n // Handle extension-specific filters\n if (categoryId.startsWith('extension-')) {\n const moduleName = categoryId.replace('extension-', '')\n return enhancedTemplates.value.filter(\n (t) => t.sourceModule === moduleName\n )\n }\n\n // Look up the filter from our stored mappings\n const filter = categoryFilters.value.get(categoryId)\n if (!filter) {\n return enhancedTemplates.value\n }\n\n // Apply the filter\n return enhancedTemplates.value.filter((template) => {\n if (filter.category && template.category !== filter.category) {\n return false\n }\n if (\n filter.categoryGroup &&\n template.categoryGroup !== filter.categoryGroup\n ) {\n return false\n }\n return true\n })\n }\n\n /**\n * New navigation structure dynamically built from JSON categories\n */\n const navGroupedTemplates = computed<(NavItemData | NavGroupData)[]>(() => {\n if (!isLoaded.value) return []\n\n const items: (NavItemData | NavGroupData)[] = []\n\n // Clear and rebuild filter mappings\n categoryFilters.value.clear()\n\n // 1. All Templates - always first\n items.push({\n id: 'all',\n label: st('templateWorkflows.category.All', 'All Templates'),\n icon: getCategoryIcon('all')\n })\n\n // 1.5. Popular categories\n\n items.push({\n id: 'popular',\n label: st('templateWorkflows.category.Popular', 'Popular'),\n icon: 'icon-[lucide--flame]'\n })\n\n // 2. Basics (isEssential categories) - always beneath All Templates if they exist\n const essentialCats = coreTemplates.value.filter(\n (cat) => cat.isEssential && cat.templates.length > 0\n )\n\n if (essentialCats.length > 0) {\n essentialCats.forEach((essentialCat) => {\n const categoryIcon = essentialCat.icon\n const categoryTitle = essentialCat.title ?? 'Getting Started'\n const categoryId = generateCategoryId('basics', essentialCat.title)\n items.push({\n id: categoryId,\n label: st(\n `templateWorkflows.category.${normalizeI18nKey(categoryTitle)}`,\n categoryTitle\n ),\n icon:\n categoryIcon ||\n getCategoryIcon(essentialCat.type || 'getting-started')\n })\n })\n }\n\n // 3. Group categories from JSON dynamically\n const categoryGroups = new Map<\n string,\n { title: string; items: NavItemData[] }\n >()\n\n // Process all categories from JSON\n coreTemplates.value.forEach((category) => {\n // Skip essential categories as they're handled as Basics\n if (category.isEssential) return\n\n const categoryGroup = category.category\n const categoryIcon = category.icon\n\n if (categoryGroup) {\n if (!categoryGroups.has(categoryGroup)) {\n categoryGroups.set(categoryGroup, {\n title: categoryGroup,\n items: []\n })\n }\n\n const group = categoryGroups.get(categoryGroup)!\n\n // Generate unique ID for this category\n const categoryId = generateCategoryId(categoryGroup, category.title)\n\n // Store the filter mapping\n categoryFilters.value.set(categoryId, {\n category: category.title,\n categoryGroup: categoryGroup\n })\n\n group.items.push({\n id: categoryId,\n label: st(\n `templateWorkflows.category.${normalizeI18nKey(category.title)}`,\n category.title\n ),\n icon: categoryIcon || getCategoryIcon(category.type || 'default')\n })\n }\n })\n\n // Add grouped categories\n categoryGroups.forEach((group, groupName) => {\n if (group.items.length > 0) {\n items.push({\n title: st(\n `templateWorkflows.category.${normalizeI18nKey(groupName)}`,\n groupName\n .split(' ')\n .map(\n (word) =>\n word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()\n )\n .join(' ')\n ),\n items: group.items\n })\n }\n })\n\n // 3.5. Partner Nodes - virtual category for OpenSource === false templates\n const partnerNodeCount = enhancedTemplates.value.filter(\n (t) => t.isPartnerNode\n ).length\n\n if (partnerNodeCount > 0) {\n items.push({\n id: 'partner-nodes',\n label: st(\n 'templateWorkflows.category.Partner Nodes',\n 'Partner Nodes'\n ),\n icon: 'icon-[lucide--handshake]'\n })\n }\n\n // 4. Extensions - always last\n const extensionCounts = enhancedTemplates.value.filter(\n (t) => t.sourceModule !== 'default'\n ).length\n\n if (extensionCounts > 0) {\n // Get unique extension modules\n const extensionModules = Array.from(\n new Set(\n enhancedTemplates.value\n .filter((t) => t.sourceModule !== 'default')\n .map((t) => t.sourceModule)\n )\n ).sort()\n\n const extensionItems: NavItemData[] = extensionModules.map(\n (moduleName) => ({\n id: `extension-${moduleName}`,\n label: st(\n `templateWorkflows.category.${normalizeI18nKey(moduleName)}`,\n moduleName\n ),\n icon: getCategoryIcon('extensions')\n })\n )\n\n items.push({\n title: st('templateWorkflows.category.Extensions', 'Extensions'),\n items: extensionItems,\n collapsible: true\n })\n }\n\n return items\n })\n\n async function loadWorkflowTemplates() {\n try {\n if (!isLoaded.value) {\n customTemplates.value = await api.getWorkflowTemplates()\n const locale = i18n.global.locale.value\n\n const [coreResult, englishResult] = await Promise.all([\n api.getCoreWorkflowTemplates(locale),\n isCloud && locale !== 'en'\n ? api.getCoreWorkflowTemplates('en')\n : Promise.resolve([])\n ])\n\n coreTemplates.value = coreResult\n englishTemplates.value = englishResult\n\n const coreNames = coreTemplates.value.flatMap((category) =>\n category.templates.map((template) => template.name)\n )\n const customNames = Object.values(customTemplates.value).flat()\n knownTemplateNames.value = new Set([...coreNames, ...customNames])\n\n isLoaded.value = true\n }\n } catch (error) {\n console.error('Error fetching workflow templates:', error)\n }\n }\n\n function getEnglishMetadata(templateName: string): {\n tags?: string[]\n category?: string\n useCase?: string\n models?: string[]\n license?: string\n } | null {\n if (englishTemplates.value.length === 0) {\n return null\n }\n\n for (const category of englishTemplates.value) {\n const template = category.templates.find((t) => t.name === templateName)\n if (template) {\n return {\n tags: template.tags,\n category: category.title,\n useCase: template.useCase,\n models: template.models,\n license: template.license\n }\n }\n }\n\n return null\n }\n\n return {\n groupedTemplates,\n navGroupedTemplates,\n enhancedTemplates,\n filterTemplatesByCategory,\n isLoaded,\n loadWorkflowTemplates,\n knownTemplateNames,\n getTemplateByName,\n getEnglishMetadata\n }\n }\n)\n","import type { InjectionKey } from 'vue'\n\nexport type AssetKind = 'image' | 'video' | 'audio' | 'model' | 'unknown'\n\nexport const OnCloseKey: InjectionKey<() => void> = Symbol()\n","<template>\n <div class=\"base-widget-layout rounded-2xl overflow-hidden relative\">\n <Button\n v-show=\"!isRightPanelOpen && hasRightPanel\"\n size=\"icon\"\n :class=\"\n cn('absolute top-4 right-18 z-10', 'transition-opacity duration-200', {\n 'opacity-0 pointer-events-none': isRightPanelOpen || !hasRightPanel\n })\n \"\n @click=\"toggleRightPanel\"\n >\n <i class=\"icon-[lucide--panel-right] text-sm\" />\n </Button>\n <Button\n size=\"lg\"\n class=\"absolute top-4 right-6 z-10 transition-opacity duration-200 w-10\"\n @click=\"closeDialog\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n <div class=\"flex h-full w-full\">\n <Transition name=\"slide-panel\">\n <nav\n v-if=\"$slots.leftPanel && showLeftPanel\"\n :class=\"[\n PANEL_SIZES.width,\n PANEL_SIZES.minWidth,\n PANEL_SIZES.maxWidth\n ]\"\n >\n <slot name=\"leftPanel\"></slot>\n </nav>\n </Transition>\n\n <div class=\"flex-1 flex bg-base-background\">\n <div class=\"flex h-full w-full flex-col\">\n <header\n v-if=\"$slots.header\"\n class=\"w-full h-18 px-6 flex items-center justify-between gap-2\"\n >\n <div class=\"flex flex-1 shrink-0 gap-2\">\n <Button v-if=\"!notMobile\" size=\"icon\" @click=\"toggleLeftPanel\">\n <i\n :class=\"\n cn(\n showLeftPanel\n ? 'icon-[lucide--panel-left]'\n : 'icon-[lucide--panel-left-close]'\n )\n \"\n />\n </Button>\n <slot name=\"header\"></slot>\n </div>\n <slot name=\"header-right-area\"></slot>\n <div\n :class=\"\n cn(\n 'flex justify-end gap-2 w-0',\n hasRightPanel && !isRightPanelOpen ? 'min-w-22' : 'min-w-10'\n )\n \"\n >\n <Button\n v-if=\"isRightPanelOpen && hasRightPanel\"\n size=\"icon\"\n @click=\"toggleRightPanel\"\n >\n <i class=\"icon-[lucide--panel-right-close]\" />\n </Button>\n </div>\n </header>\n\n <main class=\"flex min-h-0 flex-1 flex-col\">\n <!-- Fallback title bar when no leftPanel is provided -->\n <slot name=\"contentFilter\"></slot>\n <h2\n v-if=\"!$slots.leftPanel\"\n class=\"text-xxl m-0 px-6 pt-2 pb-6 capitalize\"\n >\n {{ contentTitle }}\n </h2>\n <div\n class=\"min-h-0 flex-1 px-6 pt-0 pb-10 overflow-y-auto scrollbar-custom\"\n >\n <slot name=\"content\"></slot>\n </div>\n </main>\n </div>\n <aside\n v-if=\"hasRightPanel && isRightPanelOpen\"\n class=\"w-1/4 min-w-40 max-w-80\"\n >\n <slot name=\"rightPanel\"></slot>\n </aside>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useBreakpoints } from '@vueuse/core'\nimport { computed, inject, ref, useSlots, watch } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { OnCloseKey } from '@/types/widgetTypes'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { contentTitle } = defineProps<{\n contentTitle: string\n}>()\n\nconst BREAKPOINTS = { md: 880 }\nconst PANEL_SIZES = {\n width: 'w-1/3',\n minWidth: 'min-w-40',\n maxWidth: 'max-w-56'\n}\n\nconst slots = useSlots()\nconst closeDialog = inject(OnCloseKey, () => {})\n\nconst breakpoints = useBreakpoints(BREAKPOINTS)\nconst notMobile = breakpoints.greater('md')\n\nconst isLeftPanelOpen = ref<boolean>(true)\nconst isRightPanelOpen = ref<boolean>(false)\nconst mobileMenuOpen = ref<boolean>(false)\n\nconst hasRightPanel = computed(() => !!slots.rightPanel)\n\nwatch(notMobile, (isDesktop) => {\n if (!isDesktop) {\n mobileMenuOpen.value = false\n }\n})\n\nconst showLeftPanel = computed(() => {\n const shouldShow = notMobile.value\n ? isLeftPanelOpen.value\n : mobileMenuOpen.value\n return shouldShow\n})\n\nconst toggleLeftPanel = () => {\n if (notMobile.value) {\n isLeftPanelOpen.value = !isLeftPanelOpen.value\n } else {\n mobileMenuOpen.value = !mobileMenuOpen.value\n }\n}\n\nconst toggleRightPanel = () => {\n isRightPanelOpen.value = !isRightPanelOpen.value\n}\n</script>\n<style scoped>\n.base-widget-layout {\n height: 80vh;\n width: 90vw;\n max-width: 1280px;\n aspect-ratio: 20/13;\n}\n\n@media (min-width: 1450px) {\n .base-widget-layout {\n max-width: 1724px;\n }\n}\n\n/* Fade transition for buttons */\n.fade-enter-active,\n.fade-leave-active {\n transition: opacity 0.2s ease;\n}\n\n.fade-enter-from,\n.fade-leave-to {\n opacity: 0;\n}\n\n/* Slide transition for left panel */\n.slide-panel-enter-active,\n.slide-panel-leave-active {\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n will-change: transform;\n backface-visibility: hidden;\n}\n\n.slide-panel-enter-from,\n.slide-panel-leave-to {\n transform: translateX(-100%);\n}\n</style>\n","<template>\n <div\n :class=\"\n cn(\n 'flex items-center justify-between m-0 px-3 py-0 pt-5',\n collapsible && 'cursor-pointer select-none'\n )\n \"\n @click=\"collapsible && toggleCollapse()\"\n >\n <h3 class=\"text-xs font-bold text-text-secondary uppercase\">\n {{ title }}\n </h3>\n <i\n v-if=\"collapsible\"\n :class=\"\n cn(\n 'pi transition-transform duration-200 text-xs text-text-secondary ',\n isCollapsed ? 'pi-chevron-right' : 'pi-chevron-down'\n )\n \"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nconst {\n title,\n modelValue = false,\n collapsible = false\n} = defineProps<{\n title: string\n modelValue?: boolean\n collapsible?: boolean\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nconst isCollapsed = computed({\n get: () => modelValue,\n set: (value: boolean) => emit('update:modelValue', value)\n})\n\nconst toggleCollapse = () => {\n isCollapsed.value = !isCollapsed.value\n}\n</script>\n","<template>\n <header class=\"flex h-16 items-center justify-between px-6\">\n <div class=\"flex items-center gap-2 pl-1\">\n <slot name=\"icon\">\n <i class=\"text-neutral icon-[lucide--puzzle] text-base\" />\n </slot>\n <h2 class=\"text-neutral text-base font-bold\">\n <slot></slot>\n </h2>\n </div>\n </header>\n</template>\n","<template>\n <div class=\"flex h-full w-full flex-col bg-modal-panel-background\">\n <PanelHeader>\n <template #icon>\n <slot name=\"header-icon\"></slot>\n </template>\n <slot name=\"header-title\"></slot>\n </PanelHeader>\n\n <nav\n class=\"flex scrollbar-hide flex-1 flex-col gap-1 overflow-y-auto px-3 py-4\"\n >\n <template v-for=\"(item, index) in navItems\" :key=\"index\">\n <div v-if=\"'items' in item\" class=\"flex flex-col gap-2\">\n <NavTitle\n v-model=\"collapsedGroups[item.title]\"\n :title=\"item.title\"\n :collapsible=\"item.collapsible\"\n />\n <template v-if=\"!item.collapsible || !collapsedGroups[item.title]\">\n <NavItem\n v-for=\"subItem in item.items\"\n :key=\"subItem.id\"\n :icon=\"subItem.icon\"\n :active=\"activeItem === subItem.id\"\n @click=\"activeItem = subItem.id\"\n >\n {{ subItem.label }}\n </NavItem>\n </template>\n </div>\n <div v-else class=\"flex flex-col gap-2\">\n <NavItem\n :icon=\"item.icon\"\n :active=\"activeItem === item.id\"\n @click=\"activeItem = item.id\"\n >\n {{ item.label }}\n </NavItem>\n </div>\n </template>\n </nav>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\nimport NavItem from '@/components/widget/nav/NavItem.vue'\nimport NavTitle from '@/components/widget/nav/NavTitle.vue'\nimport type { NavGroupData, NavItemData } from '@/types/navTypes'\n\nimport PanelHeader from './PanelHeader.vue'\n\nconst { navItems = [], modelValue } = defineProps<{\n navItems?: (NavItemData | NavGroupData)[]\n modelValue?: string | null\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | null]\n}>()\n\n// Track collapsed state for each group\nconst collapsedGroups = ref<Record<string, boolean>>({})\n\nconst getFirstItemId = () => {\n if (!navItems || navItems.length === 0) {\n return null\n }\n\n const firstEntry = navItems[0]\n\n if ('items' in firstEntry && firstEntry.items.length > 0) {\n return firstEntry.items[0].id\n }\n if ('id' in firstEntry) {\n return firstEntry.id\n }\n\n return null\n}\n\nconst activeItem = computed({\n get: () => modelValue ?? getFirstItemId(),\n set: (value: string | null) => emit('update:modelValue', value)\n})\n</script>\n","import { computed } from 'vue'\nimport type { CSSProperties, ComputedRef } from 'vue'\n\ninterface PopoverSizeOptions {\n minWidth?: string\n maxWidth?: string\n}\n\n/**\n * Composable for managing popover sizing styles\n * @param options Popover size configuration\n * @returns Computed style object for popover sizing\n */\nexport function usePopoverSizing(\n options: PopoverSizeOptions\n): ComputedRef<CSSProperties> {\n return computed(() => {\n const { minWidth, maxWidth } = options\n const style: CSSProperties = {}\n\n if (minWidth) {\n style.minWidth = minWidth\n }\n\n if (maxWidth) {\n style.maxWidth = maxWidth\n }\n\n return style\n })\n}\n","<template>\n <!--\n Note: Unlike SingleSelect, we don't need an explicit options prop because:\n 1. Our value template only shows a static label (not dynamic based on selection)\n 2. We display a count badge instead of actual selected labels\n 3. All PrimeVue props (including options) are passed via v-bind=\"$attrs\"\n option-label=\"name\" is required because our option template directly accesses option.name\n max-selected-labels=\"0\" is required to show count badge instead of selected item labels\n -->\n <MultiSelect\n v-model=\"selectedItems\"\n v-bind=\"{ ...$attrs, options: filteredOptions }\"\n option-label=\"name\"\n unstyled\n :max-selected-labels=\"0\"\n :pt=\"{\n root: ({ props }: MultiSelectPassThroughMethodOptions) => ({\n class: cn(\n 'h-10 relative inline-flex cursor-pointer select-none',\n 'rounded-lg bg-secondary-background text-base-foreground',\n 'transition-all duration-200 ease-in-out',\n 'border-[2.5px] border-solid',\n selectedCount > 0\n ? 'border-node-component-border'\n : 'border-transparent',\n 'focus-within:border-node-component-border',\n { 'opacity-60 cursor-default': props.disabled }\n )\n }),\n labelContainer: {\n class:\n 'flex-1 flex items-center overflow-hidden whitespace-nowrap pl-4 py-2 '\n },\n label: {\n class: 'p-0'\n },\n dropdown: {\n class: 'flex shrink-0 cursor-pointer items-center justify-center px-3'\n },\n header: () => ({\n class:\n showSearchBox || showSelectedCount || showClearButton\n ? 'block'\n : 'hidden'\n }),\n // Overlay & list visuals unchanged\n overlay: {\n class: cn(\n 'mt-2 rounded-lg py-2 px-2',\n 'bg-base-background',\n 'text-base-foreground',\n 'border border-solid border-border-default'\n )\n },\n listContainer: () => ({\n style: { maxHeight: `min(${listMaxHeight}, 50vh)` },\n class: 'scrollbar-custom'\n }),\n list: {\n class: 'flex flex-col gap-0 p-0 m-0 list-none border-none text-sm'\n },\n // Option row hover and focus tone\n option: ({ context }: MultiSelectPassThroughMethodOptions) => ({\n class: cn(\n 'flex gap-2 items-center h-10 px-2 rounded-lg cursor-pointer',\n 'hover:bg-secondary-background-hover',\n // Add focus/highlight state for keyboard navigation\n context?.focused &&\n 'bg-secondary-background-selected hover:bg-secondary-background-selected'\n )\n }),\n // Hide built-in checkboxes entirely via PT (no :deep)\n pcHeaderCheckbox: {\n root: { class: 'hidden' },\n style: { display: 'none' }\n },\n pcOptionCheckbox: {\n root: { class: 'hidden' },\n style: { display: 'none' }\n }\n }\"\n :aria-label=\"label || t('g.multiSelectDropdown')\"\n role=\"combobox\"\n :aria-expanded=\"false\"\n aria-haspopup=\"listbox\"\n :tabindex=\"0\"\n >\n <template\n v-if=\"showSearchBox || showSelectedCount || showClearButton\"\n #header\n >\n <div class=\"flex flex-col px-2 pt-2 pb-0\">\n <SearchBox\n v-if=\"showSearchBox\"\n v-model=\"searchQuery\"\n :class=\"showSelectedCount || showClearButton ? 'mb-2' : ''\"\n :show-order=\"true\"\n :show-border=\"true\"\n :place-holder=\"searchPlaceholder\"\n />\n <div\n v-if=\"showSelectedCount || showClearButton\"\n class=\"mt-2 flex items-center justify-between\"\n >\n <span\n v-if=\"showSelectedCount\"\n class=\"px-1 text-sm text-base-foreground\"\n >\n {{\n selectedCount > 0\n ? $t('g.itemsSelected', { selectedCount })\n : $t('g.itemSelected', { selectedCount })\n }}\n </span>\n <Button\n v-if=\"showClearButton\"\n variant=\"textonly\"\n size=\"md\"\n @click.stop=\"selectedItems = []\"\n >\n {{ $t('g.clearAll') }}\n </Button>\n </div>\n <div class=\"my-4 h-px bg-border-default\"></div>\n </div>\n </template>\n\n <!-- Trigger value (keep text scale identical) -->\n <template #value>\n <span class=\"text-sm\">\n {{ label }}\n </span>\n <span\n v-if=\"selectedCount > 0\"\n class=\"pointer-events-none absolute -top-2 -right-2 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-primary-background text-xs font-semibold text-base-foreground\"\n >\n {{ selectedCount }}\n </span>\n </template>\n\n <!-- Chevron size identical to current -->\n <template #dropdownicon>\n <i class=\"icon-[lucide--chevron-down] text-muted-foreground\" />\n </template>\n\n <!-- Custom option row: square checkbox + label (unchanged layout/colors) -->\n <template #option=\"slotProps\">\n <div\n role=\"button\"\n class=\"flex items-center gap-2 cursor-pointer\"\n :style=\"popoverStyle\"\n >\n <div\n class=\"flex size-4 shrink-0 items-center justify-center rounded p-0.5 transition-all duration-200\"\n :class=\"\n slotProps.selected\n ? 'bg-primary-background'\n : 'bg-secondary-background'\n \"\n >\n <i\n v-if=\"slotProps.selected\"\n class=\"text-bold icon-[lucide--check] text-xs text-base-foreground\"\n />\n </div>\n <span>\n {{ slotProps.option.name }}\n </span>\n </div>\n </template>\n </MultiSelect>\n</template>\n\n<script setup lang=\"ts\">\nimport { useFuse } from '@vueuse/integrations/useFuse'\nimport type { UseFuseOptions } from '@vueuse/integrations/useFuse'\nimport type { MultiSelectPassThroughMethodOptions } from 'primevue/multiselect'\nimport MultiSelect from 'primevue/multiselect'\nimport { computed, useAttrs } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport SearchBox from '@/components/common/SearchBox.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { usePopoverSizing } from '@/composables/usePopoverSizing'\nimport { cn } from '@/utils/tailwindUtil'\n\nimport type { SelectOption } from './types'\n\ntype Option = SelectOption\n\ndefineOptions({\n inheritAttrs: false\n})\n\ninterface Props {\n /** Input label shown on the trigger button */\n label?: string\n /** Show search box in the panel header */\n showSearchBox?: boolean\n /** Show selected count text in the panel header */\n showSelectedCount?: boolean\n /** Show \"Clear all\" action in the panel header */\n showClearButton?: boolean\n /** Placeholder for the search input */\n searchPlaceholder?: string\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 // Note: options prop is intentionally omitted.\n // It's passed via $attrs to maximize PrimeVue API compatibility\n}\nconst {\n label,\n showSearchBox = false,\n showSelectedCount = false,\n showClearButton = false,\n searchPlaceholder = 'Search...',\n listMaxHeight = '28rem',\n popoverMinWidth,\n popoverMaxWidth\n} = defineProps<Props>()\n\nconst selectedItems = defineModel<Option[]>({\n required: true\n})\nconst searchQuery = defineModel<string>('searchQuery', { default: '' })\n\nconst { t } = useI18n()\nconst selectedCount = computed(() => selectedItems.value.length)\n\nconst popoverStyle = usePopoverSizing({\n minWidth: popoverMinWidth,\n maxWidth: popoverMaxWidth\n})\nconst attrs = useAttrs()\nconst originalOptions = computed(() => (attrs.options as Option[]) || [])\n\n// Use VueUse's useFuse for better reactivity and performance\nconst fuseOptions: UseFuseOptions<Option> = {\n fuseOptions: {\n keys: ['name', 'value'],\n threshold: 0.3,\n includeScore: false\n },\n matchAllWhenSearchEmpty: true\n}\n\nconst { results } = useFuse(searchQuery, originalOptions, fuseOptions)\n\n// Filter options based on search, but always include selected items\nconst filteredOptions = computed(() => {\n if (!searchQuery.value || searchQuery.value.trim() === '') {\n return originalOptions.value\n }\n\n // results.value already contains the search results from useFuse\n const searchResults = results.value.map(\n (result: { item: Option }) => result.item\n )\n\n // Include selected items that aren't in search results\n const selectedButNotInResults = selectedItems.value.filter(\n (item) =>\n !searchResults.some((result: Option) => result.value === item.value)\n )\n\n return [...selectedButNotInResults, ...searchResults]\n})\n</script>\n","import { uniqWith } from 'es-toolkit'\nimport { computed, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport type { SelectOption } from '@/components/input/types'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\n\n/**\n * Composable that extracts available filter options from asset data\n * Provides reactive computed properties for file formats and base models\n */\nexport function useAssetFilterOptions(assets: MaybeRefOrGetter<AssetItem[]>) {\n /**\n * Extract unique file formats from asset names\n * Returns sorted SelectOption array with extensions\n */\n const availableFileFormats = computed<SelectOption[]>(() => {\n const assetList = toValue(assets)\n const extensions = assetList\n .map((asset) => {\n const extension = asset.name.split('.').pop()\n return extension && extension !== asset.name ? extension : null\n })\n .filter((extension): extension is string => extension !== null)\n\n const uniqueExtensions = uniqWith(extensions, (a, b) => a === b)\n\n return uniqueExtensions.sort().map((format) => ({\n name: `.${format}`,\n value: format\n }))\n })\n\n /**\n * Extract unique base models from asset user metadata\n * Returns sorted SelectOption array with base model names\n */\n const availableBaseModels = computed<SelectOption[]>(() => {\n const assetList = toValue(assets)\n const models = assetList\n .map((asset) => asset.user_metadata?.base_model)\n .filter(\n (baseModel): baseModel is string =>\n baseModel !== undefined && typeof baseModel === 'string'\n )\n\n const uniqueModels = uniqWith(models, (a, b) => a === b)\n\n return uniqueModels.sort().map((model) => ({\n name: model,\n value: model\n }))\n })\n\n return {\n availableFileFormats,\n availableBaseModels\n }\n}\n","<template>\n <div\n class=\"flex gap-4 items-center justify-between px-6 pt-2 pb-6\"\n data-component-id=\"asset-filter-bar\"\n >\n <div\n class=\"flex gap-4 items-center\"\n data-component-id=\"asset-filter-bar-left\"\n >\n <MultiSelect\n v-if=\"availableFileFormats.length > 0\"\n v-model=\"fileFormats\"\n :label=\"$t('assetBrowser.fileFormats')\"\n :options=\"availableFileFormats\"\n class=\"min-w-32\"\n data-component-id=\"asset-filter-file-formats\"\n @update:model-value=\"handleFilterChange\"\n />\n\n <MultiSelect\n v-if=\"availableBaseModels.length > 0\"\n v-model=\"baseModels\"\n :label=\"$t('assetBrowser.baseModels')\"\n :options=\"availableBaseModels\"\n class=\"min-w-32\"\n data-component-id=\"asset-filter-base-models\"\n @update:model-value=\"handleFilterChange\"\n />\n\n <SingleSelect\n v-if=\"hasMutableAssets\"\n v-model=\"ownership\"\n :label=\"$t('assetBrowser.ownership')\"\n :options=\"ownershipOptions\"\n class=\"min-w-42\"\n data-component-id=\"asset-filter-ownership\"\n @update:model-value=\"handleFilterChange\"\n />\n </div>\n\n <div class=\"flex items-center\" data-component-id=\"asset-filter-bar-right\">\n <SingleSelect\n v-model=\"sortBy\"\n :label=\"$t('assetBrowser.sortBy')\"\n :options=\"sortOptions\"\n class=\"min-w-32\"\n data-component-id=\"asset-filter-sort\"\n @update:model-value=\"handleFilterChange\"\n >\n <template #icon>\n <i class=\"icon-[lucide--arrow-up-down]\" />\n </template>\n </SingleSelect>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\nimport MultiSelect from '@/components/input/MultiSelect.vue'\nimport SingleSelect from '@/components/input/SingleSelect.vue'\nimport type { SelectOption } from '@/components/input/types'\nimport { t } from '@/i18n'\nimport type { OwnershipOption } from '@/platform/assets/composables/useAssetBrowser'\nimport { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\n\nconst SORT_OPTIONS = [\n { name: t('assetBrowser.sortRecent'), value: 'recent' },\n { name: t('assetBrowser.sortAZ'), value: 'name-asc' },\n { name: t('assetBrowser.sortZA'), value: 'name-desc' }\n] as const\n\ntype SortOption = (typeof SORT_OPTIONS)[number]['value']\n\nconst sortOptions = [...SORT_OPTIONS]\n\nconst ownershipOptions = [\n { name: t('assetBrowser.ownershipAll'), value: 'all' },\n { name: t('assetBrowser.ownershipMyModels'), value: 'my-models' },\n { name: t('assetBrowser.ownershipPublicModels'), value: 'public-models' }\n]\n\nexport interface FilterState {\n fileFormats: string[]\n baseModels: string[]\n sortBy: string\n ownership: OwnershipOption\n}\n\nconst { assets = [], allAssets = [] } = defineProps<{\n assets?: AssetItem[]\n allAssets?: AssetItem[]\n}>()\n\nconst fileFormats = ref<SelectOption[]>([])\nconst baseModels = ref<SelectOption[]>([])\nconst sortBy = ref<SortOption>('recent')\nconst ownership = ref<OwnershipOption>('all')\n\nconst { availableFileFormats, availableBaseModels } =\n useAssetFilterOptions(assets)\n\nconst hasMutableAssets = computed(() => {\n const assetsToCheck = allAssets.length ? allAssets : assets\n return assetsToCheck.some((asset) => asset.is_immutable === false)\n})\n\nconst emit = defineEmits<{\n filterChange: [filters: FilterState]\n}>()\n\nfunction handleFilterChange() {\n emit('filterChange', {\n fileFormats: fileFormats.value.map((option: SelectOption) => option.value),\n baseModels: baseModels.value.map((option: SelectOption) => option.value),\n sortBy: sortBy.value,\n ownership: ownership.value\n })\n}\n</script>\n","<template>\n <div class=\"relative inline-flex items-center\">\n <Button size=\"icon\" variant=\"secondary\" @click=\"popover?.toggle\">\n <i\n :class=\"\n cn(\n !isVertical\n ? 'icon-[lucide--ellipsis]'\n : 'icon-[lucide--more-vertical]',\n 'text-sm'\n )\n \"\n />\n </Button>\n\n <Popover\n ref=\"popover\"\n append-to=\"body\"\n auto-z-index\n dismissable\n close-on-escape\n unstyled\n :base-z-index=\"1000\"\n :pt=\"{\n root: {\n class: cn('absolute z-50')\n },\n content: {\n class: cn(\n 'mt-1 rounded-lg',\n 'bg-secondary-background text-base-foreground',\n 'shadow-lg'\n )\n }\n }\"\n @show=\"\n () => {\n isOpen = true\n $emit('menuOpened')\n }\n \"\n @hide=\"\n () => {\n isOpen = false\n $emit('menuClosed')\n }\n \"\n >\n <div class=\"flex min-w-40 flex-col gap-2 p-2\">\n <slot :close=\"hide\" />\n </div>\n </Popover>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport { ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { cn } from '@/utils/tailwindUtil'\n\ninterface MoreButtonProps {\n isVertical?: boolean\n}\n\nconst { isVertical = false } = defineProps<MoreButtonProps>()\n\ndefineEmits<{\n menuOpened: []\n menuClosed: []\n}>()\n\nconst isOpen = ref(false)\nconst popover = ref<InstanceType<typeof Popover>>()\n\nfunction hide() {\n popover.value?.hide()\n}\n\ndefineExpose({\n hide,\n isOpen\n})\n</script>\n","<template>\n <div\n class=\"flex flex-col px-4 py-2 text-sm text-muted-foreground border-t border-border-default\"\n >\n <p v-if=\"promptTextReal\">\n {{ promptTextReal }}\n </p>\n </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\nconst { promptText } = defineProps<{\n promptText?: MaybeRefOrGetter<string>\n}>()\n\nconst promptTextReal = computed(() => toValue(promptText))\n</script>\n","<template>\n <section class=\"w-full flex gap-2 justify-end px-2 pb-2\">\n <Button :disabled variant=\"textonly\" autofocus @click=\"$emit('cancel')\">\n {{ cancelTextX }}\n </Button>\n <Button\n :disabled\n variant=\"textonly\"\n :class=\"confirmClass\"\n @click=\"$emit('confirm')\"\n >\n {{ confirmTextX }}\n </Button>\n </section>\n</template>\n<script setup lang=\"ts\">\nimport { computed, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\n\nconst { t } = useI18n()\n\nconst { cancelText, confirmText, confirmClass, optionsDisabled } = defineProps<{\n cancelText?: string\n confirmText?: string\n confirmClass?: string\n optionsDisabled?: MaybeRefOrGetter<boolean>\n}>()\n\ndefineEmits<{\n cancel: []\n confirm: []\n}>()\n\nconst confirmTextX = computed(() => confirmText || t('g.confirm'))\nconst cancelTextX = computed(() => cancelText || t('g.cancel'))\nconst disabled = computed(() => toValue(optionsDisabled))\n</script>\n","import ConfirmBody from '@/components/dialog/confirm/ConfirmBody.vue'\nimport ConfirmFooter from '@/components/dialog/confirm/ConfirmFooter.vue'\nimport ConfirmHeader from '@/components/dialog/confirm/ConfirmHeader.vue'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport type { ComponentAttrs } from 'vue-component-type-helpers'\n\ninterface ConfirmDialogOptions {\n headerProps?: ComponentAttrs<typeof ConfirmHeader>\n props?: ComponentAttrs<typeof ConfirmBody>\n footerProps?: ComponentAttrs<typeof ConfirmFooter>\n}\n\nexport function showConfirmDialog(options: ConfirmDialogOptions = {}) {\n const dialogStore = useDialogStore()\n const { headerProps, props, footerProps } = options\n return dialogStore.showDialog({\n headerComponent: ConfirmHeader,\n component: ConfirmBody,\n footerComponent: ConfirmFooter,\n headerProps,\n props,\n footerProps,\n dialogComponentProps: {\n pt: {\n header: 'py-0! px-0!',\n content: 'p-0!',\n footer: 'p-0!'\n }\n }\n })\n}\n","<template>\n <div\n data-component-id=\"AssetCard\"\n :data-asset-id=\"asset.id\"\n :aria-labelledby=\"titleId\"\n :aria-describedby=\"descId\"\n :tabindex=\"interactive ? 0 : -1\"\n :class=\"\n cn(\n 'rounded-2xl overflow-hidden transition-all duration-200 bg-modal-card-background p-2 gap-2 flex flex-col h-full',\n interactive &&\n 'group appearance-none bg-transparent m-0 outline-none text-left hover:bg-secondary-background focus:bg-secondary-background border-none focus:outline-solid outline-base-foreground outline-4'\n )\n \"\n @keydown.enter.self=\"interactive && $emit('select', asset)\"\n >\n <div class=\"relative aspect-square w-full overflow-hidden rounded-xl\">\n <div\n v-if=\"isLoading || error\"\n class=\"flex size-full cursor-pointer items-center justify-center bg-gradient-to-br from-smoke-400 via-smoke-800 to-charcoal-400\"\n role=\"button\"\n @click.self=\"interactive && $emit('select', asset)\"\n />\n <img\n v-else\n :src=\"asset.preview_url\"\n :alt=\"displayName\"\n class=\"size-full object-cover cursor-pointer\"\n role=\"button\"\n @click.self=\"interactive && $emit('select', asset)\"\n />\n\n <AssetBadgeGroup :badges=\"asset.badges\" />\n <IconGroup\n v-if=\"showAssetOptions\"\n :class=\"\n cn(\n 'absolute top-2 right-2 invisible group-hover:visible',\n dropdownMenuButton?.isOpen && 'visible'\n )\n \"\n >\n <MoreButton ref=\"dropdown-menu-button\" size=\"sm\">\n <template #default>\n <Button\n v-if=\"flags.assetRenameEnabled\"\n variant=\"secondary\"\n size=\"md\"\n class=\"justify-start\"\n @click=\"startAssetRename\"\n >\n <i class=\"icon-[lucide--pencil]\" />\n <span>{{ $t('g.rename') }}</span>\n </Button>\n <Button\n v-if=\"flags.assetDeletionEnabled\"\n variant=\"secondary\"\n size=\"md\"\n class=\"justify-start\"\n @click=\"confirmDeletion\"\n >\n <i class=\"icon-[lucide--trash-2]\" />\n <span>{{ $t('g.delete') }}</span>\n </Button>\n </template>\n </MoreButton>\n </IconGroup>\n </div>\n <div class=\"max-h-32 flex flex-col gap-2 justify-between flex-auto\">\n <h3\n :id=\"titleId\"\n v-tooltip.top=\"{ value: displayName, showDelay: tooltipDelay }\"\n :class=\"\n cn(\n 'mb-2 m-0 text-base font-semibold line-clamp-2 wrap-anywhere',\n 'text-base-foreground'\n )\n \"\n >\n <EditableText\n :model-value=\"displayName\"\n :is-editing=\"isEditing\"\n :input-attrs=\"{ 'data-testid': 'asset-name-input' }\"\n @edit=\"assetRename\"\n @cancel=\"assetRename()\"\n />\n </h3>\n <p\n :id=\"descId\"\n v-tooltip.top=\"{ value: asset.description, showDelay: tooltipDelay }\"\n :class=\"\n cn(\n 'm-0 text-sm leading-6 overflow-hidden [-webkit-box-orient:vertical] [-webkit-line-clamp:2] [display:-webkit-box] text-muted-foreground'\n )\n \"\n >\n {{ asset.description }}\n </p>\n <div class=\"flex gap-4 text-xs text-muted-foreground mt-auto\">\n <span v-if=\"asset.stats.stars\" class=\"flex items-center gap-1\">\n <i class=\"icon-[lucide--star] size-3\" />\n {{ asset.stats.stars }}\n </span>\n <span v-if=\"asset.stats.downloadCount\" class=\"flex items-center gap-1\">\n <i class=\"icon-[lucide--download] size-3\" />\n {{ asset.stats.downloadCount }}\n </span>\n <span v-if=\"asset.stats.formattedDate\" class=\"flex items-center gap-1\">\n <i class=\"icon-[lucide--clock] size-3\" />\n {{ asset.stats.formattedDate }}\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useImage } from '@vueuse/core'\nimport { computed, ref, toValue, useId, useTemplateRef } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport IconGroup from '@/components/button/IconGroup.vue'\nimport MoreButton from '@/components/button/MoreButton.vue'\nimport EditableText from '@/components/common/EditableText.vue'\nimport { showConfirmDialog } from '@/components/dialog/confirm/confirmDialog'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue'\nimport type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'\nimport { assetService } from '@/platform/assets/services/assetService'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useToastStore } from '@/platform/updates/common/toastStore'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { asset, interactive } = defineProps<{\n asset: AssetDisplayItem\n interactive?: boolean\n}>()\n\nconst emit = defineEmits<{\n select: [asset: AssetDisplayItem]\n deleted: [asset: AssetDisplayItem]\n}>()\n\nconst { t } = useI18n()\nconst settingStore = useSettingStore()\nconst { closeDialog } = useDialogStore()\nconst { flags } = useFeatureFlags()\nconst toastStore = useToastStore()\n\nconst dropdownMenuButton = useTemplateRef<InstanceType<typeof MoreButton>>(\n 'dropdown-menu-button'\n)\n\nconst titleId = useId()\nconst descId = useId()\n\nconst isEditing = ref(false)\nconst newNameRef = ref<string>()\n\nconst displayName = computed(() => newNameRef.value ?? asset.name)\n\nconst showAssetOptions = computed(\n () =>\n (flags.assetDeletionEnabled || flags.assetRenameEnabled) &&\n !(asset.is_immutable ?? true)\n)\n\nconst tooltipDelay = computed<number>(() =>\n settingStore.get('LiteGraph.Node.TooltipDelay')\n)\n\nconst { isLoading, error } = useImage({\n src: asset.preview_url ?? '',\n alt: asset.name\n})\n\nfunction confirmDeletion() {\n dropdownMenuButton.value?.hide()\n const assetName = toValue(displayName)\n const promptText = ref<string>(t('assetBrowser.deletion.body'))\n const optionsDisabled = ref(false)\n const confirmDialog = showConfirmDialog({\n headerProps: {\n title: t('assetBrowser.deletion.header')\n },\n props: {\n promptText\n },\n footerProps: {\n confirmText: t('g.delete'),\n // TODO: These need to be put into the new Button Variants once we have them.\n confirmClass: cn(\n 'bg-danger-200 text-base-foreground hover:bg-danger-200/80 focus:bg-danger-200/80 focus:ring ring-base-foreground'\n ),\n optionsDisabled,\n onCancel: () => {\n closeDialog(confirmDialog)\n },\n onConfirm: async () => {\n optionsDisabled.value = true\n try {\n promptText.value = t('assetBrowser.deletion.inProgress', {\n assetName\n })\n await assetService.deleteAsset(asset.id)\n promptText.value = t('assetBrowser.deletion.complete', {\n assetName\n })\n // Give a second for the completion message\n await new Promise((resolve) => setTimeout(resolve, 1_000))\n emit('deleted', asset)\n } catch (err: unknown) {\n console.error(err)\n promptText.value = t('assetBrowser.deletion.failed', {\n assetName\n })\n // Give a second for the completion message\n await new Promise((resolve) => setTimeout(resolve, 3_000))\n } finally {\n closeDialog(confirmDialog)\n }\n }\n }\n })\n}\n\nfunction startAssetRename() {\n dropdownMenuButton.value?.hide()\n isEditing.value = true\n}\n\nasync function assetRename(newName?: string) {\n isEditing.value = false\n if (newName) {\n // Optimistic update\n newNameRef.value = newName\n try {\n const result = await assetService.updateAsset(asset.id, {\n name: newName\n })\n // Update with the actual name once the server responds\n newNameRef.value = result.name\n } catch (err: unknown) {\n console.error(err)\n toastStore.add({\n severity: 'error',\n summary: t('assetBrowser.rename.failed'),\n life: 10_000\n })\n newNameRef.value = undefined\n }\n }\n}\n</script>\n","<template>\n <div\n data-component-id=\"AssetGrid\"\n class=\"h-full\"\n role=\"grid\"\n :aria-label=\"$t('assetBrowser.assetCollection')\"\n :aria-rowcount=\"-1\"\n :aria-colcount=\"-1\"\n :aria-setsize=\"assets.length\"\n >\n <div v-if=\"loading\" class=\"flex h-full items-center justify-center py-20\">\n <i\n class=\"icon-[lucide--loader] size-12 animate-spin text-muted-foreground\"\n />\n </div>\n <div\n v-else-if=\"assets.length === 0\"\n class=\"flex h-full flex-col items-center justify-center py-16 text-muted-foreground\"\n >\n <i class=\"mb-4 icon-[lucide--search] size-10\" />\n <h3 class=\"mb-2 text-lg font-medium\">\n {{ $t('assetBrowser.noAssetsFound') }}\n </h3>\n <p class=\"text-sm\">{{ $t('assetBrowser.tryAdjustingFilters') }}</p>\n </div>\n <VirtualGrid\n v-else\n :items=\"assetsWithKey\"\n :grid-style=\"gridStyle\"\n :default-item-height=\"320\"\n :default-item-width=\"240\"\n >\n <template #item=\"{ item }\">\n <AssetCard\n :asset=\"item\"\n :interactive=\"true\"\n @select=\"$emit('assetSelect', $event)\"\n @deleted=\"$emit('assetDeleted', $event)\"\n />\n </template>\n </VirtualGrid>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport type { CSSProperties } from 'vue'\nimport { computed } from 'vue'\n\nimport VirtualGrid from '@/components/common/VirtualGrid.vue'\nimport AssetCard from '@/platform/assets/components/AssetCard.vue'\nimport type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'\n\nconst { assets } = defineProps<{\n assets: AssetDisplayItem[]\n loading?: boolean\n}>()\n\ndefineEmits<{\n assetSelect: [asset: AssetDisplayItem]\n assetDeleted: [asset: AssetDisplayItem]\n}>()\n\nconst assetsWithKey = computed(() =>\n assets.map((asset) => ({ ...asset, key: asset.id }))\n)\n\nconst gridStyle: Partial<CSSProperties> = {\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(15rem, 1fr))',\n gap: '1rem',\n padding: '0.5rem'\n}\n</script>\n","import type { AssetItem } from '@/platform/assets/schemas/assetSchema'\n\n/**\n * Type-safe utilities for extracting metadata from assets\n */\n\n/**\n * Safely extracts string description from asset metadata\n * @param asset - The asset to extract description from\n * @returns The description string or null if not present/not a string\n */\nexport function getAssetDescription(asset: AssetItem): string | null {\n return typeof asset.user_metadata?.description === 'string'\n ? asset.user_metadata.description\n : null\n}\n\n/**\n * Safely extracts string base_model from asset metadata\n * @param asset - The asset to extract base_model from\n * @returns The base_model string or null if not present/not a string\n */\nexport function getAssetBaseModel(asset: AssetItem): string | null {\n return typeof asset.user_metadata?.base_model === 'string'\n ? asset.user_metadata.base_model\n : null\n}\n","import { computed, ref } from 'vue'\nimport type { Ref } from 'vue'\nimport { useFuse } from '@vueuse/integrations/useFuse'\nimport type { UseFuseOptions } from '@vueuse/integrations/useFuse'\n\nimport { d, t } from '@/i18n'\nimport type { FilterState } from '@/platform/assets/components/AssetFilterBar.vue'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\nimport {\n getAssetBaseModel,\n getAssetDescription\n} from '@/platform/assets/utils/assetMetadataUtils'\n\nexport type OwnershipOption = 'all' | 'my-models' | 'public-models'\n\nfunction filterByCategory(category: string) {\n return (asset: AssetItem) => {\n if (category === 'all') return true\n\n // Check if any tag matches the category (for exact matches)\n if (asset.tags.includes(category)) return true\n\n // Check if any tag's top-level folder matches the category\n return asset.tags.some((tag) => {\n if (typeof tag === 'string' && tag.includes('/')) {\n return tag.split('/')[0] === category\n }\n return false\n })\n }\n}\n\nfunction filterByFileFormats(formats: string[]) {\n return (asset: AssetItem) => {\n if (formats.length === 0) return true\n const formatSet = new Set(formats)\n const extension = asset.name.split('.').pop()?.toLowerCase()\n return extension ? formatSet.has(extension) : false\n }\n}\n\nfunction filterByBaseModels(models: string[]) {\n return (asset: AssetItem) => {\n if (models.length === 0) return true\n const modelSet = new Set(models)\n const baseModel = getAssetBaseModel(asset)\n return baseModel ? modelSet.has(baseModel) : false\n }\n}\n\nfunction filterByOwnership(ownership: OwnershipOption) {\n return (asset: AssetItem) => {\n if (ownership === 'all') return true\n if (ownership === 'my-models') return asset.is_immutable === false\n if (ownership === 'public-models') return asset.is_immutable === true\n return true\n }\n}\n\ntype AssetBadge = {\n label: string\n type: 'type' | 'base' | 'size'\n}\n\n// Display properties for transformed assets\nexport interface AssetDisplayItem extends AssetItem {\n description: string\n badges: AssetBadge[]\n stats: {\n formattedDate?: string\n downloadCount?: string\n stars?: string\n }\n}\n\n/**\n * Asset Browser composable\n * Manages search, filtering, asset transformation and selection logic\n */\nexport function useAssetBrowser(\n assetsSource: Ref<AssetItem[] | undefined> = ref<AssetItem[] | undefined>([])\n) {\n const assets = computed<AssetItem[]>(() => assetsSource.value ?? [])\n // State\n const searchQuery = ref('')\n const selectedCategory = ref('all')\n const filters = ref<FilterState>({\n sortBy: 'recent',\n fileFormats: [],\n baseModels: [],\n ownership: 'all'\n })\n\n // Transform API asset to display asset\n function transformAssetForDisplay(asset: AssetItem): AssetDisplayItem {\n // Extract description from metadata or create from tags\n const typeTag = asset.tags.find((tag) => tag !== 'models')\n const description =\n getAssetDescription(asset) ||\n `${typeTag || t('assetBrowser.unknown')} model`\n\n // Create badges from tags and metadata\n const badges: AssetBadge[] = []\n\n // Type badge from non-root tag\n if (typeTag) {\n // Remove category prefix from badge label (e.g. \"checkpoint/model\" → \"model\")\n const badgeLabel = typeTag.includes('/')\n ? typeTag.substring(typeTag.indexOf('/') + 1)\n : typeTag\n\n badges.push({ label: badgeLabel, type: 'type' })\n }\n\n // Base model badge from metadata\n const baseModel = getAssetBaseModel(asset)\n if (baseModel) {\n badges.push({\n label: baseModel,\n type: 'base'\n })\n }\n\n // Create display stats from API data\n const stats = {\n formattedDate: d(new Date(asset.created_at), { dateStyle: 'short' }),\n downloadCount: undefined, // Not available in API\n stars: undefined // Not available in API\n }\n\n return {\n ...asset,\n description,\n badges,\n stats\n }\n }\n\n const availableCategories = computed(() => {\n const categories = assets.value\n .filter((asset) => asset.tags[0] === 'models')\n .map((asset) => asset.tags[1])\n .filter((tag): tag is string => typeof tag === 'string' && tag.length > 0)\n .map((tag) => tag.split('/')[0]) // Extract top-level folder name\n\n const uniqueCategories = Array.from(new Set(categories))\n .sort()\n .map((category) => ({\n id: category,\n label: category.charAt(0).toUpperCase() + category.slice(1),\n icon: 'icon-[lucide--package]'\n }))\n\n return [\n {\n id: 'all',\n label: t('assetBrowser.allModels'),\n icon: 'icon-[lucide--folder]'\n },\n ...uniqueCategories\n ]\n })\n\n // Compute content title from selected category\n const contentTitle = computed(() => {\n if (selectedCategory.value === 'all') {\n return t('assetBrowser.allModels')\n }\n\n const category = availableCategories.value.find(\n (cat) => cat.id === selectedCategory.value\n )\n return category?.label || t('assetBrowser.assets')\n })\n\n // Category-filtered assets for filter options (before search/format/base model filters)\n const categoryFilteredAssets = computed(() => {\n return assets.value.filter(filterByCategory(selectedCategory.value))\n })\n\n const fuseOptions: UseFuseOptions<AssetItem> = {\n fuseOptions: {\n keys: [\n { name: 'name', weight: 0.4 },\n { name: 'tags', weight: 0.3 }\n ],\n threshold: 0.4, // Higher threshold for typo tolerance (0.0 = exact, 1.0 = match all)\n ignoreLocation: true, // Search anywhere in the string, not just at the beginning\n includeScore: true\n },\n matchAllWhenSearchEmpty: true\n }\n\n const { results: fuseResults } = useFuse(\n searchQuery,\n categoryFilteredAssets,\n fuseOptions\n )\n\n const searchFiltered = computed(() =>\n fuseResults.value.map((result) => result.item)\n )\n\n const filteredAssets = computed(() => {\n const filtered = searchFiltered.value\n .filter(filterByFileFormats(filters.value.fileFormats))\n .filter(filterByBaseModels(filters.value.baseModels))\n .filter(filterByOwnership(filters.value.ownership))\n\n const sortedAssets = [...filtered]\n sortedAssets.sort((a, b) => {\n switch (filters.value.sortBy) {\n case 'name-desc':\n return b.name.localeCompare(a.name)\n case 'recent':\n return (\n new Date(b.created_at).getTime() - new Date(a.created_at).getTime()\n )\n case 'popular':\n return a.name.localeCompare(b.name)\n case 'name-asc':\n default:\n return a.name.localeCompare(b.name)\n }\n })\n\n // Transform to display format\n return sortedAssets.map(transformAssetForDisplay)\n })\n\n function updateFilters(newFilters: FilterState) {\n filters.value = { ...newFilters }\n }\n\n return {\n searchQuery,\n selectedCategory,\n availableCategories,\n contentTitle,\n categoryFilteredAssets,\n filteredAssets,\n updateFilters\n }\n}\n","const ACRONYM_TAGS = new Set(['VAE', 'CLIP', 'GLIGEN'])\n\nexport function formatCategoryLabel(raw?: string): string {\n if (!raw) return 'Models'\n\n // Special display name mappings\n if (raw === 'diffusion_models') return 'Diffusion'\n\n return raw\n .split('_')\n .map((segment) => {\n const upper = segment.toUpperCase()\n if (ACRONYM_TAGS.has(upper)) return upper\n\n const lower = segment.toLowerCase()\n return lower.charAt(0).toUpperCase() + lower.slice(1)\n })\n .join(' ')\n}\n","<template>\n <BaseModalLayout\n data-component-id=\"AssetBrowserModal\"\n class=\"size-full max-h-full max-w-full min-w-0\"\n :content-title=\"displayTitle\"\n @close=\"handleClose\"\n >\n <template v-if=\"shouldShowLeftPanel\" #leftPanel>\n <LeftSidePanel\n v-model=\"selectedCategory\"\n data-component-id=\"AssetBrowserModal-LeftSidePanel\"\n :nav-items=\"availableCategories\"\n >\n <template #header-icon>\n <div class=\"icon-[lucide--folder] size-4\" />\n </template>\n <template #header-title>\n <span class=\"capitalize\">{{ displayTitle }}</span>\n </template>\n </LeftSidePanel>\n </template>\n\n <template #header>\n <div class=\"flex w-full items-center justify-between gap-2\">\n <SearchBox\n v-model=\"searchQuery\"\n :autofocus=\"true\"\n size=\"lg\"\n :placeholder=\"$t('g.searchPlaceholder')\"\n class=\"max-w-96\"\n />\n <Button\n v-if=\"isUploadButtonEnabled\"\n variant=\"primary\"\n :size=\"breakpoints.md ? 'lg' : 'icon'\"\n data-attr=\"upload-model-button\"\n @click=\"showUploadDialog\"\n >\n <i class=\"icon-[lucide--folder-input]\" />\n <span class=\"hidden md:inline\">{{\n $t('assetBrowser.uploadModel')\n }}</span>\n </Button>\n </div>\n </template>\n\n <template #contentFilter>\n <AssetFilterBar\n :assets=\"categoryFilteredAssets\"\n :all-assets=\"fetchedAssets\"\n @filter-change=\"updateFilters\"\n />\n </template>\n\n <template #content>\n <AssetGrid\n :assets=\"filteredAssets\"\n :loading=\"isLoading\"\n @asset-select=\"handleAssetSelectAndEmit\"\n @asset-deleted=\"refreshAssets\"\n />\n </template>\n </BaseModalLayout>\n</template>\n\n<script setup lang=\"ts\">\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core'\nimport { computed, provide } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport SearchBox from '@/components/common/SearchBox.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'\nimport LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'\nimport AssetFilterBar from '@/platform/assets/components/AssetFilterBar.vue'\nimport AssetGrid from '@/platform/assets/components/AssetGrid.vue'\nimport type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'\nimport { useAssetBrowser } from '@/platform/assets/composables/useAssetBrowser'\nimport { useModelUpload } from '@/platform/assets/composables/useModelUpload'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\nimport { formatCategoryLabel } from '@/platform/assets/utils/categoryLabel'\nimport { useAssetsStore } from '@/stores/assetsStore'\nimport { useModelToNodeStore } from '@/stores/modelToNodeStore'\nimport { OnCloseKey } from '@/types/widgetTypes'\n\nconst { t } = useI18n()\nconst assetStore = useAssetsStore()\nconst modelToNodeStore = useModelToNodeStore()\nconst breakpoints = useBreakpoints(breakpointsTailwind)\n\nconst props = defineProps<{\n nodeType?: string\n assetType?: string\n onSelect?: (asset: AssetItem) => void\n onClose?: () => void\n showLeftPanel?: boolean\n title?: string\n}>()\n\nconst emit = defineEmits<{\n 'asset-select': [asset: AssetDisplayItem]\n close: []\n}>()\n\nprovide(OnCloseKey, props.onClose ?? (() => {}))\n\n// Compute the cache key based on nodeType or assetType\nconst cacheKey = computed(() => {\n if (props.nodeType) return props.nodeType\n if (props.assetType) return `tag:${props.assetType}`\n return ''\n})\n\n// Read directly from store cache - reactive to any store updates\nconst fetchedAssets = computed(\n () => assetStore.modelAssetsByNodeType.get(cacheKey.value) ?? []\n)\n\nconst isStoreLoading = computed(\n () => assetStore.modelLoadingByNodeType.get(cacheKey.value) ?? false\n)\n\n// Only show loading spinner when loading AND no cached data\nconst isLoading = computed(\n () => isStoreLoading.value && fetchedAssets.value.length === 0\n)\n\nasync function refreshAssets(): Promise<AssetItem[]> {\n if (props.nodeType) {\n return await assetStore.updateModelsForNodeType(props.nodeType)\n }\n if (props.assetType) {\n return await assetStore.updateModelsForTag(props.assetType)\n }\n return []\n}\n\n// Trigger background refresh on mount\nvoid refreshAssets()\n\nconst { isUploadButtonEnabled, showUploadDialog } =\n useModelUpload(refreshAssets)\n\nconst {\n searchQuery,\n selectedCategory,\n availableCategories,\n categoryFilteredAssets,\n filteredAssets,\n updateFilters\n} = useAssetBrowser(fetchedAssets)\n\nconst primaryCategoryTag = computed(() => {\n const assets = fetchedAssets.value ?? []\n const tagFromAssets = assets\n .map((asset) => asset.tags?.find((tag) => tag !== 'models'))\n .find((tag): tag is string => typeof tag === 'string' && tag.length > 0)\n\n if (tagFromAssets) return tagFromAssets\n\n if (props.nodeType) {\n const mapped = modelToNodeStore.getCategoryForNodeType(props.nodeType)\n if (mapped) return mapped\n }\n\n if (props.assetType) return props.assetType\n\n return 'models'\n})\n\nconst activeCategoryTag = computed(() => {\n if (selectedCategory.value !== 'all') {\n return selectedCategory.value\n }\n return primaryCategoryTag.value\n})\n\nconst displayTitle = computed(() => {\n if (props.title) return props.title\n\n const label = formatCategoryLabel(activeCategoryTag.value)\n return t('assetBrowser.allCategory', { category: label })\n})\n\nconst shouldShowLeftPanel = computed(() => {\n return props.showLeftPanel ?? true\n})\n\nfunction handleClose() {\n props.onClose?.()\n emit('close')\n}\n\nfunction handleAssetSelectAndEmit(asset: AssetDisplayItem) {\n emit('asset-select', asset)\n // onSelect callback is provided by dialog composable layer\n // It handles the appropriate transformation (filename extraction or full asset)\n props.onSelect?.(asset)\n}\n</script>\n","import AssetBrowserModal from '@/platform/assets/components/AssetBrowserModal.vue'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport type { DialogComponentProps } from '@/stores/dialogStore'\n\ninterface ShowOptions {\n /** ComfyUI node type for context (e.g., 'CheckpointLoaderSimple') */\n nodeType: string\n /** Widget input name (e.g., 'ckpt_name') */\n inputName: string\n /** Current selected asset value */\n currentValue?: string\n onAssetSelected?: (asset: AssetItem) => void\n}\n\ninterface BrowseOptions {\n /** Asset type tag to filter by (e.g., 'models') */\n assetType: string\n /** Custom modal title (optional) */\n title?: string\n /** Called when asset selected */\n onAssetSelected?: (asset: AssetItem) => void\n}\n\nconst dialogComponentProps: DialogComponentProps = {\n headless: true,\n modal: true,\n closable: true,\n pt: {\n root: {\n class: 'rounded-2xl overflow-hidden asset-browser-dialog'\n },\n header: {\n class: '!p-0 hidden'\n },\n content: {\n class: '!p-0 !m-0 h-full w-full'\n }\n }\n} as const\n\nexport const useAssetBrowserDialog = () => {\n const dialogStore = useDialogStore()\n const dialogKey = 'global-asset-browser'\n\n async function show(props: ShowOptions) {\n const handleAssetSelected = (asset: AssetItem) => {\n props.onAssetSelected?.(asset)\n dialogStore.closeDialog({ key: dialogKey })\n }\n\n dialogStore.showDialog({\n key: dialogKey,\n component: AssetBrowserModal,\n props: {\n nodeType: props.nodeType,\n inputName: props.inputName,\n currentValue: props.currentValue,\n onSelect: handleAssetSelected,\n onClose: () => dialogStore.closeDialog({ key: dialogKey })\n },\n dialogComponentProps\n })\n }\n\n async function browse(options: BrowseOptions): Promise<void> {\n const handleAssetSelected = (asset: AssetItem) => {\n options.onAssetSelected?.(asset)\n dialogStore.closeDialog({ key: dialogKey })\n }\n\n dialogStore.showDialog({\n key: dialogKey,\n component: AssetBrowserModal,\n props: {\n showLeftPanel: true,\n assetType: options.assetType,\n title: options.title,\n onSelect: handleAssetSelected,\n onClose: () => dialogStore.closeDialog({ key: dialogKey })\n },\n dialogComponentProps\n })\n }\n\n return { show, browse }\n}\n","<template>\n <!-- Icon-only mode with Popover -->\n <div\n v-if=\"displayMode === 'icon-only'\"\n class=\"relative inline-flex h-full shrink-0 items-center justify-center px-2\"\n :class=\"clickableClasses\"\n :style=\"menuBackgroundStyle\"\n @click=\"togglePopover\"\n >\n <i\n v-if=\"iconClass\"\n :class=\"['shrink-0 text-base', iconClass, iconColorClass]\"\n />\n <div\n v-else-if=\"badge.label\"\n class=\"shrink-0 rounded-full px-1.5 py-0.5 text-xxxs font-semibold\"\n :class=\"labelClasses\"\n >\n {{ badge.label }}\n </div>\n <div v-else class=\"size-2 shrink-0 rounded-full\" :class=\"dotClasses\" />\n <Popover\n ref=\"popover\"\n append-to=\"body\"\n :auto-z-index=\"true\"\n :base-z-index=\"1000\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"popoverPt\"\n >\n <div class=\"flex max-w-xs min-w-40 flex-col gap-2 p-3\">\n <div\n v-if=\"badge.label\"\n class=\"w-fit rounded-full px-1.5 py-0.5 text-xxxs font-semibold\"\n :class=\"labelClasses\"\n >\n {{ badge.label }}\n </div>\n <div class=\"text-sm font-inter\">{{ badge.text }}</div>\n <div v-if=\"badge.tooltip\" class=\"text-xs\">\n {{ badge.tooltip }}\n </div>\n </div>\n </Popover>\n </div>\n\n <!-- Compact mode: Icon + Label only with Popover -->\n <div\n v-else-if=\"displayMode === 'compact'\"\n class=\"relative inline-flex h-full\"\n :style=\"menuBackgroundStyle\"\n >\n <div\n class=\"flex h-full shrink-0 items-center gap-2 whitespace-nowrap\"\n :class=\"[\n { 'flex-row-reverse': reverseOrder },\n noPadding ? '' : 'px-3',\n clickableClasses\n ]\"\n @click=\"togglePopover\"\n >\n <i\n v-if=\"iconClass\"\n :class=\"['shrink-0 text-base', iconClass, iconColorClass]\"\n />\n <div\n v-if=\"badge.label\"\n class=\"shrink-0 rounded-full px-1.5 py-0.5 text-xxxs font-semibold\"\n :class=\"labelClasses\"\n >\n {{ badge.label }}\n </div>\n </div>\n <Popover\n ref=\"popover\"\n append-to=\"body\"\n :auto-z-index=\"true\"\n :base-z-index=\"1000\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"popoverPt\"\n >\n <div class=\"flex max-w-xs min-w-40 flex-col gap-2 p-3\">\n <div\n v-if=\"badge.label\"\n class=\"w-fit rounded-full px-1.5 py-0.5 text-xxxs font-semibold\"\n :class=\"labelClasses\"\n >\n {{ badge.label }}\n </div>\n <div class=\"text-sm font-inter\">{{ badge.text }}</div>\n <div v-if=\"badge.tooltip\" class=\"text-xs\">\n {{ badge.tooltip }}\n </div>\n </div>\n </Popover>\n </div>\n\n <!-- Full mode: Icon + Label + Text -->\n <div\n v-else\n v-tooltip=\"badge.tooltip\"\n class=\"flex h-full shrink-0 items-center gap-2 whitespace-nowrap\"\n :class=\"[{ 'flex-row-reverse': reverseOrder }, noPadding ? '' : 'px-3']\"\n :style=\"menuBackgroundStyle\"\n >\n <i\n v-if=\"iconClass\"\n :class=\"['shrink-0 text-base', iconClass, iconColorClass]\"\n />\n <div\n v-if=\"badge.label\"\n class=\"shrink-0 rounded-full px-1.5 py-0.5 text-xxxs font-semibold\"\n :class=\"labelClasses\"\n >\n {{ badge.label }}\n </div>\n <div class=\"font-inter text-sm\" :class=\"textClasses\">\n {{ badge.text }}\n </div>\n </div>\n</template>\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport { computed, ref } from 'vue'\n\nimport type { TopbarBadge } from '@/types/comfy'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst props = withDefaults(\n defineProps<{\n badge: TopbarBadge\n displayMode?: 'full' | 'compact' | 'icon-only'\n reverseOrder?: boolean\n noPadding?: boolean\n backgroundColor?: string\n }>(),\n {\n displayMode: 'full',\n reverseOrder: false,\n noPadding: false,\n backgroundColor: 'var(--comfy-menu-bg)'\n }\n)\n\nconst popover = ref<InstanceType<typeof Popover>>()\n\nconst togglePopover = (event: Event) => {\n popover.value?.toggle(event)\n}\n\nconst variant = computed(() => props.badge.variant ?? 'info')\n\nconst menuBackgroundStyle = computed(() => ({\n backgroundColor: props.backgroundColor\n}))\n\nconst labelClasses = computed(() => {\n switch (variant.value) {\n case 'error':\n return 'bg-danger-100 text-white'\n case 'warning':\n return 'bg-gold-600 text-black'\n case 'info':\n default:\n return 'bg-white text-black'\n }\n})\n\nconst textClasses = computed(() => {\n switch (variant.value) {\n case 'error':\n return 'text-danger-100'\n case 'warning':\n return 'text-warning-background'\n case 'info':\n default:\n return 'text-text-primary'\n }\n})\n\nconst iconColorClass = computed(() => textClasses.value)\n\nconst iconClass = computed(() => {\n if (props.badge.icon) {\n return props.badge.icon\n }\n switch (variant.value) {\n case 'error':\n return 'pi pi-exclamation-circle'\n case 'warning':\n return 'icon-[lucide--triangle-alert]'\n case 'info':\n default:\n return undefined\n }\n})\n\nconst clickableClasses = 'cursor-pointer transition-opacity hover:opacity-80'\n\nconst dotClasses = computed(() => {\n switch (variant.value) {\n case 'error':\n return 'bg-danger-100'\n case 'warning':\n return 'bg-gold-600'\n case 'info':\n default:\n return 'bg-slate-100'\n }\n})\n\nconst popoverPt = computed(() => ({\n root: {\n class: cn('absolute z-50')\n },\n content: {\n class: cn(\n 'mt-1 rounded-lg',\n 'bg-base-background',\n 'text-base-foreground',\n 'shadow-lg',\n 'border border-border-default'\n )\n }\n}))\n</script>\n","<template>\n <Button\n :size\n :loading=\"isLoading\"\n :disabled=\"isPolling\"\n variant=\"primary\"\n :style=\"\n variant === 'gradient'\n ? {\n background: 'var(--color-subscription-button-gradient)',\n color: 'var(--color-white)'\n }\n : undefined\n \"\n :class=\"cn('font-bold', fluid && 'w-full')\"\n @click=\"handleSubscribe\"\n >\n {{ label || $t('subscription.required.subscribe') }}\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport { onBeforeUnmount, ref, watch } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { isCloud } from '@/platform/distribution/types'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst {\n size = 'lg',\n fluid = true,\n variant = 'default',\n label\n} = defineProps<{\n label?: string\n size?: 'sm' | 'lg'\n variant?: 'default' | 'gradient'\n fluid?: boolean\n}>()\n\nconst emit = defineEmits<{\n subscribed: []\n}>()\n\nconst { subscribe, isActiveSubscription, fetchStatus, showSubscriptionDialog } =\n useSubscription()\n\nconst telemetry = useTelemetry()\n\nconst isLoading = ref(false)\nconst isPolling = ref(false)\nlet pollInterval: number | null = null\nconst isAwaitingStripeSubscription = ref(false)\n\nconst POLL_INTERVAL_MS = 3000 // Poll every 3 seconds\nconst MAX_POLL_DURATION_MS = 5 * 60 * 1000 // Stop polling after 5 minutes\n\nconst startPollingSubscriptionStatus = () => {\n isPolling.value = true\n isLoading.value = true\n\n const startTime = Date.now()\n\n const poll = async () => {\n try {\n if (Date.now() - startTime > MAX_POLL_DURATION_MS) {\n stopPolling()\n return\n }\n\n await fetchStatus()\n\n if (isActiveSubscription.value) {\n stopPolling()\n telemetry?.trackMonthlySubscriptionSucceeded()\n emit('subscribed')\n }\n } catch (error) {\n console.error(\n '[SubscribeButton] Error polling subscription status:',\n error\n )\n }\n }\n\n void poll()\n pollInterval = window.setInterval(poll, POLL_INTERVAL_MS)\n}\n\nconst stopPolling = () => {\n if (pollInterval) {\n clearInterval(pollInterval)\n pollInterval = null\n }\n isPolling.value = false\n isLoading.value = false\n}\n\nwatch(\n [isAwaitingStripeSubscription, isActiveSubscription],\n ([awaiting, isActive]) => {\n if (isCloud && awaiting && isActive) {\n emit('subscribed')\n isAwaitingStripeSubscription.value = false\n }\n }\n)\n\nconst handleSubscribe = async () => {\n if (isCloud) {\n useTelemetry()?.trackSubscription('subscribe_clicked')\n isAwaitingStripeSubscription.value = true\n showSubscriptionDialog()\n return\n }\n\n isLoading.value = true\n try {\n await subscribe()\n\n startPollingSubscriptionStatus()\n } catch (error) {\n console.error('[SubscribeButton] Error initiating subscription:', error)\n isLoading.value = false\n }\n}\n\nonBeforeUnmount(() => {\n stopPolling()\n isAwaitingStripeSubscription.value = false\n})\n</script>\n","import { defineAsyncComponent } from 'vue'\nimport { useDialogService } from '@/services/dialogService'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst DIALOG_KEY = 'subscription-required'\n\nexport const useSubscriptionDialog = () => {\n const dialogService = useDialogService()\n const dialogStore = useDialogStore()\n\n function hide() {\n dialogStore.closeDialog({ key: DIALOG_KEY })\n }\n\n function show() {\n dialogService.showLayoutDialog({\n key: DIALOG_KEY,\n component: defineAsyncComponent(\n () =>\n import('@/platform/cloud/subscription/components/SubscriptionRequiredDialogContent.vue')\n ),\n props: {\n onClose: hide\n },\n dialogComponentProps: {\n style: 'width: min(1328px, 95vw); max-height: 90vh;',\n pt: {\n root: {\n class: 'rounded-2xl bg-transparent'\n },\n content: {\n class:\n '!p-0 rounded-2xl border border-border-default bg-base-background/60 backdrop-blur-md shadow-[0_25px_80px_rgba(5,6,12,0.45)]'\n }\n }\n }\n })\n }\n\n return {\n show,\n hide\n }\n}\n","<template>\n <Toast />\n</template>\n\n<script setup lang=\"ts\">\nimport Toast from 'primevue/toast'\nimport { useToast } from 'primevue/usetoast'\nimport { nextTick, watch } from 'vue'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useToastStore } from '@/platform/updates/common/toastStore'\n\nconst toast = useToast()\nconst toastStore = useToastStore()\nconst settingStore = useSettingStore()\n\nwatch(\n () => toastStore.messagesToAdd,\n (newMessages) => {\n if (newMessages.length === 0) {\n return\n }\n\n newMessages.forEach((message) => {\n toast.add(message)\n })\n toastStore.messagesToAdd = []\n },\n { deep: true }\n)\n\nwatch(\n () => toastStore.messagesToRemove,\n (messagesToRemove) => {\n if (messagesToRemove.length === 0) {\n return\n }\n\n messagesToRemove.forEach((message) => {\n toast.remove(message)\n })\n toastStore.messagesToRemove = []\n },\n { deep: true }\n)\n\nwatch(\n () => toastStore.removeAllRequested,\n (requested) => {\n if (requested) {\n toast.removeAllGroups()\n toastStore.removeAllRequested = false\n }\n }\n)\n\nfunction updateToastPosition() {\n const styleElement =\n document.getElementById('dynamic-toast-style') || createStyleElement()\n const rect = document\n .querySelector('.graph-canvas-container')\n ?.getBoundingClientRect()\n if (!rect) return\n\n styleElement.textContent = `\n .p-toast.p-component.p-toast-top-right {\n top: ${rect.top + 100}px !important;\n right: ${window.innerWidth - (rect.left + rect.width) + 20}px !important;\n z-index: 10000 !important;\n }\n `\n}\n\nfunction createStyleElement() {\n const style = document.createElement('style')\n style.id = 'dynamic-toast-style'\n document.head.appendChild(style)\n return style\n}\n\nwatch(\n () => settingStore.get('Comfy.UseNewMenu'),\n () => nextTick(updateToastPosition),\n { immediate: true }\n)\nwatch(\n () => settingStore.get('Comfy.Sidebar.Location'),\n () => nextTick(updateToastPosition),\n { immediate: true }\n)\n</script>\n","/**\n * Cross-browser async utilities for scheduling tasks during browser idle time\n * with proper fallbacks for browsers that don't support requestIdleCallback.\n *\n * Implementation based on:\n * https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts\n */\n\ninterface IdleDeadline {\n didTimeout: boolean\n timeRemaining(): number\n}\n\ninterface IDisposable {\n dispose(): void\n}\n\n/**\n * Internal implementation function that handles the actual scheduling logic.\n * Uses feature detection to determine whether to use native requestIdleCallback\n * or fall back to setTimeout-based implementation.\n */\nlet _runWhenIdle: (\n targetWindow: any,\n callback: (idle: IdleDeadline) => void,\n timeout?: number\n) => IDisposable\n\n/**\n * Execute the callback during the next browser idle period.\n * Falls back to setTimeout-based scheduling in browsers without native support.\n */\nexport let runWhenGlobalIdle: (\n callback: (idle: IdleDeadline) => void,\n timeout?: number\n ) => IDisposable\n\n // Self-invoking function to set up the idle callback implementation\n;(function () {\n const safeGlobal: any = globalThis\n\n if (\n typeof safeGlobal.requestIdleCallback !== 'function' ||\n typeof safeGlobal.cancelIdleCallback !== 'function'\n ) {\n // Fallback implementation for browsers without native support (e.g., Safari)\n _runWhenIdle = (_targetWindow, runner, _timeout?) => {\n setTimeout(() => {\n if (disposed) {\n return\n }\n\n // Simulate IdleDeadline - give 15ms window (one frame at ~64fps)\n const end = Date.now() + 15\n const deadline: IdleDeadline = {\n didTimeout: true,\n timeRemaining() {\n return Math.max(0, end - Date.now())\n }\n }\n\n runner(Object.freeze(deadline))\n })\n\n let disposed = false\n return {\n dispose() {\n if (disposed) {\n return\n }\n disposed = true\n }\n }\n }\n } else {\n // Native requestIdleCallback implementation\n _runWhenIdle = (targetWindow: typeof safeGlobal, runner, timeout?) => {\n const handle: number = targetWindow.requestIdleCallback(\n runner,\n typeof timeout === 'number' ? { timeout } : undefined\n )\n\n let disposed = false\n return {\n dispose() {\n if (disposed) {\n return\n }\n disposed = true\n targetWindow.cancelIdleCallback(handle)\n }\n }\n }\n }\n\n runWhenGlobalIdle = (runner, timeout) =>\n _runWhenIdle(globalThis, runner, timeout)\n})()\n","<template>\n <div\n v-show=\"workspaceState.focusMode\"\n class=\"fixed z-9999 flex flex-row no-drag top-0 right-0\"\n >\n <Button\n v-tooltip=\"{ value: $t('menu.showMenu'), showDelay: 300 }\"\n variant=\"muted-textonly\"\n size=\"lg\"\n :aria-label=\"$t('menu.showMenu')\"\n aria-live=\"assertive\"\n @click=\"exitFocusMode\"\n @contextmenu=\"showNativeSystemMenu\"\n >\n <i class=\"pi pi-bars\" />\n </Button>\n <div class=\"window-actions-spacer\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { watchEffect } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { app } from '@/scripts/app'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\nimport { showNativeSystemMenu } from '@/utils/envUtil'\n\nconst workspaceState = useWorkspaceStore()\nconst settingStore = useSettingStore()\nconst exitFocusMode = () => {\n workspaceState.focusMode = false\n}\n\nwatchEffect(() => {\n if (settingStore.get('Comfy.UseNewMenu') !== 'Disabled') {\n return\n }\n if (workspaceState.focusMode) {\n app.ui.menuContainer.style.display = 'none'\n } else {\n app.ui.menuContainer.style.display = 'block'\n }\n})\n</script>\n","<template>\n <div>\n <!--\n UnloadWindowConfirmDialog: This component does not render\n anything visible. It is used to confirm the user wants to\n close the window, and if they do, it will call the\n beforeunload event.\n -->\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted } from 'vue'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\n\nconst settingStore = useSettingStore()\nconst workflowStore = useWorkflowStore()\n\nconst handleBeforeUnload = (event: BeforeUnloadEvent) => {\n if (\n settingStore.get('Comfy.Window.UnloadConfirmation') &&\n workflowStore.modifiedWorkflows.length > 0\n ) {\n event.preventDefault()\n return true\n }\n return undefined\n}\n\nonMounted(() => {\n window.addEventListener('beforeunload', handleBeforeUnload)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', handleBeforeUnload)\n})\n</script>\n","<template>\n <div\n class=\"w-full h-full absolute top-0 left-0 z-999 pointer-events-none flex flex-col\"\n >\n <slot name=\"workflow-tabs\" />\n\n <div\n class=\"pointer-events-none flex flex-1 overflow-hidden\"\n :class=\"{\n 'flex-row': sidebarLocation === 'left',\n 'flex-row-reverse': sidebarLocation === 'right'\n }\"\n >\n <div class=\"side-toolbar-container\">\n <slot name=\"side-toolbar\" />\n </div>\n\n <Splitter\n :key=\"splitterRefreshKey\"\n class=\"bg-transparent pointer-events-none border-none flex-1 overflow-hidden\"\n :state-key=\"sidebarStateKey\"\n state-storage=\"local\"\n @resizestart=\"onResizestart\"\n >\n <!-- First panel: sidebar when left, properties when right -->\n <SplitterPanel\n v-if=\"\n !focusMode && (sidebarLocation === 'left' || rightSidePanelVisible)\n \"\n :class=\"\n sidebarLocation === 'left'\n ? cn(\n 'side-bar-panel bg-comfy-menu-bg pointer-events-auto',\n sidebarPanelVisible && 'min-w-78'\n )\n : 'bg-comfy-menu-bg pointer-events-auto'\n \"\n :min-size=\"sidebarLocation === 'left' ? 10 : 15\"\n :size=\"20\"\n :style=\"firstPanelStyle\"\n :role=\"sidebarLocation === 'left' ? 'complementary' : undefined\"\n :aria-label=\"\n sidebarLocation === 'left' ? t('sideToolbar.sidebar') : undefined\n \"\n >\n <slot\n v-if=\"sidebarLocation === 'left' && sidebarPanelVisible\"\n name=\"side-bar-panel\"\n />\n <slot\n v-else-if=\"sidebarLocation === 'right'\"\n name=\"right-side-panel\"\n />\n </SplitterPanel>\n\n <!-- Main panel (always present) -->\n <SplitterPanel :size=\"80\" class=\"flex flex-col\">\n <slot name=\"topmenu\" :sidebar-panel-visible />\n\n <Splitter\n class=\"bg-transparent pointer-events-none border-none splitter-overlay-bottom mr-1 mb-1 ml-1 flex-1\"\n layout=\"vertical\"\n :pt:gutter=\"\n cn(\n 'rounded-tl-lg rounded-tr-lg ',\n !(bottomPanelVisible && !focusMode) && 'hidden'\n )\n \"\n state-key=\"bottom-panel-splitter\"\n state-storage=\"local\"\n @resizestart=\"onResizestart\"\n >\n <SplitterPanel class=\"graph-canvas-panel relative\">\n <slot name=\"graph-canvas-panel\" />\n </SplitterPanel>\n <SplitterPanel\n v-show=\"bottomPanelVisible && !focusMode\"\n class=\"bottom-panel border border-(--p-panel-border-color) max-w-full overflow-x-auto bg-comfy-menu-bg pointer-events-auto rounded-lg\"\n >\n <slot name=\"bottom-panel\" />\n </SplitterPanel>\n </Splitter>\n </SplitterPanel>\n\n <!-- Last panel: properties when left, sidebar when right -->\n <SplitterPanel\n v-if=\"\n !focusMode && (sidebarLocation === 'right' || rightSidePanelVisible)\n \"\n :class=\"\n sidebarLocation === 'right'\n ? cn(\n 'side-bar-panel bg-comfy-menu-bg pointer-events-auto',\n sidebarPanelVisible && 'min-w-78'\n )\n : 'bg-comfy-menu-bg pointer-events-auto'\n \"\n :min-size=\"sidebarLocation === 'right' ? 10 : 15\"\n :size=\"20\"\n :style=\"lastPanelStyle\"\n :role=\"sidebarLocation === 'right' ? 'complementary' : undefined\"\n :aria-label=\"\n sidebarLocation === 'right' ? t('sideToolbar.sidebar') : undefined\n \"\n >\n <slot v-if=\"sidebarLocation === 'left'\" name=\"right-side-panel\" />\n <slot\n v-else-if=\"sidebarLocation === 'right' && sidebarPanelVisible\"\n name=\"side-bar-panel\"\n />\n </SplitterPanel>\n </Splitter>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { cn } from '@comfyorg/tailwind-utils'\nimport { storeToRefs } from 'pinia'\nimport Splitter from 'primevue/splitter'\nimport type { SplitterResizeStartEvent } from 'primevue/splitter'\nimport SplitterPanel from 'primevue/splitterpanel'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'\nimport { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'\nimport { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\n\nconst workspaceStore = useWorkspaceStore()\nconst settingStore = useSettingStore()\nconst rightSidePanelStore = useRightSidePanelStore()\nconst sidebarTabStore = useSidebarTabStore()\nconst { t } = useI18n()\nconst sidebarLocation = computed<'left' | 'right'>(() =>\n settingStore.get('Comfy.Sidebar.Location')\n)\n\nconst unifiedWidth = computed(() =>\n settingStore.get('Comfy.Sidebar.UnifiedWidth')\n)\n\nconst { focusMode } = storeToRefs(workspaceStore)\n\nconst { activeSidebarTabId, activeSidebarTab } = storeToRefs(sidebarTabStore)\nconst { bottomPanelVisible } = storeToRefs(useBottomPanelStore())\nconst { isOpen: rightSidePanelVisible } = storeToRefs(rightSidePanelStore)\n\nconst sidebarPanelVisible = computed(() => activeSidebarTab.value !== null)\n\nconst sidebarStateKey = computed(() => {\n return unifiedWidth.value\n ? 'unified-sidebar'\n : // When no tab is active, use a default key to maintain state\n (activeSidebarTabId.value ?? 'default-sidebar')\n})\n\n/**\n * Avoid triggering default behaviors during drag-and-drop, such as text selection.\n */\nfunction onResizestart({ originalEvent: event }: SplitterResizeStartEvent) {\n event.preventDefault()\n}\n\n/*\n * Force refresh the splitter when right panel visibility or sidebar location changes\n * to recalculate the width and panel order\n */\nconst splitterRefreshKey = computed(() => {\n return `main-splitter${rightSidePanelVisible.value ? '-with-right-panel' : ''}-${sidebarLocation.value}`\n})\n\nconst firstPanelStyle = computed(() => {\n if (sidebarLocation.value === 'left') {\n return { display: sidebarPanelVisible.value ? 'flex' : 'none' }\n }\n return undefined\n})\n\nconst lastPanelStyle = computed(() => {\n if (sidebarLocation.value === 'right') {\n return { display: sidebarPanelVisible.value ? 'flex' : 'none' }\n }\n return undefined\n})\n</script>\n\n<style scoped>\n:deep(.p-splitter-gutter) {\n pointer-events: auto;\n}\n\n:deep(.p-splitter-gutter:hover),\n:deep(.p-splitter-gutter[data-p-gutter-resizing='true']) {\n transition: background-color 0.2s ease 300ms;\n background-color: var(--p-primary-color);\n}\n\n/* Hide sidebar gutter when sidebar is not visible */\n:deep(.side-bar-panel[style*='display: none'] + .p-splitter-gutter),\n:deep(.p-splitter-gutter + .side-bar-panel[style*='display: none']) {\n display: none;\n}\n\n.splitter-overlay-bottom :deep(.p-splitter-gutter) {\n transform: translateY(5px);\n}\n</style>\n","/**\n * Build a tooltip configuration object compatible with v-tooltip.\n * Consumers pass the translated text value.\n */\nexport const buildTooltipConfig = (value: string) => ({\n value,\n showDelay: 300,\n hideDelay: 0,\n pt: {\n text: {\n class:\n 'border-node-component-tooltip-border bg-node-component-tooltip-surface text-node-component-tooltip border rounded-md px-2 py-1 text-xs leading-none shadow-none'\n },\n arrow: {\n class: 'border-t-node-component-tooltip-border'\n }\n }\n})\n","import { defineAsyncComponent } from 'vue'\n\nimport { isCloud } from '@/platform/distribution/types'\n\nexport default isCloud && window.__CONFIG__?.subscription_required\n ? defineAsyncComponent(() => import('./CloudRunButtonWrapper.vue'))\n : defineAsyncComponent(() => import('./ComfyQueueButton.vue'))\n","<template>\n <div class=\"flex h-full items-center\">\n <div\n v-if=\"isDragging && !isDocked\"\n :class=\"actionbarClass\"\n @mouseenter=\"onMouseEnterDropZone\"\n @mouseleave=\"onMouseLeaveDropZone\"\n >\n {{ t('actionbar.dockToTop') }}\n </div>\n\n <Panel\n class=\"pointer-events-auto\"\n :style=\"style\"\n :class=\"panelClass\"\n :pt=\"{\n header: { class: 'hidden' },\n content: { class: isDocked ? 'p-0' : 'p-1' }\n }\"\n >\n <div ref=\"panelRef\" class=\"flex items-center select-none gap-2\">\n <span\n ref=\"dragHandleRef\"\n :class=\"\n cn(\n 'drag-handle cursor-grab w-3 h-max',\n isDragging && 'cursor-grabbing'\n )\n \"\n />\n <Suspense @resolve=\"comfyRunButtonResolved\">\n <ComfyRunButton />\n </Suspense>\n <Button\n v-tooltip.bottom=\"cancelJobTooltipConfig\"\n variant=\"destructive\"\n size=\"icon\"\n :disabled=\"isExecutionIdle\"\n :aria-label=\"t('menu.interrupt')\"\n @click=\"cancelCurrentJob\"\n >\n <i class=\"icon-[lucide--x] size-4\" />\n </Button>\n </div>\n </Panel>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {\n useDraggable,\n useEventListener,\n useLocalStorage,\n watchDebounced\n} from '@vueuse/core'\nimport { clamp } from 'es-toolkit/compat'\nimport { storeToRefs } from 'pinia'\nimport Panel from 'primevue/panel'\nimport { computed, nextTick, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { buildTooltipConfig } from '@/composables/useTooltipConfig'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { cn } from '@/utils/tailwindUtil'\n\nimport ComfyRunButton from './ComfyRunButton'\n\nconst settingsStore = useSettingStore()\nconst commandStore = useCommandStore()\nconst { t } = useI18n()\nconst { isIdle: isExecutionIdle } = storeToRefs(useExecutionStore())\n\nconst position = computed(() => settingsStore.get('Comfy.UseNewMenu'))\nconst visible = computed(() => position.value !== 'Disabled')\n\nconst tabContainer = document.querySelector('.workflow-tabs-container')\nconst panelRef = ref<HTMLElement | null>(null)\nconst dragHandleRef = ref<HTMLElement | null>(null)\nconst isDocked = useLocalStorage('Comfy.MenuPosition.Docked', true)\nconst storedPosition = useLocalStorage('Comfy.MenuPosition.Floating', {\n x: 0,\n y: 0\n})\nconst { x, y, style, isDragging } = useDraggable(panelRef, {\n initialValue: { x: 0, y: 0 },\n handle: dragHandleRef,\n containerElement: document.body,\n onMove: (event) => {\n // Prevent dragging the menu over the top of the tabs\n const minY = tabContainer?.getBoundingClientRect().bottom ?? 40\n if (event.y < minY) {\n event.y = minY\n }\n }\n})\n\n// Update storedPosition when x or y changes\nwatchDebounced(\n [x, y],\n ([newX, newY]) => {\n storedPosition.value = { x: newX, y: newY }\n },\n { debounce: 300 }\n)\n\n// Set initial position to bottom center\nconst setInitialPosition = () => {\n if (panelRef.value) {\n const screenWidth = window.innerWidth\n const screenHeight = window.innerHeight\n const menuWidth = panelRef.value.offsetWidth\n const menuHeight = panelRef.value.offsetHeight\n\n if (menuWidth === 0 || menuHeight === 0) {\n return\n }\n\n // Check if stored position exists and is within bounds\n if (storedPosition.value.x !== 0 || storedPosition.value.y !== 0) {\n // Ensure stored position is within screen bounds\n x.value = clamp(storedPosition.value.x, 0, screenWidth - menuWidth)\n y.value = clamp(storedPosition.value.y, 0, screenHeight - menuHeight)\n captureLastDragState()\n return\n }\n\n // If no stored position or current position, set to bottom center\n if (x.value === 0 && y.value === 0) {\n x.value = clamp((screenWidth - menuWidth) / 2, 0, screenWidth - menuWidth)\n y.value = clamp(\n screenHeight - menuHeight - 10,\n 0,\n screenHeight - menuHeight\n )\n captureLastDragState()\n }\n }\n}\n\n//The ComfyRunButton is a dynamic import. Which means it will not be loaded onMount in this component.\n//So we must use suspense resolve to ensure that is has loaded and updated the DOM before calling setInitialPosition()\nasync function comfyRunButtonResolved() {\n await nextTick()\n setInitialPosition()\n}\n\nwatch(visible, async (newVisible) => {\n if (newVisible) {\n await nextTick(setInitialPosition)\n }\n})\n\n/**\n * Track run button handle drag start using mousedown on the drag handle.\n */\nuseEventListener(dragHandleRef, 'mousedown', () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'actionbar_run_handle_drag_start'\n })\n})\n\nconst lastDragState = ref({\n x: x.value,\n y: y.value,\n windowWidth: window.innerWidth,\n windowHeight: window.innerHeight\n})\nconst captureLastDragState = () => {\n lastDragState.value = {\n x: x.value,\n y: y.value,\n windowWidth: window.innerWidth,\n windowHeight: window.innerHeight\n }\n}\nwatch(\n isDragging,\n (newIsDragging) => {\n if (!newIsDragging) {\n // Stop dragging\n captureLastDragState()\n }\n },\n { immediate: true }\n)\n\nconst adjustMenuPosition = () => {\n if (panelRef.value) {\n const screenWidth = window.innerWidth\n const screenHeight = window.innerHeight\n const menuWidth = panelRef.value.offsetWidth\n const menuHeight = panelRef.value.offsetHeight\n\n // Calculate distances to all edges\n const distanceLeft = lastDragState.value.x\n const distanceRight =\n lastDragState.value.windowWidth - (lastDragState.value.x + menuWidth)\n const distanceTop = lastDragState.value.y\n const distanceBottom =\n lastDragState.value.windowHeight - (lastDragState.value.y + menuHeight)\n\n // Find the smallest distance to determine which edge to anchor to\n const distances = [\n { edge: 'left', distance: distanceLeft },\n { edge: 'right', distance: distanceRight },\n { edge: 'top', distance: distanceTop },\n { edge: 'bottom', distance: distanceBottom }\n ]\n const closestEdge = distances.reduce((min, curr) =>\n curr.distance < min.distance ? curr : min\n )\n\n // Calculate vertical position as a percentage of screen height\n const verticalRatio =\n lastDragState.value.y / lastDragState.value.windowHeight\n const horizontalRatio =\n lastDragState.value.x / lastDragState.value.windowWidth\n\n // Apply positioning based on closest edge\n if (closestEdge.edge === 'left') {\n x.value = closestEdge.distance // Maintain exact distance from left\n y.value = verticalRatio * screenHeight\n } else if (closestEdge.edge === 'right') {\n x.value = screenWidth - menuWidth - closestEdge.distance // Maintain exact distance from right\n y.value = verticalRatio * screenHeight\n } else if (closestEdge.edge === 'top') {\n x.value = horizontalRatio * screenWidth\n y.value = closestEdge.distance // Maintain exact distance from top\n } else {\n // bottom\n x.value = horizontalRatio * screenWidth\n y.value = screenHeight - menuHeight - closestEdge.distance // Maintain exact distance from bottom\n }\n\n // Ensure the menu stays within the screen bounds\n x.value = clamp(x.value, 0, screenWidth - menuWidth)\n y.value = clamp(y.value, 0, screenHeight - menuHeight)\n }\n}\n\nuseEventListener(window, 'resize', adjustMenuPosition)\n\n// Drop zone state\nconst isMouseOverDropZone = ref(false)\n\n// Mouse event handlers for self-contained drop zone\nconst onMouseEnterDropZone = () => {\n if (isDragging.value) {\n isMouseOverDropZone.value = true\n }\n}\n\nconst onMouseLeaveDropZone = () => {\n if (isDragging.value) {\n isMouseOverDropZone.value = false\n }\n}\n\n// Handle drag state changes\nwatch(isDragging, (dragging) => {\n if (dragging) {\n // Starting to drag - undock if docked\n if (isDocked.value) {\n isDocked.value = false\n }\n } else {\n // Stopped dragging - dock if mouse is over drop zone\n if (isMouseOverDropZone.value) {\n isDocked.value = true\n }\n // Reset drop zone state\n isMouseOverDropZone.value = false\n }\n})\n\nconst cancelJobTooltipConfig = computed(() =>\n buildTooltipConfig(t('menu.interrupt'))\n)\n\nconst cancelCurrentJob = async () => {\n if (isExecutionIdle.value) return\n await commandStore.execute('Comfy.Interrupt')\n}\n\nconst actionbarClass = computed(() =>\n cn(\n 'w-[200px] border-dashed border-blue-500 opacity-80',\n 'm-1.5 flex items-center justify-center self-stretch',\n 'rounded-md before:w-50 before:-ml-50 before:h-full',\n 'pointer-events-auto',\n isMouseOverDropZone.value &&\n 'border-[3px] opacity-100 scale-105 shadow-[0_0_20px] shadow-blue-500'\n )\n)\nconst panelClass = computed(() =>\n cn(\n 'actionbar pointer-events-auto z-1300',\n isDragging.value && 'select-none pointer-events-none',\n isDocked.value\n ? 'p-0 static border-none bg-transparent'\n : 'fixed shadow-interface'\n )\n)\n</script>\n","import type { MenuItem } from 'primevue/menuitem'\nimport type { ComputedRef, Ref } from 'vue'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'\nimport {\n useWorkflowBookmarkStore,\n useWorkflowStore\n} from '@/platform/workflow/management/stores/workflowStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useSubgraphStore } from '@/stores/subgraphStore'\n\ninterface WorkflowActionsMenuOptions {\n /** Whether this is the root workflow level. Defaults to true. */\n isRoot?: boolean\n /** Whether to include the delete workflow action. Defaults to true. */\n includeDelete?: boolean\n /** Override the workflow to operate on. If not provided, uses activeWorkflow. */\n workflow?: Ref<ComfyWorkflow | null> | ComputedRef<ComfyWorkflow | null>\n}\n\nexport function useWorkflowActionsMenu(\n startRename: () => void,\n options: WorkflowActionsMenuOptions = {}\n) {\n const { isRoot = true, includeDelete = true, workflow } = options\n const { t } = useI18n()\n const workflowStore = useWorkflowStore()\n const workflowService = useWorkflowService()\n const bookmarkStore = useWorkflowBookmarkStore()\n const commandStore = useCommandStore()\n const subgraphStore = useSubgraphStore()\n\n const targetWorkflow = computed(\n () => workflow?.value ?? workflowStore.activeWorkflow\n )\n\n /** Switch to the target workflow tab if it's not already active */\n const ensureWorkflowActive = async (wf: ComfyWorkflow | null) => {\n if (!wf || wf === workflowStore.activeWorkflow) return\n await workflowService.openWorkflow(wf)\n }\n\n const menuItems = computed<MenuItem[]>(() => {\n const workflow = targetWorkflow.value\n const isBlueprint = workflow\n ? subgraphStore.isSubgraphBlueprint(workflow)\n : false\n\n const items: MenuItem[] = []\n\n const addItem = (\n label: string,\n icon: string,\n command: () => void,\n visible = true,\n disabled = false,\n separator = false\n ) => {\n if (!visible) return\n if (separator) items.push({ separator: true })\n items.push({ label, icon, command, disabled })\n }\n\n addItem(\n t('g.rename'),\n 'pi pi-pencil',\n async () => {\n await ensureWorkflowActive(targetWorkflow.value)\n startRename()\n },\n true,\n isRoot && !workflow?.isPersisted\n )\n\n addItem(\n t('breadcrumbsMenu.duplicate'),\n 'pi pi-copy',\n async () => {\n if (workflow) {\n await workflowService.duplicateWorkflow(workflow)\n }\n },\n isRoot && !isBlueprint\n )\n\n addItem(\n t('menuLabels.Save'),\n 'pi pi-save',\n async () => {\n await ensureWorkflowActive(workflow)\n await commandStore.execute('Comfy.SaveWorkflow')\n },\n isRoot,\n false,\n true\n )\n\n addItem(\n t('menuLabels.Save As'),\n 'pi pi-save',\n async () => {\n await ensureWorkflowActive(workflow)\n await commandStore.execute('Comfy.SaveWorkflowAs')\n },\n isRoot\n )\n\n addItem(\n bookmarkStore.isBookmarked(workflow?.path ?? '')\n ? t('tabMenu.removeFromBookmarks')\n : t('tabMenu.addToBookmarks'),\n 'pi pi-bookmark' +\n (bookmarkStore.isBookmarked(workflow?.path ?? '') ? '-fill' : ''),\n async () => {\n if (workflow?.path) {\n await bookmarkStore.toggleBookmarked(workflow.path)\n }\n },\n isRoot,\n workflow?.isTemporary ?? false\n )\n\n addItem(\n t('menuLabels.Export'),\n 'pi pi-download',\n async () => {\n await ensureWorkflowActive(workflow)\n await commandStore.execute('Comfy.ExportWorkflow')\n },\n isRoot\n )\n\n addItem(\n t('menuLabels.Export (API)'),\n 'pi pi-download',\n async () => {\n await ensureWorkflowActive(workflow)\n await commandStore.execute('Comfy.ExportWorkflowAPI')\n },\n isRoot\n )\n\n addItem(\n t('breadcrumbsMenu.clearWorkflow'),\n 'pi pi-trash',\n async () => {\n await ensureWorkflowActive(workflow)\n await commandStore.execute('Comfy.ClearWorkflow')\n },\n true,\n false,\n true\n )\n\n addItem(\n t('subgraphStore.publish'),\n 'pi pi-upload',\n async () => {\n if (workflow) {\n await workflowService.saveWorkflowAs(workflow)\n }\n },\n isRoot && isBlueprint,\n false,\n true\n )\n\n addItem(\n isBlueprint\n ? t('breadcrumbsMenu.deleteBlueprint')\n : t('breadcrumbsMenu.deleteWorkflow'),\n 'pi pi-times',\n async () => {\n if (workflow) {\n await workflowService.deleteWorkflow(workflow)\n }\n },\n isRoot && includeDelete,\n false,\n true\n )\n\n return items\n })\n\n return {\n menuItems\n }\n}\n","import { unref } from 'vue'\nimport type { MaybeRef } from 'vue'\n\nimport type {\n LGraph,\n LGraphNode,\n Subgraph\n} from '@/lib/litegraph/src/litegraph'\nimport type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'\nimport { collectAllNodes } from '@/utils/graphTraversalUtil'\n\nexport type NodeDefLookup = Record<string, ComfyNodeDefImpl | undefined>\n\nconst isNodeMissingDefinition = (\n node: LGraphNode,\n nodeDefsByName: NodeDefLookup\n) => {\n const nodeName = node?.type\n if (!nodeName) return false\n return !nodeDefsByName[nodeName]\n}\n\nexport const collectMissingNodes = (\n graph: LGraph | Subgraph | null | undefined,\n nodeDefsByName: MaybeRef<NodeDefLookup>\n): LGraphNode[] => {\n if (!graph) return []\n const lookup = unref(nodeDefsByName)\n return collectAllNodes(graph, (node) => isNodeMissingDefinition(node, lookup))\n}\n\nexport const graphHasMissingNodes = (\n graph: LGraph | Subgraph | null | undefined,\n nodeDefsByName: MaybeRef<NodeDefLookup>\n) => {\n return collectMissingNodes(graph, nodeDefsByName).length > 0\n}\n","<template>\n <a\n ref=\"wrapperRef\"\n v-tooltip.bottom=\"{\n value: tooltipText,\n showDelay: 512\n }\"\n draggable=\"false\"\n href=\"#\"\n class=\"p-breadcrumb-item-link h-8 cursor-pointer px-2\"\n :class=\"{\n 'flex items-center gap-1': isActive,\n 'p-breadcrumb-item-link-menu-visible': menu?.overlayVisible,\n 'p-breadcrumb-item-link-icon-visible': isActive,\n 'active-breadcrumb-item': isActive\n }\"\n @click=\"handleClick\"\n >\n <i\n v-if=\"hasMissingNodes && isRoot\"\n class=\"icon-[lucide--triangle-alert] text-warning-background\"\n />\n <span class=\"p-breadcrumb-item-label px-2\">{{ item.label }}</span>\n <Tag v-if=\"item.isBlueprint\" value=\"Blueprint\" severity=\"primary\" />\n <i v-if=\"isActive\" class=\"pi pi-angle-down text-[10px]\"></i>\n </a>\n <Menu\n v-if=\"isActive || isRoot\"\n ref=\"menu\"\n :model=\"menuItems\"\n :popup=\"true\"\n :pt=\"{\n root: {\n style: 'background-color: var(--comfy-menu-bg)'\n },\n itemLink: {\n class: 'py-2'\n }\n }\"\n />\n <InputText\n v-if=\"isEditing\"\n ref=\"itemInputRef\"\n v-model=\"itemLabel\"\n class=\"fixed z-10000 px-2 py-2 text-[.8rem]\"\n @blur=\"inputBlur(false)\"\n @click.stop\n @keydown.enter=\"inputBlur(true)\"\n @keydown.esc=\"inputBlur(false)\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\nimport type { MenuState } from 'primevue/menu'\nimport Menu from 'primevue/menu'\nimport type { MenuItem } from 'primevue/menuitem'\nimport Tag from 'primevue/tag'\nimport { computed, nextTick, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useWorkflowActionsMenu } from '@/composables/useWorkflowActionsMenu'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport {\n ComfyWorkflow,\n useWorkflowStore\n} from '@/platform/workflow/management/stores/workflowStore'\nimport { app } from '@/scripts/app'\nimport { useDialogService } from '@/services/dialogService'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useNodeDefStore } from '@/stores/nodeDefStore'\nimport { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'\nimport { appendJsonExt } from '@/utils/formatUtil'\nimport { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'\n\ninterface Props {\n item: MenuItem\n isActive?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n isActive: false\n})\n\nconst nodeDefStore = useNodeDefStore()\nconst hasMissingNodes = computed(() =>\n graphHasMissingNodes(app.rootGraph, nodeDefStore.nodeDefsByName)\n)\n\nconst { t } = useI18n()\nconst menu = ref<InstanceType<typeof Menu> & MenuState>()\nconst dialogService = useDialogService()\nconst workflowStore = useWorkflowStore()\nconst workflowService = useWorkflowService()\nconst isEditing = ref(false)\nconst itemLabel = ref<string>()\nconst itemInputRef = ref<{ $el?: HTMLInputElement }>()\nconst wrapperRef = ref<HTMLAnchorElement>()\n\nconst rename = async (\n newName: string | null | undefined,\n initialName: string\n) => {\n if (newName && newName !== initialName) {\n // Synchronize the node titles with the new name\n props.item.updateTitle?.(newName)\n\n if (workflowStore.activeSubgraph) {\n workflowStore.activeSubgraph.name = newName\n } else if (workflowStore.activeWorkflow) {\n try {\n await workflowService.renameWorkflow(\n workflowStore.activeWorkflow,\n ComfyWorkflow.basePath + appendJsonExt(newName)\n )\n } catch (error) {\n console.error(error)\n dialogService.showErrorDialog(error)\n return\n }\n }\n\n // Force the navigation stack to recompute the labels\n // TODO: investigate if there is a better way to do this\n const navigationStore = useSubgraphNavigationStore()\n navigationStore.restoreState(navigationStore.exportState())\n }\n}\n\nconst isRoot = props.item.key === 'root'\n\nconst tooltipText = computed(() => {\n if (hasMissingNodes.value && isRoot) {\n return t('breadcrumbsMenu.missingNodesWarning')\n }\n return props.item.label\n})\n\nconst startRename = async () => {\n // Check if element is hidden (collapsed breadcrumb)\n // When collapsed, root item is hidden via CSS display:none, so use rename command\n if (isRoot && wrapperRef.value?.offsetParent === null) {\n await useCommandStore().execute('Comfy.RenameWorkflow')\n return\n }\n\n isEditing.value = true\n itemLabel.value = props.item.label as string\n void nextTick(() => {\n if (itemInputRef.value?.$el) {\n itemInputRef.value.$el.focus()\n itemInputRef.value.$el.select()\n if (wrapperRef.value) {\n itemInputRef.value.$el.style.width = `${Math.max(200, wrapperRef.value.offsetWidth)}px`\n }\n }\n })\n}\n\nconst { menuItems } = useWorkflowActionsMenu(startRename, { isRoot })\n\nconst handleClick = (event: MouseEvent) => {\n if (isEditing.value) {\n return\n }\n\n if (event.detail === 1) {\n if (props.isActive) {\n menu.value?.toggle(event)\n } else {\n props.item.command?.({ item: props.item, originalEvent: event })\n }\n } else if (props.isActive && event.detail === 2) {\n menu.value?.hide()\n event.stopPropagation()\n event.preventDefault()\n startRename()\n }\n}\n\nconst inputBlur = async (doRename: boolean) => {\n if (doRename) {\n await rename(itemLabel.value, props.item.label as string)\n }\n\n isEditing.value = false\n}\n\nconst toggleMenu = (event: MouseEvent) => {\n menu.value?.toggle(event)\n}\n\ndefineExpose({\n toggleMenu\n})\n</script>\n\n<style scoped>\n@reference '../../assets/css/style.css';\n\n.p-breadcrumb-item-link,\n.p-breadcrumb-item-icon {\n @apply select-none;\n}\n\n.p-breadcrumb-item-link {\n @apply overflow-hidden;\n}\n\n.p-breadcrumb-item-label {\n @apply whitespace-nowrap text-ellipsis overflow-hidden;\n}\n\n.active-breadcrumb-item {\n color: var(--text-primary);\n}\n</style>\n","import { useMutationObserver, useResizeObserver } from '@vueuse/core'\nimport { debounce } from 'es-toolkit/compat'\nimport { readonly, ref } from 'vue'\n\n/**\n * Observes an element for overflow changes and optionally debounces the check\n * @param element - The element to observe\n * @param options - The options for the observer\n * @param options.debounceTime - The time to debounce the check in milliseconds\n * @param options.useMutationObserver - Whether to use a mutation observer to check for overflow\n * @param options.useResizeObserver - Whether to use a resize observer to check for overflow\n * @returns An object containing the isOverflowing state and the checkOverflow function to manually trigger\n */\nexport const useOverflowObserver = (\n element: HTMLElement,\n options?: {\n debounceTime?: number\n useMutationObserver?: boolean\n useResizeObserver?: boolean\n onCheck?: (isOverflowing: boolean) => void\n }\n) => {\n options = {\n debounceTime: 25,\n useMutationObserver: true,\n useResizeObserver: true,\n ...options\n }\n\n const isOverflowing = ref(false)\n const disposeFns: (() => void)[] = []\n const disposed = ref(false)\n\n const checkOverflowFn = () => {\n isOverflowing.value = element.scrollWidth > element.clientWidth\n options.onCheck?.(isOverflowing.value)\n }\n\n const checkOverflow = options.debounceTime\n ? debounce(checkOverflowFn, options.debounceTime)\n : checkOverflowFn\n\n if (options.useMutationObserver) {\n disposeFns.push(\n useMutationObserver(element, checkOverflow, {\n subtree: true,\n childList: true\n }).stop\n )\n }\n if (options.useResizeObserver) {\n disposeFns.push(useResizeObserver(element, checkOverflow).stop)\n }\n\n return {\n isOverflowing: readonly(isOverflowing),\n disposed: readonly(disposed),\n checkOverflow,\n dispose: () => {\n disposed.value = true\n disposeFns.forEach((fn) => fn())\n }\n }\n}\n","<template>\n <div\n class=\"subgraph-breadcrumb flex w-auto drop-shadow-[var(--interface-panel-drop-shadow)]\"\n :class=\"{\n 'subgraph-breadcrumb-collapse': collapseTabs,\n 'subgraph-breadcrumb-overflow': overflowingTabs\n }\"\n :style=\"{\n '--p-breadcrumb-gap': `0px`,\n '--p-breadcrumb-item-margin': `${ITEM_GAP / 2}px`,\n '--p-breadcrumb-item-min-width': `${MIN_WIDTH}px`,\n '--p-breadcrumb-item-padding': `${ITEM_PADDING}px`,\n '--p-breadcrumb-icon-width': `${ICON_WIDTH}px`\n }\"\n >\n <Button\n class=\"context-menu-button pointer-events-auto h-8 w-8 shrink-0 border border-transparent bg-transparent p-0 transition-all hover:rounded-lg hover:border-interface-stroke hover:bg-comfy-menu-bg\"\n icon=\"pi pi-bars\"\n text\n severity=\"secondary\"\n size=\"small\"\n @click=\"handleMenuClick\"\n />\n <Button\n v-if=\"isInSubgraph\"\n class=\"back-button pointer-events-auto h-8 w-8 shrink-0 border border-transparent bg-transparent p-0 transition-all hover:rounded-lg hover:border-interface-stroke hover:bg-comfy-menu-bg\"\n text\n severity=\"secondary\"\n size=\"small\"\n @click=\"handleBackClick\"\n >\n <i class=\"icon-[lucide--undo-2]\" />\n </Button>\n <Breadcrumb\n ref=\"breadcrumbRef\"\n class=\"w-fit rounded-lg p-0\"\n :class=\"{ hidden: !isInSubgraph }\"\n :model=\"items\"\n :pt=\"{ item: { class: 'pointer-events-auto' } }\"\n :aria-label=\"$t('g.graphNavigation')\"\n >\n <template #item=\"{ item }\">\n <SubgraphBreadcrumbItem\n :ref=\"(el) => setItemRef(item, el)\"\n :item=\"item\"\n :is-active=\"item.key === activeItemKey\"\n />\n </template>\n <template #separator\n ><span style=\"transform: scale(1.5)\"> / </span></template\n >\n </Breadcrumb>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Breadcrumb from 'primevue/breadcrumb'\nimport Button from 'primevue/button'\nimport type { MenuItem } from 'primevue/menuitem'\nimport { computed, onUpdated, ref, watch } from 'vue'\n\nimport SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbItem.vue'\nimport { useOverflowObserver } from '@/composables/element/useOverflowObserver'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'\nimport { useSubgraphStore } from '@/stores/subgraphStore'\nimport { forEachSubgraphNode } from '@/utils/graphTraversalUtil'\n\nconst MIN_WIDTH = 28\nconst ITEM_GAP = 8\nconst ITEM_PADDING = 8\nconst ICON_WIDTH = 20\n\nconst workflowStore = useWorkflowStore()\nconst navigationStore = useSubgraphNavigationStore()\nconst breadcrumbRef = ref<InstanceType<typeof Breadcrumb>>()\nconst rootItemRef = ref<InstanceType<typeof SubgraphBreadcrumbItem>>()\nconst setItemRef = (item: MenuItem, el: unknown) => {\n if (item.key === 'root') {\n rootItemRef.value = el as InstanceType<typeof SubgraphBreadcrumbItem>\n }\n}\nconst workflowName = computed(() => workflowStore.activeWorkflow?.filename)\nconst isBlueprint = computed(() =>\n useSubgraphStore().isSubgraphBlueprint(workflowStore.activeWorkflow)\n)\nconst collapseTabs = ref(false)\nconst overflowingTabs = ref(false)\n\nconst isInSubgraph = computed(() => navigationStore.navigationStack.length > 0)\n\nconst home = computed(() => ({\n label: workflowName.value,\n icon: 'pi pi-home',\n key: 'root',\n isBlueprint: isBlueprint.value,\n command: () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'breadcrumb_subgraph_root_selected'\n })\n const canvas = useCanvasStore().getCanvas()\n if (!canvas.graph) throw new TypeError('Canvas has no graph')\n\n canvas.setGraph(canvas.graph.rootGraph)\n }\n}))\n\nconst items = computed(() => {\n const items = navigationStore.navigationStack.map<MenuItem>((subgraph) => ({\n label: subgraph.name,\n key: `subgraph-${subgraph.id}`,\n command: () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'breadcrumb_subgraph_item_selected'\n })\n const canvas = useCanvasStore().getCanvas()\n if (!canvas.graph) throw new TypeError('Canvas has no graph')\n\n canvas.setGraph(subgraph)\n },\n updateTitle: (title: string) => {\n const rootGraph = useCanvasStore().getCanvas().graph?.rootGraph\n if (!rootGraph) return\n\n forEachSubgraphNode(rootGraph, subgraph.id, (node) => {\n node.title = title\n })\n }\n }))\n\n return [home.value, ...items]\n})\n\nconst activeItemKey = computed(() => items.value.at(-1)?.key)\n\nconst handleMenuClick = (event: MouseEvent) => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'breadcrumb_subgraph_menu_selected'\n })\n rootItemRef.value?.toggleMenu(event)\n}\n\nconst handleBackClick = () => {\n void useCommandStore().execute('Comfy.Graph.ExitSubgraph')\n}\n\nconst breadcrumbElement = computed(() => {\n if (!breadcrumbRef.value) return null\n\n const el = (breadcrumbRef.value as unknown as { $el: HTMLElement }).$el\n const list = el?.querySelector('.p-breadcrumb-list') as HTMLElement\n return list\n})\n\n// Check for overflow on breadcrumb items and collapse/expand the breadcrumb to fit\nlet overflowObserver: ReturnType<typeof useOverflowObserver> | undefined\nwatch(breadcrumbElement, (el) => {\n overflowObserver?.dispose()\n overflowObserver = undefined\n\n if (!el) return\n\n overflowObserver = useOverflowObserver(el, {\n onCheck: (isOverflowing) => {\n overflowingTabs.value = isOverflowing\n\n if (collapseTabs.value) {\n // Items are currently hidden, check if we can show them\n if (!isOverflowing) {\n const items = [\n ...el.querySelectorAll('.p-breadcrumb-item')\n ] as HTMLElement[]\n\n if (items.length < 3) return\n\n const itemsWithIcon = items.filter((item) =>\n item.querySelector('.p-breadcrumb-item-link-icon-visible')\n ).length\n const separators = el.querySelectorAll(\n '.p-breadcrumb-separator'\n ) as NodeListOf<HTMLElement>\n const separator = separators[separators.length - 1] as HTMLElement\n const separatorWidth = separator.offsetWidth\n\n // items + separators + gaps + icons\n const itemsWidth =\n (MIN_WIDTH + ITEM_PADDING + ITEM_PADDING) * items.length +\n itemsWithIcon * ICON_WIDTH\n const separatorsWidth = (items.length - 1) * separatorWidth\n const gapsWidth = (items.length - 1) * (ITEM_GAP * 2)\n const totalWidth = itemsWidth + separatorsWidth + gapsWidth\n const containerWidth = el.clientWidth\n\n if (totalWidth <= containerWidth) {\n collapseTabs.value = false\n }\n }\n } else if (isOverflowing) {\n collapseTabs.value = true\n }\n }\n })\n})\n\n// If e.g. the workflow name changes, we need to check the overflow again\nonUpdated(() => {\n if (!overflowObserver?.disposed.value) {\n overflowObserver?.checkOverflow()\n }\n})\n</script>\n\n<style scoped>\n@reference '../../assets/css/style.css';\n\n.subgraph-breadcrumb:not(:empty) {\n flex: auto;\n flex-shrink: 10000;\n min-width: 120px;\n}\n\n.subgraph-breadcrumb,\n:deep(.p-breadcrumb) {\n @apply overflow-hidden;\n}\n\n:deep(.p-breadcrumb) {\n width: 100%;\n background-color: transparent;\n}\n\n:deep(.p-breadcrumb-item) {\n @apply flex items-center overflow-hidden h-8;\n min-width: calc(var(--p-breadcrumb-item-min-width) + 1rem);\n border: 1px solid transparent;\n background-color: transparent;\n transition: all 0.2s;\n /* Collapse middle items first */\n flex-shrink: 10000;\n}\n\n:deep(.p-breadcrumb-separator) {\n border: 1px solid transparent;\n background-color: transparent;\n display: flex;\n padding: 0 var(--p-breadcrumb-item-margin);\n}\n\n:deep(.p-breadcrumb-item-link) {\n padding: 0\n calc(var(--p-breadcrumb-item-margin) + var(--p-breadcrumb-item-padding));\n}\n\n:deep(.p-breadcrumb-item:hover) {\n @apply rounded-lg;\n border-color: var(--interface-stroke);\n background-color: var(--comfy-menu-bg);\n}\n\n:deep(.p-breadcrumb-item:has(.p-breadcrumb-item-link-icon-visible)) {\n min-width: calc(var(--p-breadcrumb-item-min-width) + 1rem + 20px);\n}\n\n:deep(.p-breadcrumb-item:first-child) {\n /* Then collapse the root workflow */\n flex-shrink: 5000;\n\n .p-breadcrumb-item-link {\n padding-left: var(--p-breadcrumb-item-padding);\n }\n}\n\n:deep(.p-breadcrumb-item:last-child) {\n /* Then collapse the active item */\n flex-shrink: 1;\n}\n\n:deep(.p-breadcrumb-item-link-menu-visible) {\n background-color: color-mix(\n in srgb,\n var(--fg-color) 10%,\n var(--comfy-menu-bg)\n ) !important;\n color: var(--fg-color);\n}\n</style>\n\n<style>\n@reference '../../assets/css/style.css';\n\n.subgraph-breadcrumb-collapse .p-breadcrumb-list {\n .p-breadcrumb-item,\n .p-breadcrumb-separator {\n @apply hidden;\n }\n\n .p-breadcrumb-item:nth-last-child(3),\n .p-breadcrumb-separator:nth-last-child(2),\n .p-breadcrumb-item:nth-last-child(1) {\n @apply flex;\n }\n}\n</style>\n","<template>\n <div class=\"flex flex-col gap-3 p-2\">\n <div class=\"flex flex-col gap-1\">\n <div\n class=\"relative h-2 w-full overflow-hidden rounded-full border border-interface-stroke bg-interface-panel-surface\"\n >\n <div\n class=\"absolute inset-0 h-full rounded-full transition-[width]\"\n :style=\"totalProgressStyle\"\n />\n <div\n class=\"absolute inset-0 h-full rounded-full transition-[width]\"\n :style=\"currentNodeProgressStyle\"\n />\n </div>\n <div class=\"flex items-start justify-end gap-4 text-[12px] leading-none\">\n <div class=\"flex items-center gap-1 text-text-primary opacity-90\">\n <i18n-t keypath=\"sideToolbar.queueProgressOverlay.total\">\n <template #percent>\n <span class=\"font-bold\">{{ totalPercentFormatted }}</span>\n </template>\n </i18n-t>\n </div>\n <div class=\"flex items-center gap-1 text-text-secondary\">\n <span>{{ t('sideToolbar.queueProgressOverlay.currentNode') }}</span>\n <span class=\"inline-block max-w-[10rem] truncate\">{{\n currentNodeName\n }}</span>\n <span class=\"flex items-center gap-1\">\n <span>{{ currentNodePercentFormatted }}</span>\n </span>\n </div>\n </div>\n </div>\n\n <div :class=\"bottomRowClass\">\n <div class=\"flex items-center gap-4 text-[12px] text-text-primary\">\n <div class=\"flex items-center gap-2\">\n <span class=\"opacity-90\">\n <span class=\"font-bold\">{{ runningCount }}</span>\n <span class=\"ml-1\">{{\n t('sideToolbar.queueProgressOverlay.running')\n }}</span>\n </span>\n <Button\n v-if=\"runningCount > 0\"\n v-tooltip.top=\"cancelJobTooltip\"\n variant=\"destructive\"\n size=\"icon\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.interruptAll')\"\n @click=\"$emit('interruptAll')\"\n >\n <i\n class=\"icon-[lucide--x] block size-4 leading-none text-text-primary\"\n />\n </Button>\n </div>\n\n <div class=\"flex items-center gap-2\">\n <span class=\"opacity-90\">\n <span class=\"font-bold\">{{ queuedCount }}</span>\n <span class=\"ml-1\">{{\n t('sideToolbar.queueProgressOverlay.queuedSuffix')\n }}</span>\n </span>\n <Button\n v-if=\"queuedCount > 0\"\n v-tooltip.top=\"clearQueueTooltip\"\n variant=\"destructive\"\n size=\"icon\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.clearQueued')\"\n @click=\"$emit('clearQueued')\"\n >\n <i\n class=\"icon-[lucide--list-x] block size-4 leading-none text-text-primary\"\n />\n </Button>\n </div>\n </div>\n\n <Button\n class=\"min-w-30 flex-1 px-2 py-0\"\n variant=\"secondary\"\n size=\"md\"\n @click=\"$emit('viewAllJobs')\"\n >\n {{ t('sideToolbar.queueProgressOverlay.viewAllJobs') }}\n </Button>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { buildTooltipConfig } from '@/composables/useTooltipConfig'\n\ndefineProps<{\n totalProgressStyle: Record<string, string>\n currentNodeProgressStyle: Record<string, string>\n totalPercentFormatted: string\n currentNodePercentFormatted: string\n currentNodeName: string\n runningCount: number\n queuedCount: number\n bottomRowClass: string\n}>()\n\ndefineEmits<{\n (e: 'interruptAll'): void\n (e: 'clearQueued'): void\n (e: 'viewAllJobs'): void\n}>()\n\nconst { t } = useI18n()\nconst cancelJobTooltip = computed(() =>\n buildTooltipConfig(t('sideToolbar.queueProgressOverlay.cancelJobTooltip'))\n)\nconst clearQueueTooltip = computed(() =>\n buildTooltipConfig(t('sideToolbar.queueProgressOverlay.clearQueueTooltip'))\n)\n</script>\n","<template>\n <Button\n variant=\"secondary\"\n size=\"lg\"\n class=\"group w-full justify-between gap-3 p-1 text-left font-normal hover:cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-background\"\n :aria-label=\"props.ariaLabel\"\n @click=\"emit('click', $event)\"\n >\n <span class=\"inline-flex items-center gap-2\">\n <span v-if=\"props.mode === 'allFailed'\" class=\"inline-flex items-center\">\n <i\n class=\"ml-1 icon-[lucide--circle-alert] block size-4 leading-none text-destructive-background\"\n />\n </span>\n\n <span class=\"inline-flex items-center gap-2\">\n <span\n v-if=\"props.mode !== 'allFailed'\"\n class=\"relative inline-flex h-6 items-center\"\n >\n <span\n v-for=\"(url, idx) in props.thumbnailUrls\"\n :key=\"url + idx\"\n class=\"inline-block h-6 w-6 overflow-hidden rounded-[6px] border-0 bg-secondary-background\"\n :style=\"{ marginLeft: idx === 0 ? '0' : '-12px' }\"\n >\n <img\n :src=\"url\"\n :alt=\"$t('sideToolbar.queueProgressOverlay.preview')\"\n class=\"h-full w-full object-cover\"\n />\n </span>\n </span>\n\n <span class=\"text-[14px] font-normal text-text-primary\">\n <template v-if=\"props.mode === 'allSuccess'\">\n <i18n-t\n keypath=\"sideToolbar.queueProgressOverlay.jobsCompleted\"\n :plural=\"props.completedCount\"\n >\n <template #count>\n <span class=\"font-bold\">{{ props.completedCount }}</span>\n </template>\n </i18n-t>\n </template>\n <template v-else-if=\"props.mode === 'mixed'\">\n <i18n-t\n keypath=\"sideToolbar.queueProgressOverlay.jobsCompleted\"\n :plural=\"props.completedCount\"\n >\n <template #count>\n <span class=\"font-bold\">{{ props.completedCount }}</span>\n </template>\n </i18n-t>\n <span>, </span>\n <i18n-t\n keypath=\"sideToolbar.queueProgressOverlay.jobsFailed\"\n :plural=\"props.failedCount\"\n >\n <template #count>\n <span class=\"font-bold\">{{ props.failedCount }}</span>\n </template>\n </i18n-t>\n </template>\n <template v-else>\n <i18n-t\n keypath=\"sideToolbar.queueProgressOverlay.jobsFailed\"\n :plural=\"props.failedCount\"\n >\n <template #count>\n <span class=\"font-bold\">{{ props.failedCount }}</span>\n </template>\n </i18n-t>\n </template>\n </span>\n </span>\n </span>\n\n <span\n class=\"flex items-center justify-center rounded p-1 text-text-secondary transition-colors duration-200 ease-in-out\"\n >\n <i class=\"icon-[lucide--chevron-down] block size-4 leading-none\" />\n </span>\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport type {\n CompletionSummary,\n CompletionSummaryMode\n} from '@/composables/queue/useCompletionSummary'\n\ntype Props = {\n mode: CompletionSummaryMode\n completedCount: CompletionSummary['completedCount']\n failedCount: CompletionSummary['failedCount']\n thumbnailUrls?: CompletionSummary['thumbnailUrls']\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n thumbnailUrls: () => []\n})\n\nconst emit = defineEmits<{\n (e: 'click', event: MouseEvent): void\n}>()\n</script>\n","<template>\n <div class=\"pointer-events-auto\">\n <CompletionSummaryBanner\n :mode=\"summary.mode\"\n :completed-count=\"summary.completedCount\"\n :failed-count=\"summary.failedCount\"\n :thumbnail-urls=\"summary.thumbnailUrls\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.expandCollapsedQueue')\"\n @click=\"$emit('summaryClick')\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n'\n\nimport CompletionSummaryBanner from '@/components/queue/CompletionSummaryBanner.vue'\nimport type { CompletionSummary } from '@/composables/queue/useCompletionSummary'\n\ndefineProps<{ summary: CompletionSummary }>()\n\ndefineEmits<{\n (e: 'summaryClick'): void\n}>()\n\nconst { t } = useI18n()\n</script>\n","<template>\n <div\n class=\"flex h-12 items-center justify-between gap-2 border-b border-interface-stroke px-2\"\n >\n <div class=\"px-2 text-[14px] font-normal text-text-primary\">\n <span>{{ headerTitle }}</span>\n <span\n v-if=\"showConcurrentIndicator\"\n class=\"ml-4 inline-flex items-center gap-1 text-blue-100\"\n >\n <span class=\"inline-block size-2 rounded-full bg-blue-100\" />\n <span>\n <span class=\"font-bold\">{{ concurrentWorkflowCount }}</span>\n <span class=\"ml-1\">{{\n t('sideToolbar.queueProgressOverlay.running')\n }}</span>\n </span>\n </span>\n </div>\n <div v-if=\"!isCloud\" class=\"flex items-center gap-1\">\n <Button\n v-tooltip.top=\"moreTooltipConfig\"\n variant=\"textonly\"\n size=\"icon\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.moreOptions')\"\n @click=\"onMoreClick\"\n >\n <i\n class=\"icon-[lucide--more-horizontal] block size-4 leading-none text-text-secondary\"\n />\n </Button>\n <Popover\n ref=\"morePopoverRef\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: { class: 'absolute z-50' },\n content: {\n class: [\n 'bg-transparent border-none p-0 pt-2 rounded-lg shadow-lg font-inter'\n ]\n }\n }\"\n >\n <div\n class=\"flex flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3 font-inter\"\n >\n <Button\n class=\"w-full justify-start\"\n variant=\"textonly\"\n size=\"sm\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.clearHistory')\"\n @click=\"onClearHistoryFromMenu\"\n >\n <i class=\"icon-[lucide--file-x-2] size-4 text-muted\" />\n <span>{{\n t('sideToolbar.queueProgressOverlay.clearHistory')\n }}</span>\n </Button>\n </div>\n </Popover>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport type { PopoverMethods } from 'primevue/popover'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { buildTooltipConfig } from '@/composables/useTooltipConfig'\nimport { isCloud } from '@/platform/distribution/types'\n\ndefineProps<{\n headerTitle: string\n showConcurrentIndicator: boolean\n concurrentWorkflowCount: number\n}>()\n\nconst emit = defineEmits<{\n (e: 'clearHistory'): void\n}>()\n\nconst { t } = useI18n()\n\nconst morePopoverRef = ref<PopoverMethods | null>(null)\nconst moreTooltipConfig = computed(() => buildTooltipConfig(t('g.more')))\n\nconst onMoreClick = (event: MouseEvent) => {\n morePopoverRef.value?.toggle(event)\n}\nconst onClearHistoryFromMenu = () => {\n morePopoverRef.value?.hide()\n emit('clearHistory')\n}\n</script>\n","<template>\n <Popover\n ref=\"jobItemPopoverRef\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: { class: 'absolute z-50' },\n content: {\n class: [\n 'bg-transparent border-none p-0 pt-2 rounded-lg shadow-lg font-inter'\n ]\n }\n }\"\n >\n <div\n class=\"flex min-w-[14rem] flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3 font-inter\"\n >\n <template v-for=\"entry in entries\" :key=\"entry.key\">\n <div v-if=\"entry.kind === 'divider'\" class=\"px-2 py-1\">\n <div class=\"h-px bg-interface-stroke\" />\n </div>\n <Button\n v-else\n class=\"w-full justify-start bg-transparent\"\n variant=\"textonly\"\n size=\"sm\"\n :aria-label=\"entry.label\"\n @click=\"onEntry(entry)\"\n >\n <i\n v-if=\"entry.icon\"\n :class=\"[\n entry.icon,\n 'block size-4 shrink-0 leading-none text-text-secondary'\n ]\"\n />\n <span>{{ entry.label }}</span>\n </Button>\n </template>\n </div>\n </Popover>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport { ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport type { MenuEntry } from '@/composables/queue/useJobMenu'\n\ndefineProps<{ entries: MenuEntry[] }>()\n\nconst emit = defineEmits<{\n (e: 'action', entry: MenuEntry): void\n}>()\n\nconst jobItemPopoverRef = ref<InstanceType<typeof Popover> | null>(null)\n\nfunction open(event: Event) {\n if (jobItemPopoverRef.value) {\n jobItemPopoverRef.value.toggle(event)\n }\n}\n\nfunction hide() {\n jobItemPopoverRef.value?.hide()\n}\n\nfunction onEntry(entry: MenuEntry) {\n emit('action', entry)\n}\n\ndefineExpose({ open, hide })\n</script>\n","<template>\n <div class=\"flex items-center justify-between gap-2 px-3\">\n <div class=\"min-w-0 flex-1 overflow-x-auto\">\n <div class=\"inline-flex items-center gap-1 whitespace-nowrap\">\n <Button\n v-for=\"tab in visibleJobTabs\"\n :key=\"tab\"\n :variant=\"selectedJobTab === tab ? 'secondary' : 'muted-textonly'\"\n size=\"sm\"\n class=\"px-3\"\n @click=\"$emit('update:selectedJobTab', tab)\"\n >\n {{ tabLabel(tab) }}\n </Button>\n </div>\n </div>\n <div class=\"ml-2 flex shrink-0 items-center gap-2\">\n <Button\n v-if=\"showWorkflowFilter\"\n v-tooltip.top=\"filterTooltipConfig\"\n variant=\"secondary\"\n size=\"icon\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.filterJobs')\"\n @click=\"onFilterClick\"\n >\n <i class=\"icon-[lucide--list-filter] size-4\" />\n <span\n v-if=\"selectedWorkflowFilter !== 'all'\"\n class=\"pointer-events-none absolute -top-1 -right-1 inline-block size-2 rounded-full bg-base-foreground\"\n />\n </Button>\n <Popover\n v-if=\"showWorkflowFilter\"\n ref=\"filterPopoverRef\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: { class: 'absolute z-50' },\n content: {\n class: [\n 'bg-transparent border-none p-0 pt-2 rounded-lg shadow-lg font-inter'\n ]\n }\n }\"\n >\n <div\n class=\"flex min-w-[12rem] flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3\"\n >\n <Button\n class=\"w-full justify-between\"\n variant=\"textonly\"\n size=\"sm\"\n @click=\"selectWorkflowFilter('all')\"\n >\n <span>{{\n t('sideToolbar.queueProgressOverlay.filterAllWorkflows')\n }}</span>\n <i\n v-if=\"selectedWorkflowFilter === 'all'\"\n class=\"icon-[lucide--check] size-4\"\n />\n </Button>\n <div class=\"mx-2 mt-1 h-px\" />\n <Button\n class=\"w-full justify-between\"\n variant=\"textonly\"\n @click=\"selectWorkflowFilter('current')\"\n >\n <span>{{\n t('sideToolbar.queueProgressOverlay.filterCurrentWorkflow')\n }}</span>\n <i\n v-if=\"selectedWorkflowFilter === 'current'\"\n class=\"icon-[lucide--check] block size-4 leading-none text-text-secondary\"\n />\n </Button>\n </div>\n </Popover>\n <Button\n v-tooltip.top=\"sortTooltipConfig\"\n variant=\"secondary\"\n size=\"icon\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.sortJobs')\"\n @click=\"onSortClick\"\n >\n <i class=\"icon-[lucide--arrow-up-down] size-4\" />\n <span\n v-if=\"selectedSortMode !== 'mostRecent'\"\n class=\"pointer-events-none absolute -top-1 -right-1 inline-block size-2 rounded-full bg-base-foreground\"\n />\n </Button>\n <Popover\n ref=\"sortPopoverRef\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: { class: 'absolute z-50' },\n content: {\n class: [\n 'bg-transparent border-none p-0 pt-2 rounded-lg shadow-lg font-inter'\n ]\n }\n }\"\n >\n <div\n class=\"flex min-w-[12rem] flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3\"\n >\n <template v-for=\"(mode, index) in jobSortModes\" :key=\"mode\">\n <Button\n class=\"w-full justify-between\"\n variant=\"textonly\"\n size=\"sm\"\n @click=\"selectSortMode(mode)\"\n >\n <span>{{ sortLabel(mode) }}</span>\n <i\n v-if=\"selectedSortMode === mode\"\n class=\"icon-[lucide--check] size-4 text-text-secondary\"\n />\n </Button>\n <div\n v-if=\"index < jobSortModes.length - 1\"\n class=\"mx-2 mt-1 h-px\"\n />\n </template>\n </div>\n </Popover>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { jobSortModes, jobTabs } from '@/composables/queue/useJobList'\nimport type { JobSortMode, JobTab } from '@/composables/queue/useJobList'\nimport { buildTooltipConfig } from '@/composables/useTooltipConfig'\nimport { isCloud } from '@/platform/distribution/types'\n\nconst props = defineProps<{\n selectedJobTab: JobTab\n selectedWorkflowFilter: 'all' | 'current'\n selectedSortMode: JobSortMode\n hasFailedJobs: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:selectedJobTab', value: JobTab): void\n (e: 'update:selectedWorkflowFilter', value: 'all' | 'current'): void\n (e: 'update:selectedSortMode', value: JobSortMode): void\n}>()\n\nconst { t } = useI18n()\nconst filterPopoverRef = ref<InstanceType<typeof Popover> | null>(null)\nconst sortPopoverRef = ref<InstanceType<typeof Popover> | null>(null)\n\nconst filterTooltipConfig = computed(() =>\n buildTooltipConfig(t('sideToolbar.queueProgressOverlay.filterBy'))\n)\nconst sortTooltipConfig = computed(() =>\n buildTooltipConfig(t('sideToolbar.queueProgressOverlay.sortBy'))\n)\n\n// This can be removed when cloud implements /jobs and we switch to it.\nconst showWorkflowFilter = !isCloud\n\nconst visibleJobTabs = computed(() =>\n props.hasFailedJobs ? jobTabs : jobTabs.filter((tab) => tab !== 'Failed')\n)\n\nconst onFilterClick = (event: Event) => {\n if (filterPopoverRef.value) {\n filterPopoverRef.value.toggle(event)\n }\n}\nconst selectWorkflowFilter = (value: 'all' | 'current') => {\n ;(filterPopoverRef.value as any)?.hide?.()\n emit('update:selectedWorkflowFilter', value)\n}\n\nconst onSortClick = (event: Event) => {\n if (sortPopoverRef.value) {\n sortPopoverRef.value.toggle(event)\n }\n}\n\nconst selectSortMode = (value: JobSortMode) => {\n ;(sortPopoverRef.value as any)?.hide?.()\n emit('update:selectedSortMode', value)\n}\n\nconst tabLabel = (tab: JobTab) => {\n if (tab === 'All') return t('g.all')\n if (tab === 'Completed') return t('g.completed')\n return t('g.failed')\n}\n\nconst sortLabel = (mode: JobSortMode) => {\n if (mode === 'mostRecent') {\n return t('queue.jobList.sortMostRecent')\n }\n if (mode === 'totalGenerationTime') {\n return t('queue.jobList.sortTotalGenerationTime')\n }\n return ''\n}\n</script>\n","import { computed } from 'vue'\nimport type { ComputedRef } from 'vue'\n\nimport type { ExecutionErrorWsMessage } from '@/schemas/apiSchema'\nimport type { TaskItemImpl } from '@/stores/queueStore'\n\ntype CopyHandler = (value: string) => void | Promise<void>\n\nexport type JobErrorDialogService = {\n showExecutionErrorDialog: (error: ExecutionErrorWsMessage) => void\n showErrorDialog: (\n error: Error,\n options?: {\n reportType?: string\n [key: string]: unknown\n }\n ) => void\n}\n\ntype JobExecutionError = {\n detail?: ExecutionErrorWsMessage\n message: string\n}\n\nexport const extractExecutionError = (\n task: TaskItemImpl | null\n): JobExecutionError | null => {\n const status = (task as TaskItemImpl | null)?.status\n const messages = (status as { messages?: unknown[] } | undefined)?.messages\n if (!Array.isArray(messages) || !messages.length) return null\n const record = messages.find((entry: unknown) => {\n return Array.isArray(entry) && entry[0] === 'execution_error'\n }) as [string, ExecutionErrorWsMessage?] | undefined\n if (!record) return null\n const detail = record[1]\n const message = String(detail?.exception_message ?? '')\n return {\n detail,\n message\n }\n}\n\nexport type UseJobErrorReportingOptions = {\n taskForJob: ComputedRef<TaskItemImpl | null>\n copyToClipboard: CopyHandler\n dialog: JobErrorDialogService\n}\n\nexport const useJobErrorReporting = ({\n taskForJob,\n copyToClipboard,\n dialog\n}: UseJobErrorReportingOptions) => {\n const errorMessageValue = computed(() => {\n const error = extractExecutionError(taskForJob.value)\n return error?.message ?? ''\n })\n\n const copyErrorMessage = () => {\n if (errorMessageValue.value) {\n void copyToClipboard(errorMessageValue.value)\n }\n }\n\n const reportJobError = () => {\n const error = extractExecutionError(taskForJob.value)\n if (error?.detail) {\n dialog.showExecutionErrorDialog(error.detail)\n return\n }\n if (errorMessageValue.value) {\n dialog.showErrorDialog(new Error(errorMessageValue.value), {\n reportType: 'queueJobError'\n })\n }\n }\n\n return {\n errorMessageValue,\n copyErrorMessage,\n reportJobError\n }\n}\n","import { computed } from 'vue'\nimport type { ComputedRef, Ref } from 'vue'\n\nimport type { useExecutionStore } from '@/stores/executionStore'\nimport type { TaskItemImpl, useQueueStore } from '@/stores/queueStore'\nimport type { JobState } from '@/types/queue'\n\ntype QueueStore = ReturnType<typeof useQueueStore>\ntype ExecutionStore = ReturnType<typeof useExecutionStore>\n\nexport type UseQueueEstimatesOptions = {\n queueStore: QueueStore\n executionStore: ExecutionStore\n taskForJob: ComputedRef<TaskItemImpl | null>\n jobState: ComputedRef<JobState | null>\n firstSeenTs: ComputedRef<number | undefined>\n jobsAhead: ComputedRef<number | null>\n nowTs: Ref<number>\n}\n\ntype EstimateRange = [number, number]\n\nexport const formatElapsedTime = (ms: number): string => {\n const totalSec = Math.max(0, Math.floor(ms / 1000))\n const minutes = Math.floor(totalSec / 60)\n const seconds = totalSec % 60\n return `${minutes}m ${seconds}s`\n}\n\nconst pickRecentDurations = (queueStore: QueueStore) =>\n queueStore.historyTasks\n .map((task: TaskItemImpl) => Number(task.executionTimeInSeconds))\n .filter(\n (value: number | undefined) =>\n typeof value === 'number' && !Number.isNaN(value)\n ) as number[]\n\nexport const useQueueEstimates = ({\n queueStore,\n executionStore,\n taskForJob,\n jobState,\n firstSeenTs,\n jobsAhead,\n nowTs\n}: UseQueueEstimatesOptions) => {\n const runningWorkflowCount = computed(\n () => executionStore.runningWorkflowCount\n )\n\n const showParallelQueuedStats = computed(\n () =>\n jobState.value === 'pending' &&\n !!firstSeenTs.value &&\n (runningWorkflowCount.value ?? 0) > 1\n )\n\n const recentDurations = computed<number[]>(() =>\n pickRecentDurations(queueStore).slice(-20)\n )\n\n const runningRemainingRangeSeconds = computed<EstimateRange | null>(() => {\n const durations = recentDurations.value\n if (!durations.length) return null\n const sorted = durations.slice().sort((a, b) => a - b)\n const avg = sorted.reduce((sum, value) => sum + value, 0) / sorted.length\n const p75 =\n sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.75))]\n const running = queueStore.runningTasks as TaskItemImpl[]\n const now = nowTs.value\n const remaining = running\n .map((task) => task.executionStartTimestamp)\n .filter((timestamp): timestamp is number => typeof timestamp === 'number')\n .map((startTs) => {\n const elapsed = Math.max(0, Math.floor((now - startTs) / 1000))\n return {\n lo: Math.max(0, Math.round(avg - elapsed)),\n hi: Math.max(0, Math.round(p75 - elapsed))\n }\n })\n if (!remaining.length) return null\n const minLo = remaining.reduce(\n (min, range) => Math.min(min, range.lo),\n Infinity\n )\n const minHi = remaining.reduce(\n (min, range) => Math.min(min, range.hi),\n Infinity\n )\n return [minLo, minHi]\n })\n\n const estimateRangeSeconds = computed<EstimateRange | null>(() => {\n const durations = recentDurations.value\n if (!durations.length) return null\n const ahead = jobsAhead.value\n if (ahead == null) return null\n const sorted = durations.slice().sort((a, b) => a - b)\n const avg = sorted.reduce((sum, value) => sum + value, 0) / sorted.length\n const p75 =\n sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.75))]\n if (ahead <= 0) {\n return runningRemainingRangeSeconds.value ?? [0, 0]\n }\n const runningCount = Math.max(1, runningWorkflowCount.value || 1)\n const batches = Math.ceil(ahead / runningCount)\n return [Math.round(avg * batches), Math.round(p75 * batches)]\n })\n\n const estimateRemainingRangeSeconds = computed<EstimateRange | null>(() => {\n const durations = recentDurations.value\n if (!durations.length) return null\n const sorted = durations.slice().sort((a, b) => a - b)\n const avg = sorted.reduce((sum, value) => sum + value, 0) / sorted.length\n const p75 =\n sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.75))]\n const task = taskForJob.value as TaskItemImpl & {\n executionStartTimestamp?: number\n }\n const execStart =\n jobState.value === 'running' ? task?.executionStartTimestamp : undefined\n const baseTs = execStart ?? firstSeenTs.value\n const elapsed = baseTs\n ? Math.max(0, Math.floor((nowTs.value - baseTs) / 1000))\n : 0\n const lo = Math.max(0, Math.round(avg - elapsed))\n const hi = Math.max(0, Math.round(p75 - elapsed))\n return [lo, hi]\n })\n\n const timeElapsedValue = computed(() => {\n const task = taskForJob.value as TaskItemImpl & {\n executionStartTimestamp?: number\n }\n const execStart =\n jobState.value === 'running' ? task?.executionStartTimestamp : undefined\n const baseTs = execStart ?? firstSeenTs.value\n if (!baseTs) return ''\n return formatElapsedTime(nowTs.value - baseTs)\n })\n\n return {\n runningWorkflowCount,\n showParallelQueuedStats,\n estimateRangeSeconds,\n estimateRemainingRangeSeconds,\n timeElapsedValue\n }\n}\n","<template>\n <div\n class=\"w-[300px] min-w-[260px] rounded-lg border border-interface-stroke bg-interface-panel-surface shadow-md\"\n >\n <div class=\"flex items-center border-b border-interface-stroke p-4\">\n <span\n class=\"text-[0.875rem] leading-normal font-normal text-text-primary\"\n >{{ t('queue.jobDetails.header') }}</span\n >\n </div>\n <div class=\"flex flex-col gap-6 px-4 pt-4 pb-4\">\n <div class=\"grid grid-cols-2 items-center gap-x-2 gap-y-2\">\n <template v-for=\"row in baseRows\" :key=\"row.label\">\n <div\n class=\"flex items-center text-[0.75rem] leading-normal font-normal text-text-primary\"\n >\n {{ row.label }}\n </div>\n <div\n class=\"flex min-w-0 items-center text-[0.75rem] leading-normal font-normal text-text-secondary\"\n >\n <span class=\"block min-w-0 truncate\">{{ row.value }}</span>\n <Button\n v-if=\"row.canCopy\"\n size=\"icon\"\n variant=\"muted-textonly\"\n :aria-label=\"copyAriaLabel\"\n @click.stop=\"copyJobId\"\n >\n <i class=\"icon-[lucide--copy] size-4\" />\n </Button>\n </div>\n </template>\n </div>\n\n <div\n v-if=\"extraRows.length\"\n class=\"grid grid-cols-2 items-center gap-x-2 gap-y-2\"\n >\n <template v-for=\"row in extraRows\" :key=\"row.label\">\n <div\n class=\"flex items-center text-[0.75rem] leading-normal font-normal text-text-primary\"\n >\n {{ row.label }}\n </div>\n <div\n class=\"flex min-w-0 items-center text-[0.75rem] leading-normal font-normal text-text-secondary\"\n >\n <span class=\"block min-w-0 truncate\">{{ row.value }}</span>\n </div>\n </template>\n </div>\n\n <div v-if=\"jobState === 'failed'\" class=\"grid grid-cols-2 gap-x-2\">\n <div\n class=\"flex items-center text-[0.75rem] leading-normal font-normal text-text-primary\"\n >\n {{ t('queue.jobDetails.errorMessage') }}\n </div>\n <div class=\"flex items-center justify-between gap-4\">\n <Button\n class=\"justify-start px-0\"\n variant=\"muted-textonly\"\n size=\"sm\"\n icon-position=\"right\"\n @click.stop=\"copyErrorMessage\"\n >\n <span>{{ copyAriaLabel }}</span>\n <i class=\"icon-[lucide--copy] block size-3.5 leading-none\" />\n </Button>\n <Button\n class=\"justify-start px-0\"\n variant=\"muted-textonly\"\n size=\"sm\"\n icon-position=\"right\"\n @click.stop=\"reportJobError\"\n >\n <span>{{ t('queue.jobDetails.report') }}</span>\n <i\n class=\"icon-[lucide--message-circle-warning] block size-3.5 leading-none\"\n />\n </Button>\n </div>\n <div\n class=\"col-span-2 mt-2 rounded bg-interface-panel-hover-surface px-4 py-2 text-[0.75rem] leading-normal text-text-secondary\"\n >\n {{ errorMessageValue }}\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCopyToClipboard } from '@/composables/useCopyToClipboard'\nimport { isCloud } from '@/platform/distribution/types'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useDialogService } from '@/services/dialogService'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useQueueStore } from '@/stores/queueStore'\nimport type { TaskItemImpl } from '@/stores/queueStore'\nimport { formatClockTime } from '@/utils/dateTimeUtil'\nimport { jobStateFromTask } from '@/utils/queueUtil'\n\nimport { useJobErrorReporting } from './useJobErrorReporting'\nimport { formatElapsedTime, useQueueEstimates } from './useQueueEstimates'\n\nconst props = defineProps<{\n jobId: string\n workflowId?: string\n}>()\n\nconst copyAriaLabel = computed(() => t('g.copy'))\n\nconst workflowStore = useWorkflowStore()\nconst queueStore = useQueueStore()\nconst executionStore = useExecutionStore()\nconst dialog = useDialogService()\nconst { locale, t } = useI18n()\n\nconst workflowValue = computed(() => {\n const wid = props.workflowId\n if (!wid) return ''\n const activeId = workflowStore.activeWorkflow?.activeState?.id\n if (activeId && activeId === wid) {\n return workflowStore.activeWorkflow?.filename ?? wid\n }\n return wid\n})\nconst jobIdValue = computed(() => props.jobId)\n\nconst { copyToClipboard } = useCopyToClipboard()\nconst copyJobId = () => void copyToClipboard(jobIdValue.value)\n\nconst taskForJob = computed(() => {\n const pid = props.jobId\n const findIn = (arr: TaskItemImpl[]) =>\n arr.find((t) => String(t.promptId ?? '') === String(pid))\n return (\n findIn(queueStore.pendingTasks) ||\n findIn(queueStore.runningTasks) ||\n findIn(queueStore.historyTasks) ||\n null\n )\n})\n\nconst jobState = computed(() => {\n const task = taskForJob.value\n if (!task) return null\n const isInitializing = executionStore.isPromptInitializing(\n String(task?.promptId)\n )\n return jobStateFromTask(task, isInitializing)\n})\n\nconst firstSeenTs = computed<number | undefined>(() => {\n const task = taskForJob.value\n return task?.createTime\n})\n\nconst queuedAtValue = computed(() =>\n firstSeenTs.value !== undefined\n ? formatClockTime(firstSeenTs.value, locale.value)\n : ''\n)\n\nconst currentQueueIndex = computed<number | null>(() => {\n const task = taskForJob.value\n return task ? Number(task.queueIndex) : null\n})\n\nconst jobsAhead = computed<number | null>(() => {\n const idx = currentQueueIndex.value\n if (idx == null) return null\n const ahead = queueStore.pendingTasks.filter(\n (t: TaskItemImpl) => Number(t.queueIndex) < idx\n )\n return ahead.length\n})\n\nconst queuePositionValue = computed(() => {\n if (jobsAhead.value == null) return ''\n const n = jobsAhead.value\n return t('queue.jobDetails.queuePositionValue', { count: n }, n)\n})\n\nconst nowTs = ref<number>(Date.now())\nlet timer: number | null = null\nonMounted(() => {\n timer = window.setInterval(() => {\n nowTs.value = Date.now()\n }, 1000)\n})\nonUnmounted(() => {\n if (timer != null) {\n clearInterval(timer)\n timer = null\n }\n})\n\nconst {\n showParallelQueuedStats,\n estimateRangeSeconds,\n estimateRemainingRangeSeconds,\n timeElapsedValue\n} = useQueueEstimates({\n queueStore,\n executionStore,\n taskForJob,\n jobState,\n firstSeenTs,\n jobsAhead,\n nowTs\n})\n\nconst formatEta = (lo: number, hi: number): string => {\n if (hi <= 60) {\n const hiS = Math.max(1, Math.round(hi))\n const loS = Math.max(1, Math.min(hiS, Math.round(lo)))\n if (loS === hiS)\n return t('queue.jobDetails.eta.seconds', { count: hiS }, hiS)\n return t('queue.jobDetails.eta.secondsRange', { lo: loS, hi: hiS })\n }\n if (lo >= 60 && hi < 90) {\n return t('queue.jobDetails.eta.minutes', { count: 1 }, 1)\n }\n const loM = Math.max(1, Math.floor(lo / 60))\n const hiM = Math.max(loM, Math.ceil(hi / 60))\n if (loM === hiM) {\n return t('queue.jobDetails.eta.minutes', { count: loM }, loM)\n }\n return t('queue.jobDetails.eta.minutesRange', { lo: loM, hi: hiM })\n}\n\nconst estimatedStartInValue = computed(() => {\n const range = estimateRangeSeconds.value\n if (!range) return ''\n const [lo, hi] = range\n return formatEta(lo, hi)\n})\n\nconst estimatedFinishInValue = computed(() => {\n const range = estimateRemainingRangeSeconds.value\n if (!range) return ''\n const [lo, hi] = range\n return formatEta(lo, hi)\n})\n\ntype DetailRow = { label: string; value: string; canCopy?: boolean }\n\nconst baseRows = computed<DetailRow[]>(() => [\n { label: t('queue.jobDetails.workflow'), value: workflowValue.value },\n { label: t('queue.jobDetails.jobId'), value: jobIdValue.value, canCopy: true }\n])\n\nconst extraRows = computed<DetailRow[]>(() => {\n if (jobState.value === 'pending') {\n if (!firstSeenTs.value) return []\n const rows: DetailRow[] = [\n { label: t('queue.jobDetails.queuedAt'), value: queuedAtValue.value }\n ]\n if (showParallelQueuedStats.value) {\n rows.push(\n {\n label: t('queue.jobDetails.queuePosition'),\n value: queuePositionValue.value\n },\n {\n label: t('queue.jobDetails.timeElapsed'),\n value: timeElapsedValue.value\n },\n {\n label: t('queue.jobDetails.estimatedStartIn'),\n value: estimatedStartInValue.value\n }\n )\n }\n return rows\n }\n if (jobState.value === 'running') {\n if (!firstSeenTs.value) return []\n return [\n { label: t('queue.jobDetails.queuedAt'), value: queuedAtValue.value },\n {\n label: t('queue.jobDetails.timeElapsed'),\n value: timeElapsedValue.value\n },\n {\n label: t('queue.jobDetails.estimatedFinishIn'),\n value: estimatedFinishInValue.value\n }\n ]\n }\n if (jobState.value === 'completed') {\n const task = taskForJob.value as any\n const endTs: number | undefined = task?.executionEndTimestamp\n const execMs: number | undefined = task?.executionTime\n const generatedOnValue = endTs ? formatClockTime(endTs, locale.value) : ''\n const totalGenTimeValue =\n execMs !== undefined ? formatElapsedTime(execMs) : ''\n const computeHoursValue =\n execMs !== undefined ? (execMs / 3600000).toFixed(3) + ' hours' : ''\n\n const rows: DetailRow[] = [\n { label: t('queue.jobDetails.generatedOn'), value: generatedOnValue },\n {\n label: t('queue.jobDetails.totalGenerationTime'),\n value: totalGenTimeValue\n }\n ]\n if (isCloud) {\n rows.push({\n label: t('queue.jobDetails.computeHoursUsed'),\n value: computeHoursValue\n })\n }\n return rows\n }\n if (jobState.value === 'failed') {\n const task = taskForJob.value as any\n const execMs: number | undefined = task?.executionTime\n const failedAfterValue =\n execMs !== undefined ? formatElapsedTime(execMs) : ''\n const computeHoursValue =\n execMs !== undefined ? (execMs / 3600000).toFixed(3) + ' hours' : ''\n const rows: DetailRow[] = [\n { label: t('queue.jobDetails.queuedAt'), value: queuedAtValue.value },\n { label: t('queue.jobDetails.failedAfter'), value: failedAfterValue }\n ]\n if (isCloud) {\n rows.push({\n label: t('queue.jobDetails.computeHoursUsed'),\n value: computeHoursValue\n })\n }\n return rows\n }\n return []\n})\n\nconst { errorMessageValue, copyErrorMessage, reportJobError } =\n useJobErrorReporting({\n taskForJob,\n copyToClipboard,\n dialog\n })\n</script>\n","<template>\n <div class=\"w-[300px] min-w-[260px] rounded-lg shadow-md\">\n <div class=\"p-3\">\n <div class=\"relative aspect-square w-full overflow-hidden rounded-lg\">\n <img\n ref=\"imgRef\"\n :src=\"imageUrl\"\n :alt=\"name\"\n class=\"h-full w-full cursor-pointer object-contain\"\n @click=\"$emit('image-click')\"\n @load=\"onImgLoad\"\n />\n <div\n v-if=\"timeLabel\"\n class=\"absolute bottom-2 left-2 rounded px-2 py-0.5 text-xs text-text-primary\"\n :style=\"{\n background: 'rgba(217, 217, 217, 0.40)',\n backdropFilter: 'blur(2px)'\n }\"\n >\n {{ timeLabel }}\n </div>\n </div>\n <div class=\"mt-2 text-center\">\n <div\n class=\"truncate text-[0.875rem] leading-normal font-semibold text-text-primary\"\n :title=\"name\"\n >\n {{ name }}\n </div>\n <div\n v-if=\"width && height\"\n class=\"mt-1 text-[0.75rem] leading-normal text-text-secondary\"\n >\n {{ width }}x{{ height }}\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\ndefineOptions({ inheritAttrs: false })\n\ndefineProps<{\n imageUrl: string\n name: string\n timeLabel?: string\n}>()\n\ndefineEmits(['image-click'])\n\nconst imgRef = ref<HTMLImageElement | null>(null)\nconst width = ref<number | null>(null)\nconst height = ref<number | null>(null)\n\nconst onImgLoad = () => {\n const el = imgRef.value\n if (!el) return\n width.value = el.naturalWidth || null\n height.value = el.naturalHeight || null\n}\n</script>\n","<template>\n <div\n ref=\"rowRef\"\n class=\"relative\"\n @mouseenter=\"onRowEnter\"\n @mouseleave=\"onRowLeave\"\n @contextmenu.stop.prevent=\"onContextMenu\"\n >\n <Teleport to=\"body\">\n <div\n v-if=\"!isPreviewVisible && showDetails && popoverPosition\"\n class=\"fixed z-50\"\n :style=\"{\n top: `${popoverPosition.top}px`,\n right: `${popoverPosition.right}px`\n }\"\n @mouseenter=\"onPopoverEnter\"\n @mouseleave=\"onPopoverLeave\"\n >\n <JobDetailsPopover\n :job-id=\"props.jobId\"\n :workflow-id=\"props.workflowId\"\n />\n </div>\n </Teleport>\n <Teleport to=\"body\">\n <div\n v-if=\"isPreviewVisible && canShowPreview && popoverPosition\"\n class=\"fixed z-50\"\n :style=\"{\n top: `${popoverPosition.top}px`,\n right: `${popoverPosition.right}px`\n }\"\n @mouseenter=\"onPreviewEnter\"\n @mouseleave=\"onPreviewLeave\"\n >\n <QueueAssetPreview\n :image-url=\"iconImageUrl!\"\n :name=\"props.title\"\n :time-label=\"rightText || undefined\"\n @image-click=\"emit('view')\"\n />\n </div>\n </Teleport>\n <div\n class=\"relative flex items-center justify-between gap-2 overflow-hidden rounded-lg border border-secondary-background bg-secondary-background p-1 text-[12px] text-text-primary transition-colors duration-150 ease-in-out hover:border-secondary-background-hover hover:bg-secondary-background-hover\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <div\n v-if=\"\n props.state === 'running' &&\n hasAnyProgressPercent(\n props.progressTotalPercent,\n props.progressCurrentPercent\n )\n \"\n :class=\"progressBarContainerClass\"\n >\n <div\n v-if=\"hasProgressPercent(props.progressTotalPercent)\"\n :class=\"progressBarPrimaryClass\"\n :style=\"progressPercentStyle(props.progressTotalPercent)\"\n />\n <div\n v-if=\"hasProgressPercent(props.progressCurrentPercent)\"\n :class=\"progressBarSecondaryClass\"\n :style=\"progressPercentStyle(props.progressCurrentPercent)\"\n />\n </div>\n\n <div class=\"relative z-1 flex items-center gap-1\">\n <div class=\"relative inline-flex items-center justify-center\">\n <div\n class=\"absolute left-1/2 top-1/2 size-10 -translate-x-1/2 -translate-y-1/2\"\n @mouseenter.stop=\"onIconEnter\"\n @mouseleave.stop=\"onIconLeave\"\n />\n <div\n class=\"inline-flex h-6 w-6 items-center justify-center overflow-hidden rounded-[6px]\"\n >\n <img\n v-if=\"iconImageUrl\"\n :src=\"iconImageUrl\"\n class=\"h-full w-full object-cover\"\n />\n <i\n v-else\n :class=\"cn(iconClass, 'size-4', shouldSpin && 'animate-spin')\"\n />\n </div>\n </div>\n </div>\n\n <div class=\"relative z-1 min-w-0 flex-1\">\n <div class=\"truncate opacity-90\" :title=\"props.title\">\n <slot name=\"primary\">{{ props.title }}</slot>\n </div>\n </div>\n\n <!--\n TODO: Refactor action buttons to use a declarative config system.\n\n Instead of hardcoding button visibility logic in the template, define an array of\n action button configs with properties like:\n - icon, label, action, tooltip\n - visibleStates: JobState[] (which job states show this button)\n - alwaysVisible: boolean (show without hover)\n - destructive: boolean (use destructive styling)\n\n Then render buttons in two groups:\n 1. Always-visible buttons (outside Transition)\n 2. Hover-only buttons (inside Transition)\n\n This would eliminate the current duplication where the cancel button exists\n both outside (for running) and inside (for pending) the Transition.\n -->\n <div class=\"relative z-1 flex items-center gap-2 text-text-secondary\">\n <Transition\n mode=\"out-in\"\n enter-active-class=\"transition-opacity transition-transform duration-150 ease-out\"\n leave-active-class=\"transition-opacity transition-transform duration-150 ease-in\"\n enter-from-class=\"opacity-0 translate-y-0.5\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-0.5\"\n >\n <div\n v-if=\"isHovered\"\n key=\"actions\"\n class=\"inline-flex items-center gap-2 pr-1\"\n >\n <Button\n v-if=\"props.state === 'failed' && computedShowClear\"\n v-tooltip.top=\"deleteTooltipConfig\"\n variant=\"destructive\"\n size=\"icon\"\n :aria-label=\"t('g.delete')\"\n @click.stop=\"onDeleteClick\"\n >\n <i class=\"icon-[lucide--trash-2] size-4\" />\n </Button>\n <Button\n v-else-if=\"\n props.state !== 'completed' &&\n props.state !== 'running' &&\n computedShowClear\n \"\n v-tooltip.top=\"cancelTooltipConfig\"\n variant=\"destructive\"\n size=\"icon\"\n :aria-label=\"t('g.cancel')\"\n @click.stop=\"onCancelClick\"\n >\n <i class=\"icon-[lucide--x] size-4\" />\n </Button>\n <Button\n v-else-if=\"props.state === 'completed'\"\n variant=\"textonly\"\n size=\"sm\"\n @click.stop=\"emit('view')\"\n >{{ t('menuLabels.View') }}</Button\n >\n <Button\n v-if=\"props.showMenu !== undefined ? props.showMenu : true\"\n v-tooltip.top=\"moreTooltipConfig\"\n variant=\"textonly\"\n size=\"icon-sm\"\n :aria-label=\"t('g.more')\"\n @click.stop=\"emit('menu', $event)\"\n >\n <i class=\"icon-[lucide--more-horizontal] size-4\" />\n </Button>\n </div>\n <div\n v-else-if=\"props.state !== 'running'\"\n key=\"secondary\"\n class=\"pr-2\"\n >\n <slot name=\"secondary\">{{ props.rightText }}</slot>\n </div>\n </Transition>\n <!-- Running job cancel button - always visible -->\n <Button\n v-if=\"props.state === 'running' && computedShowClear\"\n v-tooltip.top=\"cancelTooltipConfig\"\n variant=\"destructive\"\n size=\"icon\"\n :aria-label=\"t('g.cancel')\"\n @click.stop=\"onCancelClick\"\n >\n <i class=\"icon-[lucide--x] size-4\" />\n </Button>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport JobDetailsPopover from '@/components/queue/job/JobDetailsPopover.vue'\nimport QueueAssetPreview from '@/components/queue/job/QueueAssetPreview.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useProgressBarBackground } from '@/composables/useProgressBarBackground'\nimport { buildTooltipConfig } from '@/composables/useTooltipConfig'\nimport type { JobState } from '@/types/queue'\nimport { iconForJobState } from '@/utils/queueDisplay'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst props = withDefaults(\n defineProps<{\n jobId: string\n workflowId?: string\n state: JobState\n title: string\n rightText?: string\n iconName?: string\n iconImageUrl?: string\n showClear?: boolean\n showMenu?: boolean\n progressTotalPercent?: number\n progressCurrentPercent?: number\n activeDetailsId?: string | null\n }>(),\n {\n workflowId: undefined,\n rightText: '',\n iconName: undefined,\n iconImageUrl: undefined,\n showClear: undefined,\n showMenu: undefined,\n progressTotalPercent: undefined,\n progressCurrentPercent: undefined,\n runningNodeName: undefined,\n activeDetailsId: null\n }\n)\n\nconst emit = defineEmits<{\n (e: 'cancel'): void\n (e: 'delete'): void\n (e: 'menu', event: MouseEvent): void\n (e: 'view'): void\n (e: 'details-enter', jobId: string): void\n (e: 'details-leave', jobId: string): void\n}>()\n\nconst { t } = useI18n()\nconst {\n progressBarContainerClass,\n progressBarPrimaryClass,\n progressBarSecondaryClass,\n hasProgressPercent,\n hasAnyProgressPercent,\n progressPercentStyle\n} = useProgressBarBackground()\n\nconst cancelTooltipConfig = computed(() => buildTooltipConfig(t('g.cancel')))\nconst deleteTooltipConfig = computed(() => buildTooltipConfig(t('g.delete')))\nconst moreTooltipConfig = computed(() => buildTooltipConfig(t('g.more')))\n\nconst rowRef = ref<HTMLDivElement | null>(null)\nconst showDetails = computed(() => props.activeDetailsId === props.jobId)\n\nconst onRowEnter = () => {\n if (!isPreviewVisible.value) emit('details-enter', props.jobId)\n}\nconst onRowLeave = () => emit('details-leave', props.jobId)\nconst onPopoverEnter = () => emit('details-enter', props.jobId)\nconst onPopoverLeave = () => emit('details-leave', props.jobId)\n\nconst isPreviewVisible = ref(false)\nconst previewHideTimer = ref<number | null>(null)\nconst previewShowTimer = ref<number | null>(null)\nconst clearPreviewHideTimer = () => {\n if (previewHideTimer.value !== null) {\n clearTimeout(previewHideTimer.value)\n previewHideTimer.value = null\n }\n}\nconst clearPreviewShowTimer = () => {\n if (previewShowTimer.value !== null) {\n clearTimeout(previewShowTimer.value)\n previewShowTimer.value = null\n }\n}\nconst canShowPreview = computed(\n () => props.state === 'completed' && !!props.iconImageUrl\n)\nconst scheduleShowPreview = () => {\n if (!canShowPreview.value) return\n clearPreviewHideTimer()\n clearPreviewShowTimer()\n previewShowTimer.value = window.setTimeout(() => {\n isPreviewVisible.value = true\n previewShowTimer.value = null\n }, 200)\n}\nconst scheduleHidePreview = () => {\n clearPreviewHideTimer()\n clearPreviewShowTimer()\n previewHideTimer.value = window.setTimeout(() => {\n isPreviewVisible.value = false\n previewHideTimer.value = null\n }, 150)\n}\nconst onIconEnter = () => scheduleShowPreview()\nconst onIconLeave = () => scheduleHidePreview()\nconst onPreviewEnter = () => scheduleShowPreview()\nconst onPreviewLeave = () => scheduleHidePreview()\n\nconst popoverPosition = ref<{ top: number; right: number } | null>(null)\n\nconst updatePopoverPosition = () => {\n const el = rowRef.value\n if (!el) return\n const rect = el.getBoundingClientRect()\n const gap = 8\n popoverPosition.value = {\n top: rect.top,\n right: window.innerWidth - rect.left + gap\n }\n}\n\nconst isAnyPopoverVisible = computed(\n () => showDetails.value || (isPreviewVisible.value && canShowPreview.value)\n)\n\nwatch(\n isAnyPopoverVisible,\n (visible) => {\n if (visible) {\n nextTick(updatePopoverPosition)\n } else {\n popoverPosition.value = null\n }\n },\n { immediate: false }\n)\n\nconst isHovered = ref(false)\n\nconst iconClass = computed(() => {\n if (props.iconName) return props.iconName\n return iconForJobState(props.state)\n})\n\nconst shouldSpin = computed(\n () =>\n props.state === 'pending' &&\n iconClass.value === iconForJobState('pending') &&\n !props.iconImageUrl\n)\n\nconst computedShowClear = computed(() => {\n if (props.showClear !== undefined) return props.showClear\n return props.state !== 'completed'\n})\n\nconst emitDetailsLeave = () => emit('details-leave', props.jobId)\n\nconst onCancelClick = () => {\n emitDetailsLeave()\n emit('cancel')\n}\n\nconst onDeleteClick = () => {\n emitDetailsLeave()\n emit('delete')\n}\n\nconst onContextMenu = (event: MouseEvent) => {\n const shouldShowMenu = props.showMenu !== undefined ? props.showMenu : true\n if (shouldShowMenu) emit('menu', event)\n}\n</script>\n","<template>\n <div class=\"flex flex-col gap-4 px-3 pb-4\">\n <div\n v-for=\"group in displayedJobGroups\"\n :key=\"group.key\"\n class=\"flex flex-col gap-2\"\n >\n <div class=\"text-[12px] leading-none text-text-secondary\">\n {{ group.label }}\n </div>\n <QueueJobItem\n v-for=\"ji in group.items\"\n :key=\"ji.id\"\n :job-id=\"ji.id\"\n :workflow-id=\"ji.taskRef?.workflow?.id\"\n :state=\"ji.state\"\n :title=\"ji.title\"\n :right-text=\"ji.meta\"\n :icon-name=\"ji.iconName\"\n :icon-image-url=\"ji.iconImageUrl\"\n :show-clear=\"ji.showClear\"\n :show-menu=\"true\"\n :progress-total-percent=\"ji.progressTotalPercent\"\n :progress-current-percent=\"ji.progressCurrentPercent\"\n :running-node-name=\"ji.runningNodeName\"\n :active-details-id=\"activeDetailsId\"\n @cancel=\"emitCancelItem(ji)\"\n @delete=\"emitDeleteItem(ji)\"\n @menu=\"(ev) => $emit('menu', ji, ev)\"\n @view=\"$emit('viewItem', ji)\"\n @details-enter=\"onDetailsEnter\"\n @details-leave=\"onDetailsLeave\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onBeforeUnmount, ref, watch } from 'vue'\n\nimport QueueJobItem from '@/components/queue/job/QueueJobItem.vue'\nimport type { JobGroup, JobListItem } from '@/composables/queue/useJobList'\n\nconst props = defineProps<{ displayedJobGroups: JobGroup[] }>()\n\nconst emit = defineEmits<{\n (e: 'cancelItem', item: JobListItem): void\n (e: 'deleteItem', item: JobListItem): void\n (e: 'menu', item: JobListItem, ev: MouseEvent): void\n (e: 'viewItem', item: JobListItem): void\n}>()\n\nconst emitCancelItem = (item: JobListItem) => {\n emit('cancelItem', item)\n}\n\nconst emitDeleteItem = (item: JobListItem) => {\n emit('deleteItem', item)\n}\n\nconst activeDetailsId = ref<string | null>(null)\nconst hideTimer = ref<number | null>(null)\nconst showTimer = ref<number | null>(null)\nconst clearHideTimer = () => {\n if (hideTimer.value !== null) {\n clearTimeout(hideTimer.value)\n hideTimer.value = null\n }\n}\nconst clearShowTimer = () => {\n if (showTimer.value !== null) {\n clearTimeout(showTimer.value)\n showTimer.value = null\n }\n}\nconst onDetailsEnter = (jobId: string) => {\n clearHideTimer()\n clearShowTimer()\n showTimer.value = window.setTimeout(() => {\n activeDetailsId.value = jobId\n showTimer.value = null\n }, 200)\n}\nconst onDetailsLeave = (jobId: string) => {\n clearHideTimer()\n clearShowTimer()\n hideTimer.value = window.setTimeout(() => {\n if (activeDetailsId.value === jobId) activeDetailsId.value = null\n hideTimer.value = null\n }, 150)\n}\n\nconst resetActiveDetails = () => {\n clearHideTimer()\n clearShowTimer()\n activeDetailsId.value = null\n}\n\nwatch(\n () => props.displayedJobGroups,\n (groups) => {\n const activeId = activeDetailsId.value\n if (!activeId) return\n\n const hasActiveJob = groups.some((group) =>\n group.items.some((item) => item.id === activeId)\n )\n\n if (!hasActiveJob) resetActiveDetails()\n }\n)\n\nonBeforeUnmount(resetActiveDetails)\n</script>\n","<template>\n <div class=\"flex w-full flex-col gap-4\">\n <QueueOverlayHeader\n :header-title=\"headerTitle\"\n :show-concurrent-indicator=\"showConcurrentIndicator\"\n :concurrent-workflow-count=\"concurrentWorkflowCount\"\n @clear-history=\"$emit('clearHistory')\"\n />\n\n <div class=\"flex items-center justify-between px-3\">\n <Button\n class=\"grow gap-1 justify-center\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"$emit('showAssets')\"\n >\n <i class=\"icon-[comfy--image-ai-edit] size-4\" />\n <span>{{ t('sideToolbar.queueProgressOverlay.showAssets') }}</span>\n </Button>\n <div class=\"ml-4 inline-flex items-center\">\n <div\n class=\"inline-flex h-6 items-center text-[12px] leading-none text-text-primary opacity-90\"\n >\n <span class=\"font-bold\">{{ queuedCount }}</span>\n <span class=\"ml-1\">{{\n t('sideToolbar.queueProgressOverlay.queuedSuffix')\n }}</span>\n </div>\n <Button\n v-if=\"queuedCount > 0\"\n class=\"ml-2\"\n variant=\"destructive\"\n size=\"icon\"\n :aria-label=\"t('sideToolbar.queueProgressOverlay.clearQueued')\"\n @click=\"$emit('clearQueued')\"\n >\n <i class=\"icon-[lucide--list-x] size-4\" />\n </Button>\n </div>\n </div>\n\n <JobFiltersBar\n :selected-job-tab=\"selectedJobTab\"\n :selected-workflow-filter=\"selectedWorkflowFilter\"\n :selected-sort-mode=\"selectedSortMode\"\n :has-failed-jobs=\"hasFailedJobs\"\n @update:selected-job-tab=\"$emit('update:selectedJobTab', $event)\"\n @update:selected-workflow-filter=\"\n $emit('update:selectedWorkflowFilter', $event)\n \"\n @update:selected-sort-mode=\"$emit('update:selectedSortMode', $event)\"\n />\n\n <div class=\"flex-1 min-h-0 overflow-y-auto\">\n <JobGroupsList\n :displayed-job-groups=\"displayedJobGroups\"\n @cancel-item=\"onCancelItemEvent\"\n @delete-item=\"onDeleteItemEvent\"\n @view-item=\"$emit('viewItem', $event)\"\n @menu=\"onMenuItem\"\n />\n </div>\n\n <JobContextMenu\n ref=\"jobContextMenuRef\"\n :entries=\"jobMenuEntries\"\n @action=\"onJobMenuAction\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport type {\n JobGroup,\n JobListItem,\n JobSortMode,\n JobTab\n} from '@/composables/queue/useJobList'\nimport type { MenuEntry } from '@/composables/queue/useJobMenu'\nimport { useJobMenu } from '@/composables/queue/useJobMenu'\n\nimport QueueOverlayHeader from './QueueOverlayHeader.vue'\nimport JobContextMenu from './job/JobContextMenu.vue'\nimport JobFiltersBar from './job/JobFiltersBar.vue'\nimport JobGroupsList from './job/JobGroupsList.vue'\n\ndefineProps<{\n headerTitle: string\n showConcurrentIndicator: boolean\n concurrentWorkflowCount: number\n queuedCount: number\n selectedJobTab: JobTab\n selectedWorkflowFilter: 'all' | 'current'\n selectedSortMode: JobSortMode\n displayedJobGroups: JobGroup[]\n hasFailedJobs: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'showAssets'): void\n (e: 'clearHistory'): void\n (e: 'clearQueued'): void\n (e: 'update:selectedJobTab', value: JobTab): void\n (e: 'update:selectedWorkflowFilter', value: 'all' | 'current'): void\n (e: 'update:selectedSortMode', value: JobSortMode): void\n (e: 'cancelItem', item: JobListItem): void\n (e: 'deleteItem', item: JobListItem): void\n (e: 'viewItem', item: JobListItem): void\n}>()\n\nconst { t } = useI18n()\n\nconst currentMenuItem = ref<JobListItem | null>(null)\nconst jobContextMenuRef = ref<InstanceType<typeof JobContextMenu> | null>(null)\n\nconst { jobMenuEntries } = useJobMenu(\n () => currentMenuItem.value,\n (item) => emit('viewItem', item)\n)\n\nconst onCancelItemEvent = (item: JobListItem) => {\n emit('cancelItem', item)\n}\n\nconst onDeleteItemEvent = (item: JobListItem) => {\n emit('deleteItem', item)\n}\n\nconst onMenuItem = (item: JobListItem, event: Event) => {\n currentMenuItem.value = item\n jobContextMenuRef.value?.open(event)\n}\n\nconst onJobMenuAction = async (entry: MenuEntry) => {\n if (entry.kind === 'divider') return\n if (entry.onClick) await entry.onClick()\n jobContextMenuRef.value?.hide()\n}\n</script>\n","<template>\n <section\n class=\"w-[360px] rounded-2xl border border-interface-stroke bg-interface-panel-surface text-text-primary shadow-interface font-inter\"\n >\n <header\n class=\"flex items-center justify-between border-b border-interface-stroke px-4 py-4\"\n >\n <p class=\"m-0 text-[14px] font-normal leading-none\">\n {{ t('sideToolbar.queueProgressOverlay.clearHistoryDialogTitle') }}\n </p>\n <Button\n size=\"icon\"\n variant=\"muted-textonly\"\n :aria-label=\"t('g.close')\"\n @click=\"onCancel\"\n >\n <i class=\"icon-[lucide--x] block size-4 leading-none\" />\n </Button>\n </header>\n\n <div class=\"flex flex-col gap-4 px-4 py-4 text-[14px] text-text-secondary\">\n <p class=\"m-0\">\n {{\n t('sideToolbar.queueProgressOverlay.clearHistoryDialogDescription')\n }}\n </p>\n <p class=\"m-0\">\n {{ t('sideToolbar.queueProgressOverlay.clearHistoryDialogAssetsNote') }}\n </p>\n </div>\n\n <footer class=\"flex items-center justify-end px-4 py-4\">\n <div class=\"flex items-center gap-4 leading-none\">\n <Button variant=\"muted-textonly\" size=\"lg\" @click=\"onCancel\">\n {{ t('g.cancel') }}\n </Button>\n <Button\n variant=\"secondary\"\n size=\"lg\"\n :disabled=\"isClearing\"\n @click=\"onConfirm\"\n >{{ t('g.clear') }}</Button\n >\n </div>\n </footer>\n </section>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport { useQueueStore } from '@/stores/queueStore'\n\nconst dialogStore = useDialogStore()\nconst queueStore = useQueueStore()\nconst { t } = useI18n()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\n\nconst isClearing = ref(false)\n\nconst clearHistory = wrapWithErrorHandlingAsync(\n async () => {\n await queueStore.clear(['history'])\n dialogStore.closeDialog()\n },\n undefined,\n () => {\n isClearing.value = false\n }\n)\n\nconst onConfirm = async () => {\n if (isClearing.value) return\n isClearing.value = true\n await clearHistory()\n}\n\nconst onCancel = () => {\n dialogStore.closeDialog()\n}\n</script>\n","import { computed, ref, watch } from 'vue'\n\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useQueueStore } from '@/stores/queueStore'\nimport { jobStateFromTask } from '@/utils/queueUtil'\n\nexport type CompletionSummaryMode = 'allSuccess' | 'mixed' | 'allFailed'\n\nexport type CompletionSummary = {\n mode: CompletionSummaryMode\n completedCount: number\n failedCount: number\n thumbnailUrls: string[]\n}\n\n/**\n * Tracks queue activity transitions and exposes a short-lived summary of the\n * most recent generation batch.\n */\nexport const useCompletionSummary = () => {\n const queueStore = useQueueStore()\n const executionStore = useExecutionStore()\n\n const isActive = computed(\n () => queueStore.runningTasks.length > 0 || !executionStore.isIdle\n )\n\n const lastActiveStartTs = ref<number | null>(null)\n const _summary = ref<CompletionSummary | null>(null)\n const dismissTimer = ref<number | null>(null)\n\n const clearDismissTimer = () => {\n if (dismissTimer.value !== null) {\n clearTimeout(dismissTimer.value)\n dismissTimer.value = null\n }\n }\n\n const startDismissTimer = () => {\n clearDismissTimer()\n dismissTimer.value = window.setTimeout(() => {\n _summary.value = null\n dismissTimer.value = null\n }, 6000)\n }\n\n const clearSummary = () => {\n _summary.value = null\n clearDismissTimer()\n }\n\n watch(\n isActive,\n (active, prev) => {\n if (!prev && active) {\n lastActiveStartTs.value = Date.now()\n }\n if (prev && !active) {\n const start = lastActiveStartTs.value ?? 0\n const finished = queueStore.historyTasks.filter((t: any) => {\n const ts: number | undefined = t.executionEndTimestamp\n return typeof ts === 'number' && ts >= start\n })\n\n if (!finished.length) {\n _summary.value = null\n clearDismissTimer()\n return\n }\n\n let completedCount = 0\n let failedCount = 0\n const imagePreviews: string[] = []\n\n for (const task of finished) {\n const state = jobStateFromTask(task, false)\n if (state === 'completed') {\n completedCount++\n const preview = task.previewOutput\n if (preview?.isImage) {\n imagePreviews.push(preview.urlWithTimestamp)\n }\n } else if (state === 'failed') {\n failedCount++\n }\n }\n\n if (completedCount === 0 && failedCount === 0) {\n _summary.value = null\n clearDismissTimer()\n return\n }\n\n let mode: CompletionSummaryMode = 'mixed'\n if (failedCount === 0) mode = 'allSuccess'\n else if (completedCount === 0) mode = 'allFailed'\n\n _summary.value = {\n mode,\n completedCount,\n failedCount,\n thumbnailUrls: imagePreviews.slice(0, 3)\n }\n startDismissTimer()\n }\n },\n { immediate: true }\n )\n\n const summary = computed(() => _summary.value)\n\n return {\n summary,\n clearSummary\n }\n}\n","import { ref, shallowRef } from 'vue'\n\nimport type { JobListItem } from '@/composables/queue/useJobList'\nimport type { ResultItemImpl } from '@/stores/queueStore'\n\n/**\n * Manages result gallery state and activation for queue items.\n */\nexport function useResultGallery(getFilteredTasks: () => any[]) {\n const galleryActiveIndex = ref(-1)\n const galleryItems = shallowRef<ResultItemImpl[]>([])\n\n const onViewItem = (item: JobListItem) => {\n const items: ResultItemImpl[] = getFilteredTasks().flatMap((t: any) => {\n const preview = t.previewOutput\n return preview && preview.supportsPreview ? [preview] : []\n })\n\n if (!items.length) return\n\n galleryItems.value = items\n const activeUrl: string | undefined = item.taskRef?.previewOutput?.url\n const idx = activeUrl ? items.findIndex((o) => o.url === activeUrl) : 0\n galleryActiveIndex.value = idx >= 0 ? idx : 0\n }\n\n return {\n galleryActiveIndex,\n galleryItems,\n onViewItem\n }\n}\n","<template>\n <div\n v-show=\"isVisible\"\n :class=\"['flex', 'justify-end', 'w-full', 'pointer-events-none']\"\n >\n <div\n class=\"pointer-events-auto flex w-[350px] min-w-[310px] max-h-[60vh] flex-col overflow-hidden rounded-lg border font-inter transition-colors duration-200 ease-in-out\"\n :class=\"containerClass\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <!-- Expanded state -->\n <QueueOverlayExpanded\n v-if=\"isExpanded\"\n v-model:selected-job-tab=\"selectedJobTab\"\n v-model:selected-workflow-filter=\"selectedWorkflowFilter\"\n v-model:selected-sort-mode=\"selectedSortMode\"\n class=\"flex-1 min-h-0\"\n :header-title=\"headerTitle\"\n :show-concurrent-indicator=\"showConcurrentIndicator\"\n :concurrent-workflow-count=\"concurrentWorkflowCount\"\n :queued-count=\"queuedCount\"\n :displayed-job-groups=\"displayedJobGroups\"\n :has-failed-jobs=\"hasFailedJobs\"\n @show-assets=\"openAssetsSidebar\"\n @clear-history=\"onClearHistoryFromMenu\"\n @clear-queued=\"cancelQueuedWorkflows\"\n @cancel-item=\"onCancelItem\"\n @delete-item=\"onDeleteItem\"\n @view-item=\"inspectJobAsset\"\n />\n\n <QueueOverlayActive\n v-else-if=\"hasActiveJob\"\n :total-progress-style=\"totalProgressStyle\"\n :current-node-progress-style=\"currentNodeProgressStyle\"\n :total-percent-formatted=\"totalPercentFormatted\"\n :current-node-percent-formatted=\"currentNodePercentFormatted\"\n :current-node-name=\"currentNodeName\"\n :running-count=\"runningCount\"\n :queued-count=\"queuedCount\"\n :bottom-row-class=\"bottomRowClass\"\n @interrupt-all=\"interruptAll\"\n @clear-queued=\"cancelQueuedWorkflows\"\n @view-all-jobs=\"viewAllJobs\"\n />\n\n <QueueOverlayEmpty\n v-else-if=\"completionSummary\"\n :summary=\"completionSummary\"\n @summary-click=\"onSummaryClick\"\n />\n </div>\n </div>\n\n <ResultGallery\n v-model:active-index=\"galleryActiveIndex\"\n :all-gallery-items=\"galleryItems\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport QueueOverlayActive from '@/components/queue/QueueOverlayActive.vue'\nimport QueueOverlayEmpty from '@/components/queue/QueueOverlayEmpty.vue'\nimport QueueOverlayExpanded from '@/components/queue/QueueOverlayExpanded.vue'\nimport QueueClearHistoryDialog from '@/components/queue/dialogs/QueueClearHistoryDialog.vue'\nimport ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'\nimport { useCompletionSummary } from '@/composables/queue/useCompletionSummary'\nimport { useJobList } from '@/composables/queue/useJobList'\nimport type { JobListItem } from '@/composables/queue/useJobList'\nimport { useQueueProgress } from '@/composables/queue/useQueueProgress'\nimport { useResultGallery } from '@/composables/queue/useResultGallery'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useAssetSelectionStore } from '@/platform/assets/composables/useAssetSelectionStore'\nimport { isCloud } from '@/platform/distribution/types'\nimport { api } from '@/scripts/api'\nimport { useAssetsStore } from '@/stores/assetsStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useQueueStore } from '@/stores/queueStore'\nimport { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'\n\ntype OverlayState = 'hidden' | 'empty' | 'active' | 'expanded'\n\nconst props = withDefaults(\n defineProps<{\n expanded?: boolean\n menuHovered?: boolean\n }>(),\n {\n menuHovered: false\n }\n)\n\nconst emit = defineEmits<{\n (e: 'update:expanded', value: boolean): void\n}>()\n\nconst { t } = useI18n()\nconst queueStore = useQueueStore()\nconst commandStore = useCommandStore()\nconst executionStore = useExecutionStore()\nconst sidebarTabStore = useSidebarTabStore()\nconst dialogStore = useDialogStore()\nconst assetsStore = useAssetsStore()\nconst assetSelectionStore = useAssetSelectionStore()\nconst { wrapWithErrorHandlingAsync } = useErrorHandling()\n\nconst {\n totalPercentFormatted,\n currentNodePercentFormatted,\n totalProgressStyle,\n currentNodeProgressStyle\n} = useQueueProgress()\nconst isHovered = ref(false)\nconst isOverlayHovered = computed(() => isHovered.value || props.menuHovered)\nconst internalExpanded = ref(false)\nconst isExpanded = computed({\n get: () =>\n props.expanded === undefined ? internalExpanded.value : props.expanded,\n set: (value) => {\n if (props.expanded === undefined) {\n internalExpanded.value = value\n }\n emit('update:expanded', value)\n }\n})\n\nconst { summary: completionSummary, clearSummary } = useCompletionSummary()\nconst hasCompletionSummary = computed(() => completionSummary.value !== null)\n\nconst runningCount = computed(() => queueStore.runningTasks.length)\nconst queuedCount = computed(() => queueStore.pendingTasks.length)\nconst isExecuting = computed(() => !executionStore.isIdle)\nconst hasActiveJob = computed(() => runningCount.value > 0 || isExecuting.value)\nconst activeJobsCount = computed(() => runningCount.value + queuedCount.value)\n\nconst overlayState = computed<OverlayState>(() => {\n if (isExpanded.value) return 'expanded'\n if (hasActiveJob.value) return 'active'\n if (hasCompletionSummary.value) return 'empty'\n return 'hidden'\n})\n\nconst showBackground = computed(\n () =>\n overlayState.value === 'expanded' ||\n overlayState.value === 'empty' ||\n (overlayState.value === 'active' && isOverlayHovered.value)\n)\n\nconst isVisible = computed(() => overlayState.value !== 'hidden')\n\nconst containerClass = computed(() =>\n showBackground.value\n ? 'border-interface-stroke bg-interface-panel-surface shadow-interface'\n : 'border-transparent bg-transparent shadow-none'\n)\n\nconst bottomRowClass = computed(\n () =>\n `flex items-center justify-end gap-4 transition-opacity duration-200 ease-in-out ${\n overlayState.value === 'active' && isOverlayHovered.value\n ? 'opacity-100 pointer-events-auto'\n : 'opacity-0 pointer-events-none'\n }`\n)\nconst headerTitle = computed(() =>\n hasActiveJob.value\n ? `${activeJobsCount.value} ${t('sideToolbar.queueProgressOverlay.activeJobsSuffix')}`\n : t('sideToolbar.queueProgressOverlay.jobQueue')\n)\n\nconst concurrentWorkflowCount = computed(\n () => executionStore.runningWorkflowCount\n)\nconst showConcurrentIndicator = computed(\n () => concurrentWorkflowCount.value > 1\n)\n\nconst {\n selectedJobTab,\n selectedWorkflowFilter,\n selectedSortMode,\n hasFailedJobs,\n filteredTasks,\n groupedJobItems,\n currentNodeName\n} = useJobList()\n\nconst displayedJobGroups = computed(() => groupedJobItems.value)\n\nconst onCancelItem = wrapWithErrorHandlingAsync(async (item: JobListItem) => {\n const promptId = item.taskRef?.promptId\n if (!promptId) return\n\n if (item.state === 'running' || item.state === 'initialization') {\n // Running/initializing jobs: interrupt execution\n await api.interrupt(promptId)\n await queueStore.update()\n } else if (item.state === 'pending') {\n // Pending jobs: remove from queue\n await api.deleteItem('queue', promptId)\n await queueStore.update()\n }\n})\n\nconst onDeleteItem = wrapWithErrorHandlingAsync(async (item: JobListItem) => {\n if (!item.taskRef) return\n await queueStore.delete(item.taskRef)\n})\n\nconst {\n galleryActiveIndex,\n galleryItems,\n onViewItem: openResultGallery\n} = useResultGallery(() => filteredTasks.value)\n\nconst setExpanded = (expanded: boolean) => {\n isExpanded.value = expanded\n}\n\nconst openExpandedFromEmpty = () => {\n setExpanded(true)\n}\n\nconst viewAllJobs = () => {\n setExpanded(true)\n}\n\nconst onSummaryClick = () => {\n openExpandedFromEmpty()\n clearSummary()\n}\n\nconst openAssetsSidebar = () => {\n sidebarTabStore.activeSidebarTabId = 'assets'\n}\n\nconst focusAssetInSidebar = async (item: JobListItem) => {\n const task = item.taskRef\n const promptId = task?.promptId\n const preview = task?.previewOutput\n if (!promptId || !preview) return\n\n const assetId = String(promptId)\n openAssetsSidebar()\n await nextTick()\n await assetsStore.updateHistory()\n const asset = assetsStore.historyAssets.find(\n (existingAsset) => existingAsset.id === assetId\n )\n if (!asset) {\n throw new Error('Asset not found in media assets panel')\n }\n assetSelectionStore.setSelection([assetId])\n}\n\nconst inspectJobAsset = wrapWithErrorHandlingAsync(\n async (item: JobListItem) => {\n openResultGallery(item)\n await focusAssetInSidebar(item)\n }\n)\n\nconst cancelQueuedWorkflows = wrapWithErrorHandlingAsync(async () => {\n await commandStore.execute('Comfy.ClearPendingTasks')\n})\n\nconst interruptAll = wrapWithErrorHandlingAsync(async () => {\n const tasks = queueStore.runningTasks\n const promptIds = tasks\n .map((task) => task.promptId)\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n\n if (!promptIds.length) return\n\n // Cloud backend supports cancelling specific jobs via /queue delete,\n // while /interrupt always targets the \"first\" job. Use the targeted API\n // on cloud to ensure we cancel the workflow the user clicked.\n if (isCloud) {\n await Promise.all(promptIds.map((id) => api.deleteItem('queue', id)))\n return\n }\n\n await Promise.all(promptIds.map((id) => api.interrupt(id)))\n})\n\nconst showClearHistoryDialog = () => {\n dialogStore.showDialog({\n key: 'queue-clear-history',\n component: QueueClearHistoryDialog,\n dialogComponentProps: {\n headless: true,\n closable: false,\n closeOnEscape: true,\n dismissableMask: true,\n pt: {\n root: {\n class: 'max-w-[360px] w-auto bg-transparent border-none shadow-none'\n },\n content: {\n class: '!p-0 bg-transparent'\n }\n }\n }\n })\n}\n\nconst onClearHistoryFromMenu = () => {\n showClearHistoryDialog()\n}\n</script>\n","import { defineStore } from 'pinia'\nimport { computed } from 'vue'\n\nimport type { ActionBarButton } from '@/types/comfy'\n\nimport { useExtensionStore } from './extensionStore'\n\nexport const useActionBarButtonStore = defineStore('actionBarButton', () => {\n const extensionStore = useExtensionStore()\n\n const buttons = computed<ActionBarButton[]>(() =>\n extensionStore.extensions.flatMap((e) => e.actionBarButtons ?? [])\n )\n\n return {\n buttons\n }\n})\n","<template>\n <div class=\"flex h-full shrink-0 items-center gap-1\">\n <Button\n v-for=\"(button, index) in actionBarButtonStore.buttons\"\n :key=\"index\"\n v-tooltip.bottom=\"button.tooltip\"\n :aria-label=\"button.tooltip || button.label\"\n :class=\"button.class\"\n variant=\"muted-textonly\"\n size=\"sm\"\n class=\"h-7 rounded-full\"\n @click=\"button.onClick\"\n >\n <i :class=\"button.icon\" />\n <span v-if=\"!isMobile && button.label\">{{ button.label }}</span>\n </Button>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useActionBarButtonStore } from '@/stores/actionBarButtonStore'\n\nconst actionBarButtonStore = useActionBarButtonStore()\n\nconst breakpoints = useBreakpoints(breakpointsTailwind)\nconst isMobile = breakpoints.smaller('sm')\n</script>\n","<!-- A popover that shows current user information and actions -->\n<template>\n <div\n class=\"current-user-popover w-80 -m-3 p-2 rounded-lg border border-border-default bg-base-background shadow-[1px_1px_8px_0_rgba(0,0,0,0.4)]\"\n >\n <!-- User Info Section -->\n <div class=\"flex flex-col items-center px-0 py-3 mb-4\">\n <UserAvatar\n class=\"mb-1\"\n :photo-url=\"userPhotoUrl\"\n :pt:icon:class=\"{\n 'text-2xl!': !userPhotoUrl\n }\"\n size=\"large\"\n />\n\n <!-- User Details -->\n <h3 class=\"my-0 mb-1 truncate text-base font-bold text-base-foreground\">\n {{ userDisplayName || $t('g.user') }}\n </h3>\n <p v-if=\"userEmail\" class=\"my-0 truncate text-sm text-muted\">\n {{ userEmail }}\n </p>\n <span\n v-if=\"subscriptionTierName\"\n class=\"my-0 text-xs text-foreground bg-secondary-background-hover rounded-full uppercase px-2 py-0.5 font-bold mt-2\"\n >\n {{ subscriptionTierName }}\n </span>\n </div>\n\n <!-- Credits Section -->\n <div v-if=\"isActiveSubscription\" class=\"flex items-center gap-2 px-4 py-2\">\n <i class=\"icon-[lucide--component] text-amber-400 text-sm\" />\n <Skeleton\n v-if=\"authStore.isFetchingBalance\"\n width=\"4rem\"\n height=\"1.25rem\"\n class=\"w-full\"\n />\n <span v-else class=\"text-base font-semibold text-base-foreground\">{{\n formattedBalance\n }}</span>\n <i\n v-tooltip=\"{ value: $t('credits.unified.tooltip'), showDelay: 300 }\"\n class=\"icon-[lucide--circle-help] cursor-help text-base text-muted-foreground mr-auto\"\n />\n <Button\n variant=\"secondary\"\n size=\"sm\"\n class=\"text-base-foreground\"\n data-testid=\"add-credits-button\"\n @click=\"handleTopUp\"\n >\n {{ $t('subscription.addCredits') }}\n </Button>\n </div>\n\n <div v-else class=\"flex justify-center px-4\">\n <SubscribeButton\n :fluid=\"false\"\n :label=\"$t('subscription.subscribeToComfyCloud')\"\n size=\"sm\"\n variant=\"gradient\"\n @subscribed=\"handleSubscribed\"\n />\n </div>\n\n <Divider class=\"my-2 mx-0\" />\n\n <div\n v-if=\"isActiveSubscription\"\n class=\"flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover\"\n data-testid=\"partner-nodes-menu-item\"\n @click=\"handleOpenPartnerNodesInfo\"\n >\n <i class=\"icon-[lucide--tag] text-muted-foreground text-sm\" />\n <span class=\"text-sm text-base-foreground flex-1\">{{\n $t('subscription.partnerNodesCredits')\n }}</span>\n </div>\n\n <div\n class=\"flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover\"\n data-testid=\"plans-pricing-menu-item\"\n @click=\"handleOpenPlansAndPricing\"\n >\n <i class=\"icon-[lucide--receipt-text] text-muted-foreground text-sm\" />\n <span class=\"text-sm text-base-foreground flex-1\">{{\n $t('subscription.plansAndPricing')\n }}</span>\n <span\n v-if=\"canUpgrade\"\n class=\"text-xs font-bold text-base-background bg-base-foreground px-1.5 py-0.5 rounded-full\"\n >\n {{ $t('subscription.upgrade') }}\n </span>\n </div>\n\n <div\n v-if=\"isActiveSubscription\"\n class=\"flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover\"\n data-testid=\"manage-plan-menu-item\"\n @click=\"handleOpenPlanAndCreditsSettings\"\n >\n <i class=\"icon-[lucide--file-text] text-muted-foreground text-sm\" />\n <span class=\"text-sm text-base-foreground flex-1\">{{\n $t('subscription.managePlan')\n }}</span>\n </div>\n\n <div\n class=\"flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover\"\n data-testid=\"user-settings-menu-item\"\n @click=\"handleOpenUserSettings\"\n >\n <i class=\"icon-[lucide--settings-2] text-muted-foreground text-sm\" />\n <span class=\"text-sm text-base-foreground flex-1\">{{\n $t('userSettings.accountSettings')\n }}</span>\n </div>\n\n <Divider class=\"my-2 mx-0\" />\n\n <div\n class=\"flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-secondary-background-hover\"\n data-testid=\"logout-menu-item\"\n @click=\"handleLogout\"\n >\n <i class=\"icon-[lucide--log-out] text-muted-foreground text-sm\" />\n <span class=\"text-sm text-base-foreground flex-1\">{{\n $t('auth.signOut.signOut')\n }}</span>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Divider from 'primevue/divider'\nimport Skeleton from 'primevue/skeleton'\nimport { computed, onMounted } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { formatCreditsFromCents } from '@/base/credits/comfyCredits'\nimport UserAvatar from '@/components/common/UserAvatar.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport SubscribeButton from '@/platform/cloud/subscription/components/SubscribeButton.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'\nimport { isCloud } from '@/platform/distribution/types'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useDialogService } from '@/services/dialogService'\nimport { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'\n\nconst emit = defineEmits<{\n close: []\n}>()\n\nconst { buildDocsUrl, docsPaths } = useExternalLink()\n\nconst { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =\n useCurrentUser()\nconst authActions = useFirebaseAuthActions()\nconst authStore = useFirebaseAuthStore()\nconst dialogService = useDialogService()\nconst {\n isActiveSubscription,\n subscriptionTierName,\n subscriptionTier,\n fetchStatus\n} = useSubscription()\nconst subscriptionDialog = useSubscriptionDialog()\nconst { locale } = useI18n()\n\nconst formattedBalance = computed(() => {\n const cents =\n authStore.balance?.effective_balance_micros ??\n authStore.balance?.amount_micros ??\n 0\n return formatCreditsFromCents({\n cents,\n locale: locale.value,\n numberOptions: {\n minimumFractionDigits: 0,\n maximumFractionDigits: 2\n }\n })\n})\n\nconst canUpgrade = computed(() => {\n const tier = subscriptionTier.value\n return (\n tier === 'FOUNDERS_EDITION' || tier === 'STANDARD' || tier === 'CREATOR'\n )\n})\n\nconst handleOpenUserSettings = () => {\n dialogService.showSettingsDialog('user')\n emit('close')\n}\n\nconst handleOpenPlansAndPricing = () => {\n subscriptionDialog.show()\n emit('close')\n}\n\nconst handleOpenPlanAndCreditsSettings = () => {\n if (isCloud) {\n dialogService.showSettingsDialog('subscription')\n } else {\n dialogService.showSettingsDialog('credits')\n }\n\n emit('close')\n}\n\nconst handleTopUp = () => {\n // Track purchase credits entry from avatar popover\n useTelemetry()?.trackAddApiCreditButtonClicked()\n dialogService.showTopUpCreditsDialog()\n emit('close')\n}\n\nconst handleOpenPartnerNodesInfo = () => {\n window.open(\n buildDocsUrl(docsPaths.partnerNodesPricing, { includeLocale: true }),\n '_blank'\n )\n emit('close')\n}\n\nconst handleLogout = async () => {\n await handleSignOut()\n emit('close')\n}\n\nconst handleSubscribed = async () => {\n await fetchStatus()\n}\n\nonMounted(() => {\n void authActions.fetchBalance()\n})\n</script>\n","<!-- A button that shows current authenticated user's avatar -->\n<template>\n <div>\n <Button\n v-if=\"isLoggedIn\"\n class=\"p-1 hover:bg-transparent\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('g.currentUser')\"\n @click=\"popover?.toggle($event)\"\n >\n <div\n :class=\"\n cn(\n 'flex items-center gap-1 rounded-full hover:bg-interface-button-hover-surface justify-center',\n compact && 'size-full '\n )\n \"\n >\n <UserAvatar :photo-url=\"photoURL\" :class=\"compact && 'size-full'\" />\n\n <i v-if=\"showArrow\" class=\"icon-[lucide--chevron-down] size-3 px-1\" />\n </div>\n </Button>\n\n <Popover\n ref=\"popover\"\n :show-arrow=\"false\"\n :pt=\"{\n root: {\n class: 'rounded-lg'\n }\n }\"\n >\n <CurrentUserPopover @close=\"closePopover\" />\n </Popover>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport { computed, ref } from 'vue'\n\nimport UserAvatar from '@/components/common/UserAvatar.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { cn } from '@/utils/tailwindUtil'\n\nimport CurrentUserPopover from './CurrentUserPopover.vue'\n\nconst { showArrow = true, compact = false } = defineProps<{\n showArrow?: boolean\n compact?: boolean\n}>()\n\nconst { isLoggedIn, userPhotoUrl } = useCurrentUser()\n\nconst popover = ref<InstanceType<typeof Popover> | null>(null)\nconst photoURL = computed<string | undefined>(\n () => userPhotoUrl.value ?? undefined\n)\n\nconst closePopover = () => {\n popover.value?.hide()\n}\n</script>\n","<template>\n <Button\n v-if=\"!isLoggedIn\"\n variant=\"textonly\"\n size=\"icon\"\n :class=\"cn('group rounded-full text-base-foreground p-0', className)\"\n :aria-label=\"t('g.login')\"\n @click=\"handleSignIn()\"\n @mouseenter=\"showPopover\"\n @mouseleave=\"hidePopover\"\n >\n <span\n class=\"flex size-full items-center justify-center rounded-full bg-secondary-background transition-colors group-hover:bg-transparent\"\n >\n <i class=\"icon-[lucide--user] size-4\" />\n </span>\n </Button>\n <Popover\n ref=\"popoverRef\"\n class=\"p-2\"\n @mouseout=\"hidePopover\"\n @mouseover=\"cancelHidePopover\"\n >\n <div>\n <div class=\"mb-1\">{{ t('auth.loginButton.tooltipHelp') }}</div>\n <a\n :href=\"apiNodesOverviewUrl\"\n target=\"_blank\"\n class=\"text-neutral-500 hover:text-primary\"\n >{{ t('auth.loginButton.tooltipLearnMore') }}</a\n >\n </div>\n </Popover>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport type { HTMLAttributes } from 'vue'\nimport { ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport { t } from '@/i18n'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { class: className } = defineProps<{\n class?: HTMLAttributes['class']\n}>()\n\nconst { isLoggedIn, handleSignIn } = useCurrentUser()\nconst { buildDocsUrl } = useExternalLink()\nconst apiNodesOverviewUrl = buildDocsUrl(\n '/tutorials/api-nodes/overview#api-nodes',\n {\n includeLocale: true\n }\n)\nconst popoverRef = ref<InstanceType<typeof Popover> | null>(null)\nlet hideTimeout: ReturnType<typeof setTimeout> | null = null\nlet showTimeout: ReturnType<typeof setTimeout> | null = null\n\nconst showPopover = (event: Event) => {\n // Clear any existing timeouts\n if (hideTimeout) {\n clearTimeout(hideTimeout)\n hideTimeout = null\n }\n if (showTimeout) {\n clearTimeout(showTimeout)\n showTimeout = null\n }\n\n showTimeout = setTimeout(() => {\n if (popoverRef.value) {\n popoverRef.value.show(event, event.target as HTMLElement)\n }\n }, 200)\n}\n\nconst cancelHidePopover = () => {\n if (hideTimeout) {\n clearTimeout(hideTimeout)\n hideTimeout = null\n }\n}\n\nconst hidePopover = () => {\n // Clear show timeout if mouse leaves before popover appears\n if (showTimeout) {\n clearTimeout(showTimeout)\n showTimeout = null\n }\n\n hideTimeout = setTimeout(() => {\n if (popoverRef.value) {\n popoverRef.value.hide()\n }\n }, 150) // Minimal delay to allow moving to popover\n}\n</script>\n","import type { AxiosError, AxiosResponse } from 'axios'\nimport axios from 'axios'\nimport { ref, watch } from 'vue'\n\nimport { getComfyApiBaseUrl } from '@/config/comfyApi'\nimport type { components, operations } from '@/types/comfyRegistryTypes'\nimport { isAbortError } from '@/utils/typeGuardUtil'\n\n// Use generated types from OpenAPI spec\nexport type ReleaseNote = components['schemas']['ReleaseNote']\ntype GetReleasesParams = operations['getReleaseNotes']['parameters']['query']\n\n// Use generated error response type\ntype ErrorResponse = components['schemas']['ErrorResponse']\n\nconst releaseApiClient = axios.create({\n baseURL: getComfyApiBaseUrl(),\n headers: {\n 'Content-Type': 'application/json'\n }\n})\n\n// Release service for fetching release notes\nexport const useReleaseService = () => {\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n watch(\n () => getComfyApiBaseUrl(),\n (url) => {\n releaseApiClient.defaults.baseURL = url\n }\n )\n\n // No transformation needed - API response matches the generated type\n\n // Handle API errors with context\n const handleApiError = (\n err: unknown,\n context: string,\n routeSpecificErrors?: Record<number, string>\n ): string => {\n if (!axios.isAxiosError(err))\n return err instanceof Error\n ? `${context}: ${err.message}`\n : `${context}: Unknown error occurred`\n\n const axiosError = err as AxiosError<ErrorResponse>\n\n if (axiosError.response) {\n const { status, data } = axiosError.response\n\n if (routeSpecificErrors && routeSpecificErrors[status])\n return routeSpecificErrors[status]\n\n switch (status) {\n case 400:\n return `Bad request: ${data?.message || 'Invalid input'}`\n case 401:\n return 'Unauthorized: Authentication required'\n case 403:\n return `Forbidden: ${data?.message || 'Access denied'}`\n case 404:\n return `Not found: ${data?.message || 'Resource not found'}`\n case 500:\n return `Server error: ${data?.message || 'Internal server error'}`\n default:\n return `${context}: ${data?.message || axiosError.message}`\n }\n }\n\n return `${context}: ${axiosError.message}`\n }\n\n // Execute API request with error handling\n const executeApiRequest = async <T>(\n apiCall: () => Promise<AxiosResponse<T>>,\n errorContext: string,\n routeSpecificErrors?: Record<number, string>\n ): Promise<T | null> => {\n isLoading.value = true\n error.value = null\n\n try {\n const response = await apiCall()\n return response.data\n } catch (err) {\n // Don't treat cancellations as errors\n if (isAbortError(err)) return null\n\n error.value = handleApiError(err, errorContext, routeSpecificErrors)\n return null\n } finally {\n isLoading.value = false\n }\n }\n\n // Fetch release notes from API\n const getReleases = async (\n params: GetReleasesParams,\n signal?: AbortSignal\n ): Promise<ReleaseNote[] | null> => {\n const endpoint = '/releases'\n const errorContext = 'Failed to get releases'\n const routeSpecificErrors = {\n 400: 'Invalid project or version parameter'\n }\n\n const apiResponse = await executeApiRequest(\n () =>\n releaseApiClient.get<ReleaseNote[]>(endpoint, {\n params,\n signal\n }),\n errorContext,\n routeSpecificErrors\n )\n\n return apiResponse\n }\n\n return {\n isLoading,\n error,\n getReleases\n }\n}\n","import { until } from '@vueuse/core'\nimport { defineStore } from 'pinia'\nimport { compare, valid } from 'semver'\nimport { computed, ref } from 'vue'\n\nimport { isCloud } from '@/platform/distribution/types'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useSystemStatsStore } from '@/stores/systemStatsStore'\nimport { isElectron } from '@/utils/envUtil'\nimport { stringToLocale } from '@/utils/formatUtil'\n\nimport { useReleaseService } from './releaseService'\nimport type { ReleaseNote } from './releaseService'\n\n// Store for managing release notes\nexport const useReleaseStore = defineStore('release', () => {\n // State\n const releases = ref<ReleaseNote[]>([])\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n // Services\n const releaseService = useReleaseService()\n const systemStatsStore = useSystemStatsStore()\n const settingStore = useSettingStore()\n\n const currentVersion = computed(() => {\n if (isCloud) {\n return systemStatsStore?.systemStats?.system?.cloud_version ?? ''\n }\n return systemStatsStore?.systemStats?.system?.comfyui_version ?? ''\n })\n\n // Release data from settings\n const locale = computed(() => settingStore.get('Comfy.Locale'))\n const releaseVersion = computed(() =>\n settingStore.get('Comfy.Release.Version')\n )\n const releaseStatus = computed(() => settingStore.get('Comfy.Release.Status'))\n const releaseTimestamp = computed(() =>\n settingStore.get('Comfy.Release.Timestamp')\n )\n const showVersionUpdates = computed(() =>\n settingStore.get('Comfy.Notification.ShowVersionUpdates')\n )\n\n // Most recent release\n const recentRelease = computed(() => {\n return releases.value[0] ?? null\n })\n\n // 3 most recent releases\n const recentReleases = computed(() => {\n return releases.value.slice(0, 3)\n })\n\n // Helper constants\n const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000 // 3 days\n\n const compareVersions = (\n releaseVersion: string,\n currentVer: string\n ): number => {\n if (valid(releaseVersion) && valid(currentVer)) {\n return compare(releaseVersion, currentVer)\n }\n // Non-semver (e.g. git hash): assume different = newer\n return releaseVersion === currentVer ? 0 : 1\n }\n\n // New version available?\n const isNewVersionAvailable = computed(\n () =>\n !!recentRelease.value &&\n compareVersions(\n recentRelease.value.version,\n currentVersion.value || '0.0.0'\n ) > 0\n )\n\n const isLatestVersion = computed(\n () =>\n !!recentRelease.value &&\n compareVersions(\n recentRelease.value.version,\n currentVersion.value || '0.0.0'\n ) === 0\n )\n\n const hasMediumOrHighAttention = computed(() => {\n const attention = recentRelease.value?.attention\n return attention === 'medium' || attention === 'high'\n })\n\n // Show toast if needed\n const shouldShowToast = computed(() => {\n // Only show on desktop version\n if (!isElectron() || isCloud) {\n return false\n }\n\n // Skip if notifications are disabled\n if (!showVersionUpdates.value) {\n return false\n }\n\n if (!isNewVersionAvailable.value) {\n return false\n }\n\n // Skip if low attention\n if (!hasMediumOrHighAttention.value) {\n return false\n }\n\n // Skip if user already skipped or changelog seen\n if (\n releaseVersion.value === recentRelease.value?.version &&\n ['skipped', 'changelog seen'].includes(releaseStatus.value)\n ) {\n return false\n }\n\n return true\n })\n\n // Show red-dot indicator\n const shouldShowRedDot = computed(() => {\n // Only show on desktop version\n if (!isElectron() || isCloud) {\n return false\n }\n\n // Skip if notifications are disabled\n if (!showVersionUpdates.value) {\n return false\n }\n\n // Already latest → no dot\n if (!isNewVersionAvailable.value) {\n return false\n }\n\n const { version } = recentRelease.value\n\n // Changelog seen → clear dot\n if (\n releaseVersion.value === version &&\n releaseStatus.value === 'changelog seen'\n ) {\n return false\n }\n\n // Attention medium / high (levels 2 & 3)\n if (hasMediumOrHighAttention.value) {\n // Persist until changelog is opened\n return true\n }\n\n // Attention low (level 1) and skipped → keep up to 3 d\n if (\n releaseVersion.value === version &&\n releaseStatus.value === 'skipped' &&\n releaseTimestamp.value &&\n Date.now() - releaseTimestamp.value >= THREE_DAYS_MS\n ) {\n return false\n }\n\n // Not skipped → show\n return true\n })\n\n const shouldShowPopup = computed(() => {\n if (!isElectron() && !isCloud) {\n return false\n }\n\n if (!showVersionUpdates.value) {\n return false\n }\n\n if (!recentRelease.value) {\n return false\n }\n\n // Skip version check if current version isn't semver (e.g. git hash)\n const skipVersionCheck = !valid(currentVersion.value)\n if (!skipVersionCheck && !isLatestVersion.value) {\n return false\n }\n\n if (\n releaseVersion.value === recentRelease.value.version &&\n releaseStatus.value === \"what's new seen\"\n ) {\n return false\n }\n\n return true\n })\n\n // Action handlers for user interactions\n async function handleSkipRelease(version: string): Promise<void> {\n if (\n version !== recentRelease.value?.version ||\n releaseStatus.value === 'changelog seen'\n ) {\n return\n }\n\n await settingStore.set('Comfy.Release.Version', version)\n await settingStore.set('Comfy.Release.Status', 'skipped')\n await settingStore.set('Comfy.Release.Timestamp', Date.now())\n }\n\n async function handleShowChangelog(version: string): Promise<void> {\n if (version !== recentRelease.value?.version) {\n return\n }\n\n await settingStore.set('Comfy.Release.Version', version)\n await settingStore.set('Comfy.Release.Status', 'changelog seen')\n await settingStore.set('Comfy.Release.Timestamp', Date.now())\n }\n\n async function handleWhatsNewSeen(version: string): Promise<void> {\n if (version !== recentRelease.value?.version) {\n return\n }\n\n await settingStore.set('Comfy.Release.Version', version)\n await settingStore.set('Comfy.Release.Status', \"what's new seen\")\n await settingStore.set('Comfy.Release.Timestamp', Date.now())\n }\n\n // Fetch releases from API\n async function fetchReleases(): Promise<void> {\n if (isLoading.value) {\n return\n }\n\n if (!isCloud && !showVersionUpdates.value) {\n return\n }\n\n // Skip fetching if API nodes are disabled via argv\n if (\n systemStatsStore.systemStats?.system?.argv?.includes(\n '--disable-api-nodes'\n )\n ) {\n return\n }\n isLoading.value = true\n error.value = null\n\n try {\n // Ensure system stats are loaded\n if (!systemStatsStore.systemStats) {\n await until(systemStatsStore.isInitialized)\n }\n\n const fetchedReleases = await releaseService.getReleases({\n project: isCloud ? 'cloud' : 'comfyui',\n current_version: currentVersion.value,\n form_factor: systemStatsStore.getFormFactor(),\n locale: stringToLocale(locale.value)\n })\n\n if (fetchedReleases !== null) {\n releases.value = fetchedReleases\n } else if (releaseService.error.value) {\n error.value = releaseService.error.value\n }\n } catch (err) {\n error.value =\n err instanceof Error ? err.message : 'Unknown error occurred'\n } finally {\n isLoading.value = false\n }\n }\n\n // Initialize store\n async function initialize(): Promise<void> {\n await fetchReleases()\n }\n\n return {\n releases,\n isLoading,\n error,\n recentRelease,\n recentReleases,\n shouldShowToast,\n shouldShowRedDot,\n shouldShowPopup,\n shouldShowUpdateButton: isNewVersionAvailable,\n handleSkipRelease,\n handleShowChangelog,\n handleWhatsNewSeen,\n fetchReleases,\n initialize\n }\n})\n","<template>\n <div\n v-if=\"!workspaceStore.focusMode\"\n class=\"ml-1 flex gap-x-0.5 pt-1\"\n @mouseenter=\"isTopMenuHovered = true\"\n @mouseleave=\"isTopMenuHovered = false\"\n >\n <div class=\"min-w-0 flex-1\">\n <SubgraphBreadcrumb />\n </div>\n\n <div class=\"mx-1 flex flex-col items-end gap-1\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"managerState.shouldShowManagerButtons.value\"\n class=\"pointer-events-auto flex h-12 shrink-0 items-center rounded-lg border border-interface-stroke bg-comfy-menu-bg px-2 shadow-interface\"\n >\n <Button\n v-tooltip.bottom=\"customNodesManagerTooltipConfig\"\n variant=\"secondary\"\n size=\"icon\"\n :aria-label=\"t('menu.customNodesManager')\"\n class=\"relative\"\n @click=\"openCustomNodeManager\"\n >\n <i class=\"icon-[lucide--puzzle] size-4\" />\n <span\n v-if=\"shouldShowRedDot\"\n class=\"absolute top-0.5 right-1 size-2 rounded-full bg-red-500\"\n />\n </Button>\n </div>\n\n <div\n class=\"actionbar-container pointer-events-auto flex gap-2 h-12 items-center rounded-lg border border-interface-stroke bg-comfy-menu-bg px-2 shadow-interface\"\n >\n <ActionBarButtons />\n <!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->\n <div\n ref=\"legacyCommandsContainerRef\"\n class=\"[&:not(:has(*>*:not(:empty)))]:hidden\"\n ></div>\n <ComfyActionbar />\n <Button\n v-tooltip.bottom=\"queueHistoryTooltipConfig\"\n type=\"destructive\"\n size=\"icon\"\n :aria-pressed=\"isQueueOverlayExpanded\"\n :aria-label=\"\n t('sideToolbar.queueProgressOverlay.expandCollapsedQueue')\n \"\n @click=\"toggleQueueOverlay\"\n >\n <i class=\"icon-[lucide--history] size-4\" />\n <span\n v-if=\"queuedCount > 0\"\n class=\"absolute -top-1 -right-1 min-w-[16px] rounded-full bg-primary-background py-0.25 text-[10px] font-medium leading-[14px] text-base-foreground\"\n >\n {{ queuedCount }}\n </span>\n </Button>\n <CurrentUserButton\n v-if=\"isLoggedIn && !isIntegratedTabBar\"\n class=\"shrink-0\"\n />\n <LoginButton v-else-if=\"isDesktop && !isIntegratedTabBar\" />\n <Button\n v-if=\"!isRightSidePanelOpen\"\n v-tooltip.bottom=\"rightSidePanelTooltipConfig\"\n type=\"secondary\"\n size=\"icon\"\n :aria-label=\"t('rightSidePanel.togglePanel')\"\n @click=\"rightSidePanelStore.togglePanel\"\n >\n <i class=\"icon-[lucide--panel-right] size-4\" />\n </Button>\n </div>\n </div>\n <QueueProgressOverlay\n v-model:expanded=\"isQueueOverlayExpanded\"\n :menu-hovered=\"isTopMenuHovered\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { storeToRefs } from 'pinia'\nimport { computed, onMounted, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'\nimport SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'\nimport QueueProgressOverlay from '@/components/queue/QueueProgressOverlay.vue'\nimport ActionBarButtons from '@/components/topbar/ActionBarButtons.vue'\nimport CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'\nimport LoginButton from '@/components/topbar/LoginButton.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { buildTooltipConfig } from '@/composables/useTooltipConfig'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useReleaseStore } from '@/platform/updates/common/releaseStore'\nimport { app } from '@/scripts/app'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useQueueStore, useQueueUIStore } from '@/stores/queueStore'\nimport { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\nimport { isElectron } from '@/utils/envUtil'\nimport { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'\nimport { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'\nimport { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'\n\nconst settingStore = useSettingStore()\nconst workspaceStore = useWorkspaceStore()\nconst rightSidePanelStore = useRightSidePanelStore()\nconst managerState = useManagerState()\nconst { isLoggedIn } = useCurrentUser()\nconst isDesktop = isElectron()\nconst { t } = useI18n()\nconst { toastErrorHandler } = useErrorHandling()\nconst commandStore = useCommandStore()\nconst queueStore = useQueueStore()\nconst queueUIStore = useQueueUIStore()\nconst { isOverlayExpanded: isQueueOverlayExpanded } = storeToRefs(queueUIStore)\nconst releaseStore = useReleaseStore()\nconst { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)\nconst { shouldShowRedDot: shouldShowConflictRedDot } =\n useConflictAcknowledgment()\nconst isTopMenuHovered = ref(false)\nconst queuedCount = computed(() => queueStore.pendingTasks.length)\nconst isIntegratedTabBar = computed(\n () => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'\n)\nconst queueHistoryTooltipConfig = computed(() =>\n buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory'))\n)\nconst customNodesManagerTooltipConfig = computed(() =>\n buildTooltipConfig(t('menu.customNodesManager'))\n)\n\n// Use either release red dot or conflict red dot\nconst shouldShowRedDot = computed((): boolean => {\n const releaseRedDot = showReleaseRedDot.value\n return releaseRedDot || shouldShowConflictRedDot.value\n})\n\n// Right side panel toggle\nconst { isOpen: isRightSidePanelOpen } = storeToRefs(rightSidePanelStore)\nconst rightSidePanelTooltipConfig = computed(() =>\n buildTooltipConfig(t('rightSidePanel.togglePanel'))\n)\n\n// Maintain support for legacy topbar elements attached by custom scripts\nconst legacyCommandsContainerRef = ref<HTMLElement>()\nonMounted(() => {\n if (legacyCommandsContainerRef.value) {\n app.menu.element.style.width = 'fit-content'\n legacyCommandsContainerRef.value.appendChild(app.menu.element)\n }\n})\n\nconst toggleQueueOverlay = () => {\n commandStore.execute('Comfy.Queue.ToggleOverlay')\n}\n\nconst openCustomNodeManager = async () => {\n try {\n await managerState.openManager({\n initialTab: ManagerTab.All,\n showToastOnLegacyError: false\n })\n } catch (error) {\n try {\n toastErrorHandler(error)\n } catch (toastError) {\n console.error(error)\n console.error(toastError)\n }\n }\n}\n</script>\n","<template>\n <component :is=\"extension.component\" v-if=\"extension.type === 'vue'\" />\n <div\n v-else\n :ref=\"\n (el) => {\n if (el)\n mountCustomExtension(\n props.extension as CustomExtension,\n el as HTMLElement\n )\n }\n \"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { onBeforeUnmount } from 'vue'\n\nimport type { CustomExtension, VueExtension } from '@/types/extensionTypes'\n\nconst props = defineProps<{\n extension: VueExtension | CustomExtension\n}>()\n\nconst mountCustomExtension = (extension: CustomExtension, el: HTMLElement) => {\n extension.render(el)\n}\n\nonBeforeUnmount(() => {\n if (props.extension.type === 'custom' && props.extension.destroy) {\n props.extension.destroy()\n }\n})\n</script>\n","<template>\n <div class=\"flex h-full flex-col\">\n <Tabs\n :key=\"$i18n.locale\"\n v-model:value=\"bottomPanelStore.activeBottomPanelTabId\"\n style=\"--p-tabs-tablist-background: var(--comfy-menu-bg)\"\n >\n <TabList\n pt:tab-list=\"border-none h-full flex items-center py-2 border-b-1 border-solid\"\n class=\"bg-transparent\"\n >\n <div class=\"flex w-full justify-between\">\n <div class=\"tabs-container font-inter\">\n <Tab\n v-for=\"tab in bottomPanelStore.bottomPanelTabs\"\n :key=\"tab.id\"\n :value=\"tab.id\"\n class=\"m-1 mx-2 border-none font-inter\"\n :class=\"{\n 'tab-list-single-item':\n bottomPanelStore.bottomPanelTabs.length === 1\n }\"\n :pt:root=\"\n (x: TabPassThroughMethodOptions) => ({\n class: {\n 'p-3 rounded-lg': true,\n 'pointer-events-none':\n bottomPanelStore.bottomPanelTabs.length === 1\n },\n style: {\n color: 'var(--fg-color)',\n backgroundColor:\n !x.context.active ||\n bottomPanelStore.bottomPanelTabs.length === 1\n ? ''\n : 'var(--bg-color)'\n }\n })\n \"\n >\n <span class=\"font-normal\">\n {{ getTabDisplayTitle(tab) }}\n </span>\n </Tab>\n </div>\n <div class=\"flex items-center gap-2\">\n <Button\n v-if=\"isShortcutsTabActive\"\n variant=\"muted-textonly\"\n size=\"sm\"\n @click=\"openKeybindingSettings\"\n >\n <i class=\"pi pi-cog\" />\n {{ $t('shortcuts.manageShortcuts') }}\n </Button>\n <Button\n class=\"justify-self-end\"\n variant=\"muted-textonly\"\n size=\"sm\"\n :aria-label=\"t('g.close')\"\n @click=\"closeBottomPanel\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n </div>\n </div>\n </TabList>\n </Tabs>\n <!-- h-0 to force the div to grow -->\n <div class=\"h-0 grow\">\n <ExtensionSlot\n v-if=\"\n bottomPanelStore.bottomPanelVisible &&\n bottomPanelStore.activeBottomPanelTab\n \"\n :extension=\"bottomPanelStore.activeBottomPanelTab\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Tab from 'primevue/tab'\nimport type { TabPassThroughMethodOptions } from 'primevue/tab'\nimport TabList from 'primevue/tablist'\nimport Tabs from 'primevue/tabs'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport ExtensionSlot from '@/components/common/ExtensionSlot.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useDialogService } from '@/services/dialogService'\nimport { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'\nimport type { BottomPanelExtension } from '@/types/extensionTypes'\n\nconst bottomPanelStore = useBottomPanelStore()\nconst dialogService = useDialogService()\nconst { t } = useI18n()\n\nconst isShortcutsTabActive = computed(() => {\n const activeTabId = bottomPanelStore.activeBottomPanelTabId\n return (\n activeTabId === 'shortcuts-essentials' ||\n activeTabId === 'shortcuts-view-controls'\n )\n})\n\nconst shouldCapitalizeTab = (tabId: string): boolean => {\n return tabId !== 'shortcuts-essentials' && tabId !== 'shortcuts-view-controls'\n}\n\nconst getTabDisplayTitle = (tab: BottomPanelExtension): string => {\n const title = tab.titleKey ? t(tab.titleKey) : tab.title || ''\n return shouldCapitalizeTab(tab.id) ? title.toUpperCase() : title\n}\n\nconst openKeybindingSettings = async () => {\n dialogService.showSettingsDialog('keybinding')\n}\n\nconst closeBottomPanel = () => {\n bottomPanelStore.activePanel = null\n}\n</script>\n\n<style scoped>\n:deep(.p-tablist-active-bar) {\n display: none;\n}\n</style>\n","import type { CSSProperties } from 'vue'\nimport { ref, watch } from 'vue'\n\nimport { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'\nimport type { Size, Vector2 } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\nexport interface PositionConfig {\n /* The position of the element on litegraph canvas */\n pos: Vector2\n /* The size of the element on litegraph canvas */\n size: Size\n /* The scale factor of the canvas */\n scale?: number\n}\n\nexport function useAbsolutePosition(options: { useTransform?: boolean } = {}) {\n const { useTransform = false } = options\n\n const canvasStore = useCanvasStore()\n const lgCanvas = canvasStore.getCanvas()\n const { canvasPosToClientPos, update: updateCanvasPosition } =\n useCanvasPositionConversion(lgCanvas.canvas, lgCanvas)\n\n const settingStore = useSettingStore()\n watch(\n [\n () => settingStore.get('Comfy.Sidebar.Location'),\n () => settingStore.get('Comfy.Sidebar.Size'),\n () => settingStore.get('Comfy.UseNewMenu')\n ],\n () => updateCanvasPosition(),\n { flush: 'post' }\n )\n\n /**\n * @note Do NOT convert style to a computed value, as it will cause lag when\n * updating the style on different animation frames. Vue's computed value is\n * evaluated asynchronously.\n */\n const style = ref<CSSProperties>({})\n\n /**\n * Compute the style of the element based on the position and size.\n *\n * @param position\n */\n const computeStyle = (position: PositionConfig): CSSProperties => {\n const { pos, size, scale = lgCanvas.ds.scale } = position\n const [left, top] = canvasPosToClientPos(pos)\n const [width, height] = size\n\n return useTransform\n ? {\n position: 'fixed',\n transformOrigin: '0 0',\n transform: `scale(${scale})`,\n left: `${left}px`,\n top: `${top}px`,\n width: `${width}px`,\n height: `${height}px`\n }\n : {\n position: 'fixed',\n left: `${left}px`,\n top: `${top}px`,\n width: `${width * scale}px`,\n height: `${height * scale}px`\n }\n }\n\n /**\n * Update the position of the element on the litegraph canvas.\n *\n * @param config\n */\n const updatePosition = (config: PositionConfig) => {\n style.value = computeStyle(config)\n }\n\n return {\n style,\n updatePosition\n }\n}\n","import type { CSSProperties } from 'vue'\nimport { ref } from 'vue'\n\ninterface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/**\n * Finds the intersection between two rectangles\n */\nfunction intersect(a: Rect, b: Rect): [number, number, number, number] | null {\n const x1 = Math.max(a.x, b.x)\n const y1 = Math.max(a.y, b.y)\n const x2 = Math.min(a.x + a.width, b.x + b.width)\n const y2 = Math.min(a.y + a.height, b.y + b.height)\n\n if (x1 >= x2 || y1 >= y2) {\n return null\n }\n\n return [x1, y1, x2 - x1, y2 - y1]\n}\n\ninterface ClippingOptions {\n margin?: number\n}\n\nexport const useDomClipping = (options: ClippingOptions = {}) => {\n const style = ref<CSSProperties>({})\n const { margin = 4 } = options\n\n /**\n * Calculates a clip path for an element based on its intersection with a selected area\n */\n const calculateClipPath = (\n elementRect: DOMRect,\n canvasRect: DOMRect,\n isSelected: boolean,\n selectedArea?: {\n x: number\n y: number\n width: number\n height: number\n scale: number\n offset: [number, number]\n }\n ): string => {\n if (!isSelected && selectedArea) {\n const { scale, offset } = selectedArea\n\n // Get intersection in browser space\n const intersection = intersect(\n {\n x: elementRect.left - canvasRect.left,\n y: elementRect.top - canvasRect.top,\n width: elementRect.width,\n height: elementRect.height\n },\n {\n x: (selectedArea.x + offset[0] - margin) * scale,\n y: (selectedArea.y + offset[1] - margin) * scale,\n width: (selectedArea.width + 2 * margin) * scale,\n height: (selectedArea.height + 2 * margin) * scale\n }\n )\n\n if (!intersection) {\n return ''\n }\n\n // Convert intersection to canvas scale (element has scale transform)\n const clipX =\n (intersection[0] - elementRect.left + canvasRect.left) / scale + 'px'\n const clipY =\n (intersection[1] - elementRect.top + canvasRect.top) / scale + 'px'\n const clipWidth = intersection[2] / scale + 'px'\n const clipHeight = intersection[3] / scale + 'px'\n\n return `polygon(0% 0%, 0% 100%, ${clipX} 100%, ${clipX} ${clipY}, calc(${clipX} + ${clipWidth}) ${clipY}, calc(${clipX} + ${clipWidth}) calc(${clipY} + ${clipHeight}), ${clipX} calc(${clipY} + ${clipHeight}), ${clipX} 100%, 100% 100%, 100% 0%)`\n }\n\n return ''\n }\n\n /**\n * Updates the clip-path style based on element and selection information\n */\n const updateClipPath = (\n element: HTMLElement,\n canvasElement: HTMLCanvasElement,\n isSelected: boolean,\n selectedArea?: {\n x: number\n y: number\n width: number\n height: number\n scale: number\n offset: [number, number]\n }\n ) => {\n const elementRect = element.getBoundingClientRect()\n const canvasRect = canvasElement.getBoundingClientRect()\n\n const clipPath = calculateClipPath(\n elementRect,\n canvasRect,\n isSelected,\n selectedArea\n )\n\n style.value = {\n clipPath: clipPath || 'none',\n willChange: 'clip-path'\n }\n }\n\n return {\n style,\n updateClipPath\n }\n}\n","<template>\n <div\n v-show=\"widgetState.visible\"\n ref=\"widgetElement\"\n class=\"dom-widget\"\n :title=\"tooltip\"\n :style=\"style\"\n >\n <component\n :is=\"widget.component\"\n v-if=\"isComponentWidget(widget)\"\n :model-value=\"widget.value\"\n :widget=\"widget\"\n v-bind=\"widget.props\"\n @update:model-value=\"emit('update:widgetValue', $event)\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useElementBounding, useEventListener } from '@vueuse/core'\nimport type { CSSProperties } from 'vue'\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\n\nimport { useAbsolutePosition } from '@/composables/element/useAbsolutePosition'\nimport { useDomClipping } from '@/composables/element/useDomClipping'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { isComponentWidget, isDOMWidget } from '@/scripts/domWidget'\nimport type { DomWidgetState } from '@/stores/domWidgetStore'\n\nconst { widgetState } = defineProps<{\n widgetState: DomWidgetState\n}>()\nconst widget = widgetState.widget\n\nconst emit = defineEmits<{\n 'update:widgetValue': [value: string | object]\n}>()\n\nconst widgetElement = ref<HTMLElement | undefined>()\n\n/**\n * @note Do NOT convert style to a computed value, as it will cause lag when\n * updating the style on different animation frames. Vue's computed value is\n * evaluated asynchronously.\n */\nconst style = ref<CSSProperties>({})\nconst { style: positionStyle, updatePosition } = useAbsolutePosition({\n useTransform: true\n})\nconst { style: clippingStyle, updateClipPath } = useDomClipping()\n\nconst canvasStore = useCanvasStore()\nconst settingStore = useSettingStore()\nconst enableDomClipping = computed(() =>\n settingStore.get('Comfy.DOMClippingEnabled')\n)\n\nconst updateDomClipping = () => {\n const lgCanvas = canvasStore.canvas\n if (!lgCanvas || !widgetElement.value) return\n\n const selectedNode = Object.values(lgCanvas.selected_nodes ?? {})[0]\n if (!selectedNode) {\n // Clear clipping when no node is selected\n updateClipPath(widgetElement.value, lgCanvas.canvas, false, undefined)\n return\n }\n\n const isSelected = selectedNode === widgetState.widget.node\n const renderArea = selectedNode?.renderArea\n const offset = lgCanvas.ds.offset\n const scale = lgCanvas.ds.scale\n const selectedAreaConfig = renderArea\n ? {\n x: renderArea[0],\n y: renderArea[1],\n width: renderArea[2],\n height: renderArea[3],\n scale,\n offset: [offset[0], offset[1]] as [number, number]\n }\n : undefined\n\n updateClipPath(\n widgetElement.value,\n lgCanvas.canvas,\n isSelected,\n selectedAreaConfig\n )\n}\n\n/**\n * @note mapping between canvas position and client position depends on the\n * canvas element's position, so we need to watch the canvas element's position\n * and update the position of the widget accordingly.\n */\nconst { left, top } = useElementBounding(canvasStore.getCanvas().canvas)\nwatch(\n [() => widgetState, left, top],\n ([widgetState, _, __]) => {\n updatePosition(widgetState)\n if (enableDomClipping.value) {\n updateDomClipping()\n }\n\n style.value = {\n ...positionStyle.value,\n ...(enableDomClipping.value ? clippingStyle.value : {}),\n zIndex: widgetState.zIndex,\n pointerEvents:\n widgetState.readonly || widget.computedDisabled ? 'none' : 'auto',\n opacity: widget.computedDisabled ? 0.5 : 1\n }\n },\n { deep: true }\n)\n\nwatch(\n () => widgetState.visible,\n (newVisible, oldVisible) => {\n if (!newVisible && oldVisible) {\n widget.options.onHide?.(widget)\n }\n }\n)\nuseEventListener(document, 'mousedown', (event) => {\n if (!isDOMWidget(widget) || !widgetState.visible || !widget.element.blur) {\n return\n }\n if (!widget.element.contains(event.target as HTMLElement)) {\n widget.element.blur()\n }\n})\n\nonMounted(() => {\n if (!isDOMWidget(widget)) {\n return\n }\n useEventListener(\n widget.element,\n widget.options.selectOn ?? ['focus', 'click'],\n () => {\n const lgCanvas = canvasStore.canvas\n lgCanvas?.selectNode(widgetState.widget.node)\n lgCanvas?.bringToFront(widgetState.widget.node)\n }\n )\n})\n\nconst inputSpec = widget.node.constructor.nodeData\nconst tooltip = inputSpec?.inputs?.[widget.name]?.tooltip\n\n// Mount DOM element when widget is or becomes visible\nconst mountElementIfVisible = () => {\n if (!(widgetState.visible && isDOMWidget(widget) && widgetElement.value)) {\n return\n }\n // Only append if not already a child\n if (widgetElement.value.contains(widget.element)) {\n return\n }\n widgetElement.value.appendChild(widget.element)\n}\n\n// Check on mount - but only after next tick to ensure visibility is calculated\nonMounted(() => {\n nextTick(() => {\n mountElementIfVisible()\n }).catch((error) => {\n console.error('Error mounting DOM widget element:', error)\n })\n})\n\n// And watch for visibility changes\nwatch(\n () => widgetState.visible,\n () => {\n mountElementIfVisible()\n }\n)\n</script>\n\n<style scoped>\n@reference '../../../assets/css/style.css';\n\n.dom-widget > * {\n @apply h-full w-full;\n}\n</style>\n","<template>\n <!-- Create a new stacking context for widgets to avoid z-index issues -->\n <div class=\"isolate\">\n <DomWidget\n v-for=\"widgetState in widgetStates\"\n :key=\"widgetState.widget.id\"\n :widget-state=\"widgetState\"\n @update:widget-value=\"widgetState.widget.value = $event\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { whenever } from '@vueuse/core'\nimport { computed } from 'vue'\n\nimport DomWidget from '@/components/graph/widgets/DomWidget.vue'\nimport { useChainCallback } from '@/composables/functional/useChainCallback'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useDomWidgetStore } from '@/stores/domWidgetStore'\n\nconst domWidgetStore = useDomWidgetStore()\n\nconst widgetStates = computed(() => [...domWidgetStore.widgetStates.values()])\n\nconst updateWidgets = () => {\n const lgCanvas = canvasStore.canvas\n if (!lgCanvas) return\n\n const lowQuality = lgCanvas.low_quality\n const currentGraph = lgCanvas.graph\n\n for (const widgetState of widgetStates.value) {\n const widget = widgetState.widget\n\n // Early exit for non-visible widgets\n if (!widget.isVisible() || !widgetState.active) {\n widgetState.visible = false\n continue\n }\n\n // Check if the widget's node is in the current graph\n const node = widget.node\n const isInCorrectGraph = currentGraph?.nodes.includes(node)\n\n widgetState.visible =\n !!isInCorrectGraph &&\n lgCanvas.isNodeVisible(node) &&\n !(widget.options.hideOnZoom && lowQuality)\n\n if (widgetState.visible && node) {\n const margin = widget.margin\n widgetState.pos = [node.pos[0] + margin, node.pos[1] + margin + widget.y]\n widgetState.size = [\n (widget.width ?? node.width) - margin * 2,\n (widget.computedHeight ?? 50) - margin * 2\n ]\n // TODO: optimize this logic as it's O(n), where n is the number of nodes\n widgetState.zIndex = lgCanvas.graph?.nodes.indexOf(node) ?? -1\n widgetState.readonly = lgCanvas.read_only\n }\n }\n}\n\nconst canvasStore = useCanvasStore()\nwhenever(\n () => canvasStore.canvas,\n (canvas) =>\n (canvas.onDrawForeground = useChainCallback(\n canvas.onDrawForeground,\n updateWidgets\n )),\n { immediate: true }\n)\n</script>\n","import { computed, ref } from 'vue'\n\nexport function useZoomControls() {\n const isModalVisible = ref(false)\n\n const showModal = () => {\n isModalVisible.value = true\n }\n\n const hideModal = () => {\n isModalVisible.value = false\n }\n\n const toggleModal = () => {\n isModalVisible.value = !isModalVisible.value\n }\n\n const hasActivePopup = computed(() => isModalVisible.value)\n\n return {\n isModalVisible,\n showModal,\n hideModal,\n toggleModal,\n hasActivePopup\n }\n}\n","import { useThrottleFn } from '@vueuse/core'\nimport { ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\nimport type {\n LGraph,\n LGraphNode,\n LGraphTriggerEvent\n} from '@/lib/litegraph/src/litegraph'\nimport type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { api } from '@/scripts/api'\n\nimport { MinimapDataSourceFactory } from '../data/MinimapDataSourceFactory'\nimport type { UpdateFlags } from '../types'\n\ninterface GraphCallbacks {\n onNodeAdded?: (node: LGraphNode) => void\n onNodeRemoved?: (node: LGraphNode) => void\n onConnectionChange?: (node: LGraphNode) => void\n onTrigger?: (event: LGraphTriggerEvent) => void\n}\n\nexport function useMinimapGraph(\n graph: Ref<LGraph | null>,\n onGraphChanged: () => void\n) {\n const nodeStatesCache = new Map<NodeId, string>()\n const linksCache = ref<string>('')\n const lastNodeCount = ref(0)\n const updateFlags = ref<UpdateFlags>({\n bounds: false,\n nodes: false,\n connections: false,\n viewport: false\n })\n\n // Track LayoutStore version for change detection\n const layoutStoreVersion = layoutStore.getVersion()\n\n // Map to store original callbacks per graph ID\n const originalCallbacksMap = new Map<string, GraphCallbacks>()\n\n const handleGraphChangedThrottled = useThrottleFn(() => {\n onGraphChanged()\n }, 500)\n\n const setupEventListeners = () => {\n const g = graph.value\n if (!g) return\n\n // Check if we've already wrapped this graph's callbacks\n if (originalCallbacksMap.has(g.id)) {\n return\n }\n\n // Store the original callbacks for this graph\n const originalCallbacks: GraphCallbacks = {\n onNodeAdded: g.onNodeAdded,\n onNodeRemoved: g.onNodeRemoved,\n onConnectionChange: g.onConnectionChange,\n onTrigger: g.onTrigger\n }\n originalCallbacksMap.set(g.id, originalCallbacks)\n\n g.onNodeAdded = function (node: LGraphNode) {\n originalCallbacks.onNodeAdded?.call(this, node)\n void handleGraphChangedThrottled()\n }\n\n g.onNodeRemoved = function (node: LGraphNode) {\n originalCallbacks.onNodeRemoved?.call(this, node)\n nodeStatesCache.delete(node.id)\n void handleGraphChangedThrottled()\n }\n\n g.onConnectionChange = function (node: LGraphNode) {\n originalCallbacks.onConnectionChange?.call(this, node)\n void handleGraphChangedThrottled()\n }\n\n g.onTrigger = function (event: LGraphTriggerEvent) {\n originalCallbacks.onTrigger?.call(this, event)\n\n // Listen for visual property changes that affect minimap rendering\n if (\n event.type === 'node:property:changed' &&\n (event.property === 'mode' ||\n event.property === 'bgcolor' ||\n event.property === 'color')\n ) {\n // Invalidate cache for this node to force redraw\n nodeStatesCache.delete(String(event.nodeId))\n void handleGraphChangedThrottled()\n }\n }\n }\n\n const cleanupEventListeners = (oldGraph?: LGraph) => {\n const g = oldGraph || graph.value\n if (!g) return\n\n const originalCallbacks = originalCallbacksMap.get(g.id)\n if (!originalCallbacks) {\n console.error(\n 'Attempted to cleanup event listeners for graph that was never set up'\n )\n return\n }\n\n g.onNodeAdded = originalCallbacks.onNodeAdded\n g.onNodeRemoved = originalCallbacks.onNodeRemoved\n g.onConnectionChange = originalCallbacks.onConnectionChange\n g.onTrigger = originalCallbacks.onTrigger\n\n originalCallbacksMap.delete(g.id)\n }\n\n const checkForChangesInternal = () => {\n const g = graph.value\n if (!g) return false\n\n let structureChanged = false\n let positionChanged = false\n let connectionChanged = false\n\n // Use unified data source for change detection\n const dataSource = MinimapDataSourceFactory.create(g)\n\n // Check for node count changes\n const currentNodeCount = dataSource.getNodeCount()\n if (currentNodeCount !== lastNodeCount.value) {\n structureChanged = true\n lastNodeCount.value = currentNodeCount\n }\n\n // Check for node position/size changes\n const nodes = dataSource.getNodes()\n for (const node of nodes) {\n const nodeId = node.id\n const currentState = `${node.x},${node.y},${node.width},${node.height}`\n\n if (nodeStatesCache.get(nodeId) !== currentState) {\n positionChanged = true\n nodeStatesCache.set(nodeId, currentState)\n }\n }\n\n // Clean up removed nodes from cache\n const currentNodeIds = new Set(nodes.map((n) => n.id))\n for (const [nodeId] of nodeStatesCache) {\n if (!currentNodeIds.has(nodeId)) {\n nodeStatesCache.delete(nodeId)\n structureChanged = true\n }\n }\n\n // TODO: update when Layoutstore tracks links\n const currentLinks = JSON.stringify(g.links || {})\n if (currentLinks !== linksCache.value) {\n connectionChanged = true\n linksCache.value = currentLinks\n }\n\n if (structureChanged || positionChanged) {\n updateFlags.value.bounds = true\n updateFlags.value.nodes = true\n }\n\n if (connectionChanged) {\n updateFlags.value.connections = true\n }\n\n return structureChanged || positionChanged || connectionChanged\n }\n\n const init = () => {\n setupEventListeners()\n api.addEventListener('graphChanged', handleGraphChangedThrottled)\n\n watch(layoutStoreVersion, () => {\n void handleGraphChangedThrottled()\n })\n }\n\n const destroy = () => {\n cleanupEventListeners()\n api.removeEventListener('graphChanged', handleGraphChangedThrottled)\n nodeStatesCache.clear()\n }\n\n const clearCache = () => {\n nodeStatesCache.clear()\n linksCache.value = ''\n lastNodeCount.value = 0\n }\n\n return {\n updateFlags,\n setupEventListeners,\n cleanupEventListeners,\n checkForChanges: checkForChangesInternal,\n init,\n destroy,\n clearCache\n }\n}\n","import { ref } from 'vue'\nimport type { Ref, ShallowRef } from 'vue'\n\nimport type { MinimapCanvas } from '../types'\n\nexport function useMinimapInteraction(\n containerRef: Readonly<ShallowRef<HTMLDivElement | null>>,\n bounds: Ref<{ minX: number; minY: number; width: number; height: number }>,\n scale: Ref<number>,\n width: number,\n height: number,\n centerViewOn: (worldX: number, worldY: number) => void,\n canvas: Ref<MinimapCanvas | null>\n) {\n const isDragging = ref(false)\n const containerRect = ref({\n left: 0,\n top: 0,\n width: width,\n height: height\n })\n\n const updateContainerRect = () => {\n if (!containerRef.value) return\n\n const rect = containerRef.value.getBoundingClientRect()\n containerRect.value = {\n left: rect.left,\n top: rect.top,\n width: rect.width,\n height: rect.height\n }\n }\n\n const handlePointerDown = (e: PointerEvent) => {\n isDragging.value = true\n updateContainerRect()\n const target = e.currentTarget\n if (target instanceof HTMLElement) {\n target.setPointerCapture(e.pointerId)\n }\n handlePointerMove(e)\n }\n\n const handlePointerMove = (e: PointerEvent) => {\n if (!isDragging.value || !canvas.value) return\n\n const x = e.clientX - containerRect.value.left\n const y = e.clientY - containerRect.value.top\n\n const offsetX = (width - bounds.value.width * scale.value) / 2\n const offsetY = (height - bounds.value.height * scale.value) / 2\n\n const worldX = (x - offsetX) / scale.value + bounds.value.minX\n const worldY = (y - offsetY) / scale.value + bounds.value.minY\n\n centerViewOn(worldX, worldY)\n }\n\n const releasePointer = (e?: PointerEvent) => {\n isDragging.value = false\n if (!e) return\n\n const target = e.currentTarget\n if (\n target instanceof HTMLElement &&\n target.hasPointerCapture(e.pointerId)\n ) {\n target.releasePointerCapture(e.pointerId)\n }\n }\n\n const handlePointerUp = releasePointer\n\n const handlePointerCancel = releasePointer\n\n const handleWheel = (e: WheelEvent) => {\n e.preventDefault()\n\n const c = canvas.value\n if (!c) return\n\n if (\n containerRect.value.left === 0 &&\n containerRect.value.top === 0 &&\n containerRef.value\n ) {\n updateContainerRect()\n }\n\n const ds = c.ds\n const delta = e.deltaY > 0 ? 0.9 : 1.1\n\n const newScale = ds.scale * delta\n\n const MIN_SCALE = 0.1\n const MAX_SCALE = 10\n\n if (newScale < MIN_SCALE || newScale > MAX_SCALE) return\n\n const x = e.clientX - containerRect.value.left\n const y = e.clientY - containerRect.value.top\n\n const offsetX = (width - bounds.value.width * scale.value) / 2\n const offsetY = (height - bounds.value.height * scale.value) / 2\n\n const worldX = (x - offsetX) / scale.value + bounds.value.minX\n const worldY = (y - offsetY) / scale.value + bounds.value.minY\n\n ds.scale = newScale\n\n centerViewOn(worldX, worldY)\n }\n\n return {\n isDragging,\n containerRect,\n updateContainerRect,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handlePointerCancel,\n handleWheel\n }\n}\n","import { ref } from 'vue'\nimport type { Ref, ShallowRef } from 'vue'\n\nimport type { LGraph } from '@/lib/litegraph/src/litegraph'\n\nimport { renderMinimapToCanvas } from '../minimapCanvasRenderer'\nimport type { UpdateFlags } from '../types'\n\nexport function useMinimapRenderer(\n canvasRef: Readonly<ShallowRef<HTMLCanvasElement | null>>,\n graph: Ref<LGraph | null>,\n bounds: Ref<{ minX: number; minY: number; width: number; height: number }>,\n scale: Ref<number>,\n updateFlags: Ref<UpdateFlags>,\n settings: {\n nodeColors: Ref<boolean>\n showLinks: Ref<boolean>\n showGroups: Ref<boolean>\n renderBypass: Ref<boolean>\n renderError: Ref<boolean>\n },\n width: number,\n height: number\n) {\n const needsFullRedraw = ref(true)\n const needsBoundsUpdate = ref(true)\n\n const renderMinimap = () => {\n const g = graph.value\n if (!canvasRef.value || !g) return\n\n const ctx = canvasRef.value.getContext('2d')\n if (!ctx) return\n\n // Fast path for 0 nodes - just show background\n if (!g._nodes || g._nodes.length === 0) {\n ctx.clearRect(0, 0, width, height)\n return\n }\n\n const needsRedraw =\n needsFullRedraw.value ||\n updateFlags.value.nodes ||\n updateFlags.value.connections\n\n if (needsRedraw) {\n renderMinimapToCanvas(canvasRef.value, g, {\n bounds: bounds.value,\n scale: scale.value,\n settings: {\n nodeColors: settings.nodeColors.value,\n showLinks: settings.showLinks.value,\n showGroups: settings.showGroups.value,\n renderBypass: settings.renderBypass.value,\n renderError: settings.renderError.value\n },\n width,\n height\n })\n\n needsFullRedraw.value = false\n updateFlags.value.nodes = false\n updateFlags.value.connections = false\n }\n }\n\n const updateMinimap = (\n updateBounds: () => void,\n updateViewport: () => void\n ) => {\n if (needsBoundsUpdate.value || updateFlags.value.bounds) {\n updateBounds()\n needsBoundsUpdate.value = false\n updateFlags.value.bounds = false\n needsFullRedraw.value = true\n // When bounds change, we need to update the viewport position\n updateFlags.value.viewport = true\n }\n\n if (\n needsFullRedraw.value ||\n updateFlags.value.nodes ||\n updateFlags.value.connections\n ) {\n renderMinimap()\n }\n\n // Update viewport if needed (e.g., after bounds change)\n if (updateFlags.value.viewport) {\n updateViewport()\n updateFlags.value.viewport = false\n }\n }\n\n const forceFullRedraw = () => {\n needsFullRedraw.value = true\n updateFlags.value.bounds = true\n updateFlags.value.nodes = true\n updateFlags.value.connections = true\n updateFlags.value.viewport = true\n }\n\n return {\n needsFullRedraw,\n needsBoundsUpdate,\n renderMinimap,\n updateMinimap,\n forceFullRedraw\n }\n}\n","import { computed } from 'vue'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\n\n/**\n * Composable for minimap configuration options that are set by the user in the\n * settings. Provides reactive computed properties for the settings.\n */\nexport function useMinimapSettings() {\n const settingStore = useSettingStore()\n const colorPaletteStore = useColorPaletteStore()\n\n const nodeColors = computed(() =>\n settingStore.get('Comfy.Minimap.NodeColors')\n )\n const showLinks = computed(() => settingStore.get('Comfy.Minimap.ShowLinks'))\n const showGroups = computed(() =>\n settingStore.get('Comfy.Minimap.ShowGroups')\n )\n const renderBypass = computed(() =>\n settingStore.get('Comfy.Minimap.RenderBypassState')\n )\n const renderError = computed(() =>\n settingStore.get('Comfy.Minimap.RenderErrorState')\n )\n\n const width = 253\n const height = 200\n\n // Theme-aware colors\n const isLightTheme = computed(\n () => colorPaletteStore.completedActivePalette.light_theme\n )\n\n const containerStyles = computed(() => ({\n width: `${width}px`,\n height: `${height}px`,\n border: '1px solid var(--interface-stroke)',\n borderRadius: '8px'\n }))\n\n const panelStyles = computed(() => ({\n width: `210px`,\n height: `${height}px`,\n border: '1px solid var(--interface-stroke)',\n borderRadius: '8px'\n }))\n\n return {\n nodeColors,\n showLinks,\n showGroups,\n renderBypass,\n renderError,\n containerStyles,\n panelStyles,\n isLightTheme\n }\n}\n","import { useRafFn } from '@vueuse/core'\nimport { computed, ref } from 'vue'\nimport type { Ref } from 'vue'\n\nimport type { LGraph } from '@/lib/litegraph/src/litegraph'\nimport {\n calculateMinimapScale,\n enforceMinimumBounds\n} from '@/renderer/core/spatial/boundsCalculator'\nimport { MinimapDataSourceFactory } from '@/renderer/extensions/minimap/data/MinimapDataSourceFactory'\n\nimport type { MinimapBounds, MinimapCanvas, ViewportTransform } from '../types'\n\nexport function useMinimapViewport(\n canvas: Ref<MinimapCanvas | null>,\n graph: Ref<LGraph | null>,\n width: number,\n height: number\n) {\n const bounds = ref<MinimapBounds>({\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n width: 0,\n height: 0\n })\n\n const scale = ref(1)\n const viewportTransform = ref<ViewportTransform>({\n x: 0,\n y: 0,\n width: 0,\n height: 0\n })\n\n const canvasDimensions = ref({\n width: 0,\n height: 0\n })\n\n const updateCanvasDimensions = () => {\n const c = canvas.value\n if (!c) return\n\n const canvasEl = c.canvas\n const dpr = window.devicePixelRatio || 1\n\n canvasDimensions.value = {\n width: canvasEl.clientWidth || canvasEl.width / dpr,\n height: canvasEl.clientHeight || canvasEl.height / dpr\n }\n }\n\n const calculateGraphBounds = (): MinimapBounds => {\n // Use unified data source\n const dataSource = MinimapDataSourceFactory.create(graph.value)\n\n if (!dataSource.hasData()) {\n return { minX: 0, minY: 0, maxX: 100, maxY: 100, width: 100, height: 100 }\n }\n\n const sourceBounds = dataSource.getBounds()\n return enforceMinimumBounds(sourceBounds)\n }\n\n const calculateScale = () => {\n return calculateMinimapScale(bounds.value, width, height)\n }\n\n const updateViewport = () => {\n const c = canvas.value\n if (!c) return\n\n if (\n canvasDimensions.value.width === 0 ||\n canvasDimensions.value.height === 0\n ) {\n updateCanvasDimensions()\n }\n\n const ds = c.ds\n\n const viewportWidth = canvasDimensions.value.width / ds.scale\n const viewportHeight = canvasDimensions.value.height / ds.scale\n\n const worldX = -ds.offset[0]\n const worldY = -ds.offset[1]\n\n const centerOffsetX = (width - bounds.value.width * scale.value) / 2\n const centerOffsetY = (height - bounds.value.height * scale.value) / 2\n\n viewportTransform.value = {\n x: (worldX - bounds.value.minX) * scale.value + centerOffsetX,\n y: (worldY - bounds.value.minY) * scale.value + centerOffsetY,\n width: viewportWidth * scale.value,\n height: viewportHeight * scale.value\n }\n }\n\n const updateBounds = () => {\n bounds.value = calculateGraphBounds()\n scale.value = calculateScale()\n }\n\n const centerViewOn = (worldX: number, worldY: number) => {\n const c = canvas.value\n if (!c) return\n\n if (\n canvasDimensions.value.width === 0 ||\n canvasDimensions.value.height === 0\n ) {\n updateCanvasDimensions()\n }\n\n const ds = c.ds\n\n const viewportWidth = canvasDimensions.value.width / ds.scale\n const viewportHeight = canvasDimensions.value.height / ds.scale\n\n ds.offset[0] = -(worldX - viewportWidth / 2)\n ds.offset[1] = -(worldY - viewportHeight / 2)\n\n c.setDirty(true, true)\n }\n const { resume: startViewportSync, pause: stopViewportSync } =\n useRafFn(updateViewport)\n\n return {\n bounds: computed(() => bounds.value),\n scale: computed(() => scale.value),\n viewportTransform: computed(() => viewportTransform.value),\n canvasDimensions: computed(() => canvasDimensions.value),\n updateCanvasDimensions,\n updateViewport,\n updateBounds,\n centerViewOn,\n startViewportSync,\n stopViewportSync\n }\n}\n","import { useRafFn } from '@vueuse/core'\nimport { computed, nextTick, ref, shallowRef, watch } from 'vue'\nimport type { ShallowRef } from 'vue'\n\nimport type { LGraph } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\nimport type { MinimapCanvas, MinimapSettingsKey } from '../types'\nimport { useMinimapGraph } from './useMinimapGraph'\nimport { useMinimapInteraction } from './useMinimapInteraction'\nimport { useMinimapRenderer } from './useMinimapRenderer'\nimport { useMinimapSettings } from './useMinimapSettings'\nimport { useMinimapViewport } from './useMinimapViewport'\n\nexport function useMinimap({\n canvasRefMaybe,\n containerRefMaybe\n}: {\n canvasRefMaybe?: Readonly<ShallowRef<HTMLCanvasElement | null>>\n containerRefMaybe?: Readonly<ShallowRef<HTMLDivElement | null>>\n} = {}) {\n const canvasStore = useCanvasStore()\n const workflowStore = useWorkflowStore()\n const settingStore = useSettingStore()\n\n const minimapRef = ref<HTMLElement | null>(null)\n const canvasRef = canvasRefMaybe ?? shallowRef(null)\n const containerRef = containerRefMaybe ?? shallowRef(null)\n\n const visible = ref(true)\n const initialized = ref(false)\n\n const width = 250\n const height = 200\n\n const canvas = computed(() => canvasStore.canvas as MinimapCanvas | null)\n const graph = computed(() => {\n // If we're in a subgraph, use that; otherwise use the canvas graph\n const activeSubgraph = workflowStore.activeSubgraph\n return (activeSubgraph || canvas.value?.graph) as LGraph | null\n })\n\n // Settings\n const settings = useMinimapSettings()\n const {\n nodeColors,\n showLinks,\n showGroups,\n renderBypass,\n renderError,\n containerStyles,\n panelStyles\n } = settings\n\n const updateOption = async (key: MinimapSettingsKey, value: boolean) => {\n await settingStore.set(key, value)\n renderer.forceFullRedraw()\n renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)\n }\n\n // Viewport management\n const viewport = useMinimapViewport(canvas, graph, width, height)\n\n // Interaction handling\n const interaction = useMinimapInteraction(\n containerRef,\n viewport.bounds,\n viewport.scale,\n width,\n height,\n viewport.centerViewOn,\n canvas\n )\n\n // Graph event management\n const graphManager = useMinimapGraph(graph, () => {\n renderer.forceFullRedraw()\n renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)\n })\n\n // Rendering\n const renderer = useMinimapRenderer(\n canvasRef,\n graph,\n viewport.bounds,\n viewport.scale,\n graphManager.updateFlags,\n settings,\n width,\n height\n )\n\n // RAF loop for continuous updates\n const { pause: pauseChangeDetection, resume: resumeChangeDetection } =\n useRafFn(\n async () => {\n if (visible.value) {\n const hasChanges = await graphManager.checkForChanges()\n if (hasChanges) {\n renderer.updateMinimap(\n viewport.updateBounds,\n viewport.updateViewport\n )\n }\n }\n },\n { immediate: false }\n )\n\n const init = async () => {\n if (initialized.value) return\n\n visible.value = settingStore.get('Comfy.Minimap.Visible')\n\n if (canvas.value && graph.value) {\n graphManager.init()\n\n if (containerRef.value) {\n interaction.updateContainerRect()\n }\n viewport.updateCanvasDimensions()\n\n window.addEventListener('resize', interaction.updateContainerRect)\n window.addEventListener('scroll', interaction.updateContainerRect)\n window.addEventListener('resize', viewport.updateCanvasDimensions)\n\n renderer.forceFullRedraw()\n renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)\n viewport.updateViewport()\n\n if (visible.value) {\n resumeChangeDetection()\n viewport.startViewportSync()\n }\n initialized.value = true\n }\n }\n\n const destroy = () => {\n pauseChangeDetection()\n viewport.stopViewportSync()\n graphManager.destroy()\n\n window.removeEventListener('resize', interaction.updateContainerRect)\n window.removeEventListener('scroll', interaction.updateContainerRect)\n window.removeEventListener('resize', viewport.updateCanvasDimensions)\n\n initialized.value = false\n }\n\n watch(\n canvas,\n async (newCanvas, oldCanvas) => {\n if (oldCanvas) {\n graphManager.cleanupEventListeners()\n pauseChangeDetection()\n viewport.stopViewportSync()\n graphManager.destroy()\n window.removeEventListener('resize', interaction.updateContainerRect)\n window.removeEventListener('scroll', interaction.updateContainerRect)\n window.removeEventListener('resize', viewport.updateCanvasDimensions)\n }\n if (newCanvas && !initialized.value) {\n await init()\n }\n },\n { immediate: true, flush: 'post' }\n )\n\n // Watch for graph changes (e.g., when navigating to/from subgraphs)\n watch(graph, (newGraph, oldGraph) => {\n if (newGraph && newGraph !== oldGraph) {\n graphManager.cleanupEventListeners(oldGraph || undefined)\n graphManager.setupEventListeners()\n renderer.forceFullRedraw()\n renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)\n }\n })\n\n watch(visible, async (isVisible) => {\n if (isVisible) {\n if (containerRef.value) {\n interaction.updateContainerRect()\n }\n viewport.updateCanvasDimensions()\n\n renderer.forceFullRedraw()\n\n await nextTick()\n await nextTick()\n\n renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport)\n viewport.updateViewport()\n resumeChangeDetection()\n viewport.startViewportSync()\n } else {\n pauseChangeDetection()\n viewport.stopViewportSync()\n }\n })\n\n const toggle = async () => {\n visible.value = !visible.value\n await settingStore.set('Comfy.Minimap.Visible', visible.value)\n }\n\n const setMinimapRef = (ref: HTMLElement | null) => {\n minimapRef.value = ref\n }\n\n // Dynamic viewport styles based on actual viewport transform\n const viewportStyles = computed(() => {\n const transform = viewport.viewportTransform.value\n return {\n transform: `translate(${transform.x}px, ${transform.y}px)`,\n width: `${transform.width}px`,\n height: `${transform.height}px`,\n border: `2px solid ${settings.isLightTheme.value ? '#E0E0E0' : '#FFF'}`,\n backgroundColor: `rgba(255, 255, 255, 0.2)`,\n willChange: 'transform',\n backfaceVisibility: 'hidden' as const,\n perspective: '1000px',\n pointerEvents: 'none' as const\n }\n })\n\n return {\n visible: computed(() => visible.value),\n initialized: computed(() => initialized.value),\n\n containerStyles,\n viewportStyles,\n panelStyles,\n width,\n height,\n\n nodeColors,\n showLinks,\n showGroups,\n renderBypass,\n renderError,\n\n init,\n destroy,\n toggle,\n renderMinimap: renderer.renderMinimap,\n handlePointerDown: interaction.handlePointerDown,\n handlePointerMove: interaction.handlePointerMove,\n handlePointerUp: interaction.handlePointerUp,\n handlePointerCancel: interaction.handlePointerCancel,\n handleWheel: interaction.handleWheel,\n setMinimapRef,\n updateOption\n }\n}\n","<template>\n <Button\n ref=\"buttonRef\"\n variant=\"secondary\"\n class=\"group h-8 rounded-none! bg-comfy-menu-bg p-0 transition-none! hover:rounded-lg! hover:bg-interface-button-hover-surface!\"\n :style=\"buttonStyles\"\n @click=\"toggle\"\n >\n <div class=\"flex items-center gap-1 pr-0.5\">\n <div\n class=\"rounded-lg bg-interface-panel-selected-surface p-2 group-hover:bg-interface-button-hover-surface\"\n >\n <i :class=\"currentModeIcon\" class=\"block h-4 w-4\" />\n </div>\n <i class=\"icon-[lucide--chevron-down] block h-4 w-4 pr-1.5\" />\n </div>\n </Button>\n\n <Popover\n ref=\"popover\"\n :auto-z-index=\"true\"\n :base-z-index=\"1000\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"popoverPt\"\n >\n <div class=\"flex flex-col gap-1\">\n <div\n class=\"flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-node-component-surface-hovered\"\n @click=\"setMode('select')\"\n >\n <div class=\"flex items-center gap-2\">\n <i class=\"icon-[lucide--mouse-pointer-2] h-4 w-4\" />\n <span>{{ $t('graphCanvasMenu.select') }}</span>\n </div>\n <span class=\"text-[9px] text-text-primary\">{{\n unlockCommandText\n }}</span>\n </div>\n\n <div\n class=\"flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered\"\n @click=\"setMode('hand')\"\n >\n <div class=\"flex items-center gap-2\">\n <i class=\"icon-[lucide--hand] h-4 w-4\" />\n <span>{{ $t('graphCanvasMenu.hand') }}</span>\n </div>\n <span class=\"text-[9px] text-text-primary\">{{ lockCommandText }}</span>\n </div>\n </div>\n </Popover>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport { computed, ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCommandStore } from '@/stores/commandStore'\n\ninterface Props {\n buttonStyles?: Record<string, string>\n}\n\ndefineProps<Props>()\nconst buttonRef = ref<InstanceType<typeof Button>>()\nconst popover = ref<InstanceType<typeof Popover>>()\nconst commandStore = useCommandStore()\nconst canvasStore = useCanvasStore()\n\nconst isCanvasReadOnly = computed(() => canvasStore.canvas?.read_only ?? false)\n\nconst currentModeIcon = computed(() =>\n isCanvasReadOnly.value\n ? 'icon-[lucide--hand]'\n : 'icon-[lucide--mouse-pointer-2]'\n)\n\nconst unlockCommandText = computed(() =>\n commandStore\n .formatKeySequence(commandStore.getCommand('Comfy.Canvas.Unlock'))\n .toUpperCase()\n)\n\nconst lockCommandText = computed(() =>\n commandStore\n .formatKeySequence(commandStore.getCommand('Comfy.Canvas.Lock'))\n .toUpperCase()\n)\n\nconst toggle = (event: Event) => {\n const el = (buttonRef.value as any)?.$el || buttonRef.value\n popover.value?.toggle(event, el)\n}\n\nconst setMode = (mode: 'select' | 'hand') => {\n if (mode === 'select' && isCanvasReadOnly.value) {\n void commandStore.execute('Comfy.Canvas.Unlock')\n } else if (mode === 'hand' && !isCanvasReadOnly.value) {\n void commandStore.execute('Comfy.Canvas.Lock')\n }\n popover.value?.hide()\n}\n\nconst popoverPt = computed(() => ({\n root: {\n class: 'absolute z-50 -translate-y-2'\n },\n content: {\n class: [\n 'mb-2 text-text-primary',\n 'shadow-lg border border-interface-stroke',\n 'bg-nav-background',\n 'rounded-lg',\n 'p-2 px-3',\n 'min-w-39',\n 'select-none'\n ]\n }\n}))\n</script>\n","<template>\n <div\n v-if=\"visible\"\n class=\"absolute right-0 bottom-[62px] z-1300 flex w-[250px] justify-center border-0! bg-inherit!\"\n >\n <div\n class=\"w-4/5 rounded-lg border border-interface-stroke bg-interface-panel-surface p-2 text-text-primary shadow-lg select-none\"\n :style=\"filteredMinimapStyles\"\n @click.stop\n >\n <div class=\"flex flex-col gap-1\">\n <div\n class=\"flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered\"\n @mousedown=\"startRepeat('Comfy.Canvas.ZoomIn')\"\n @mouseup=\"stopRepeat\"\n @mouseleave=\"stopRepeat\"\n >\n <span class=\"font-medium\">{{ $t('graphCanvasMenu.zoomIn') }}</span>\n <span class=\"text-[9px] text-text-primary\">{{\n zoomInCommandText\n }}</span>\n </div>\n\n <div\n class=\"flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered\"\n @mousedown=\"startRepeat('Comfy.Canvas.ZoomOut')\"\n @mouseup=\"stopRepeat\"\n @mouseleave=\"stopRepeat\"\n >\n <span class=\"font-medium\">{{ $t('graphCanvasMenu.zoomOut') }}</span>\n <span class=\"text-[9px] text-text-primary\">{{\n zoomOutCommandText\n }}</span>\n </div>\n\n <div\n class=\"flex cursor-pointer items-center justify-between rounded px-3 py-2 text-sm hover:bg-node-component-surface-hovered\"\n @click=\"executeCommand('Comfy.Canvas.FitView')\"\n >\n <span class=\"font-medium\">{{ $t('zoomControls.zoomToFit') }}</span>\n <span class=\"text-[9px] text-text-primary\">{{\n zoomToFitCommandText\n }}</span>\n </div>\n\n <div\n ref=\"zoomInputContainer\"\n class=\"zoomInputContainer flex items-center gap-1 rounded bg-input-surface p-2\"\n >\n <InputNumber\n :default-value=\"canvasStore.appScalePercentage\"\n :min=\"1\"\n :max=\"1000\"\n :show-buttons=\"false\"\n :use-grouping=\"false\"\n :unstyled=\"true\"\n input-class=\"bg-transparent border-none outline-hidden text-sm shadow-none my-0 w-full\"\n fluid\n @input=\"applyZoom\"\n @keyup.enter=\"applyZoom\"\n />\n <span class=\"flex-shrink-0 text-sm text-text-primary\">%</span>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport type { InputNumberInputEvent } from 'primevue'\nimport { InputNumber } from 'primevue'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst minimap = useMinimap()\nconst commandStore = useCommandStore()\nconst canvasStore = useCanvasStore()\nconst { formatKeySequence } = useCommandStore()\n\ninterface Props {\n visible: boolean\n}\n\nconst props = defineProps<Props>()\n\nconst interval = ref<number | null>(null)\n\nconst applyZoom = (val: InputNumberInputEvent) => {\n const inputValue = val.value as number\n if (isNaN(inputValue) || inputValue < 1 || inputValue > 1000) {\n return\n }\n canvasStore.setAppZoomFromPercentage(inputValue)\n}\n\nconst executeCommand = (command: string) => {\n void commandStore.execute(command)\n}\n\nconst startRepeat = (command: string) => {\n if (interval.value) return\n const cmd = () => commandStore.execute(command)\n void cmd()\n interval.value = window.setInterval(cmd, 100)\n}\n\nconst stopRepeat = () => {\n if (interval.value) {\n clearInterval(interval.value)\n interval.value = null\n }\n}\nconst filteredMinimapStyles = computed(() => {\n return {\n ...minimap.containerStyles.value,\n height: undefined,\n width: undefined\n }\n})\nconst zoomInCommandText = computed(() =>\n formatKeySequence(commandStore.getCommand('Comfy.Canvas.ZoomIn'))\n)\nconst zoomOutCommandText = computed(() =>\n formatKeySequence(commandStore.getCommand('Comfy.Canvas.ZoomOut'))\n)\nconst zoomToFitCommandText = computed(() =>\n formatKeySequence(commandStore.getCommand('Comfy.Canvas.FitView'))\n)\nconst zoomInputContainer = ref<HTMLDivElement | null>(null)\n\nwatch(\n () => props.visible,\n async (newVal) => {\n if (newVal) {\n await nextTick()\n const input = zoomInputContainer.value?.querySelector(\n 'input'\n ) as HTMLInputElement\n input?.focus()\n }\n }\n)\n</script>\n<style>\n.zoomInputContainer:focus-within {\n border: 1px solid var(--color-white);\n}\n</style>\n","<template>\n <div>\n <ZoomControlsModal :visible=\"isModalVisible\" @close=\"hideModal\" />\n\n <!-- Backdrop -->\n <div\n v-if=\"hasActivePopup\"\n class=\"fixed inset-0 z-1200\"\n @click=\"hideModal\"\n ></div>\n\n <ButtonGroup\n class=\"absolute right-0 bottom-0 z-1200 flex-row gap-1 border-[1px] border-interface-stroke bg-comfy-menu-bg p-2\"\n :style=\"{\n ...stringifiedMinimapStyles.buttonGroupStyles\n }\"\n @wheel=\"canvasInteractions.handleWheel\"\n >\n <CanvasModeSelector\n :button-styles=\"stringifiedMinimapStyles.buttonStyles\"\n />\n\n <div class=\"h-[27px] w-[1px] self-center bg-node-divider\" />\n\n <Button\n v-tooltip.top=\"fitViewTooltip\"\n variant=\"secondary\"\n :aria-label=\"fitViewTooltip\"\n :style=\"stringifiedMinimapStyles.buttonStyles\"\n class=\"h-8 w-8 bg-comfy-menu-bg p-0 hover:bg-interface-button-hover-surface!\"\n @click=\"() => commandStore.execute('Comfy.Canvas.FitView')\"\n >\n <i class=\"icon-[lucide--focus] h-4 w-4\" />\n </Button>\n\n <Button\n v-tooltip.top=\"t('zoomControls.label')\"\n variant=\"secondary\"\n :class=\"zoomButtonClass\"\n :aria-label=\"t('zoomControls.label')\"\n data-testid=\"zoom-controls-button\"\n :style=\"stringifiedMinimapStyles.buttonStyles\"\n @click=\"toggleModal\"\n >\n <span class=\"inline-flex items-center gap-1 px-2 text-xs\">\n <span>{{ canvasStore.appScalePercentage }}%</span>\n <i class=\"icon-[lucide--chevron-down] h-4 w-4\" />\n </span>\n </Button>\n\n <div class=\"h-[27px] w-[1px] self-center bg-node-divider\" />\n\n <Button\n v-tooltip.top=\"minimapTooltip\"\n variant=\"secondary\"\n :aria-label=\"minimapTooltip\"\n data-testid=\"toggle-minimap-button\"\n :style=\"stringifiedMinimapStyles.buttonStyles\"\n :class=\"minimapButtonClass\"\n @click=\"onMinimapToggleClick\"\n >\n <i class=\"icon-[lucide--map] h-4 w-4\" />\n </Button>\n\n <Button\n v-tooltip.top=\"{\n value: linkVisibilityTooltip,\n pt: {\n root: {\n style: 'z-index: 2; transform: translateY(-20px);'\n }\n }\n }\"\n variant=\"secondary\"\n :class=\"linkVisibleClass\"\n :aria-label=\"linkVisibilityAriaLabel\"\n data-testid=\"toggle-link-visibility-button\"\n :style=\"stringifiedMinimapStyles.buttonStyles\"\n @click=\"onLinkVisibilityToggleClick\"\n >\n <i class=\"icon-[lucide--route-off] h-4 w-4\" />\n </Button>\n </ButtonGroup>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport ButtonGroup from 'primevue/buttongroup'\nimport { computed, onBeforeUnmount, onMounted } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useZoomControls } from '@/composables/useZoomControls'\nimport { LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'\nimport { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'\nimport { useCommandStore } from '@/stores/commandStore'\n\nimport CanvasModeSelector from './CanvasModeSelector.vue'\nimport ZoomControlsModal from './modals/ZoomControlsModal.vue'\n\nconst { t } = useI18n()\nconst commandStore = useCommandStore()\nconst { formatKeySequence } = useCommandStore()\nconst canvasStore = useCanvasStore()\nconst settingStore = useSettingStore()\nconst canvasInteractions = useCanvasInteractions()\nconst minimap = useMinimap()\n\nconst { isModalVisible, toggleModal, hideModal, hasActivePopup } =\n useZoomControls()\n\nconst stringifiedMinimapStyles = computed(() => {\n const buttonGroupKeys = ['borderRadius']\n const buttonKeys = ['borderRadius']\n const additionalButtonStyles = {\n border: 'none'\n }\n\n const containerStyles = minimap.containerStyles.value\n\n const buttonStyles = {\n ...Object.fromEntries(\n Object.entries(containerStyles).filter(([key]) =>\n buttonKeys.includes(key)\n )\n ),\n ...additionalButtonStyles\n }\n const buttonGroupStyles = Object.entries(containerStyles)\n .filter(([key]) => buttonGroupKeys.includes(key))\n .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})\n\n return { buttonStyles, buttonGroupStyles }\n})\n\n// Computed properties for reactive states\nconst linkHidden = computed(\n () => settingStore.get('Comfy.LinkRenderMode') === LiteGraph.HIDDEN_LINK\n)\n\n// Computed properties for command text\nconst fitViewCommandText = computed(() =>\n formatKeySequence(\n commandStore.getCommand('Comfy.Canvas.FitView')\n ).toUpperCase()\n)\nconst minimapCommandText = computed(() =>\n formatKeySequence(\n commandStore.getCommand('Comfy.Canvas.ToggleMinimap')\n ).toUpperCase()\n)\n\n// Computed properties for button classes and states\nconst zoomButtonClass = computed(() => [\n 'bg-comfy-menu-bg',\n isModalVisible.value ? 'not-active:bg-interface-panel-selected-surface!' : '',\n 'hover:bg-interface-button-hover-surface!',\n 'p-0',\n 'h-8',\n 'w-15'\n])\n\nconst minimapButtonClass = computed(() => ({\n 'bg-comfy-menu-bg': true,\n 'hover:bg-interface-button-hover-surface!': true,\n 'not-active:bg-interface-panel-selected-surface!': settingStore.get(\n 'Comfy.Minimap.Visible'\n ),\n 'p-0': true,\n 'w-8': true,\n 'h-8': true\n}))\n\n// Computed properties for tooltip and aria-label texts\nconst fitViewTooltip = computed(() => {\n const label = t('graphCanvasMenu.fitView')\n const shortcut = fitViewCommandText.value\n return shortcut ? `${label} (${shortcut})` : label\n})\nconst minimapTooltip = computed(() => {\n const label = settingStore.get('Comfy.Minimap.Visible')\n ? t('zoomControls.hideMinimap')\n : t('zoomControls.showMinimap')\n const shortcut = minimapCommandText.value\n return shortcut ? `${label} (${shortcut})` : label\n})\nconst linkVisibilityTooltip = computed(() =>\n linkHidden.value\n ? t('graphCanvasMenu.showLinks')\n : t('graphCanvasMenu.hideLinks')\n)\nconst linkVisibilityAriaLabel = computed(() =>\n linkHidden.value\n ? t('graphCanvasMenu.showLinks')\n : t('graphCanvasMenu.hideLinks')\n)\nconst linkVisibleClass = computed(() => [\n 'bg-comfy-menu-bg',\n linkHidden.value ? 'not-active:bg-interface-panel-selected-surface!' : '',\n 'hover:bg-interface-button-hover-surface!',\n 'p-0',\n 'w-8',\n 'h-8'\n])\n\nonMounted(() => {\n canvasStore.initScaleSync()\n})\n\n/**\n * Track minimap toggle button click and execute the command.\n */\nconst onMinimapToggleClick = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'graph_menu_minimap_toggle_clicked'\n })\n void commandStore.execute('Comfy.Canvas.ToggleMinimap')\n}\n\n/**\n * Track hide/show links button click and execute the command.\n */\nconst onLinkVisibilityToggleClick = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'graph_menu_hide_links_toggle_clicked'\n })\n void commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')\n}\n\nonBeforeUnmount(() => {\n canvasStore.cleanupScaleSync()\n})\n</script>\n","<template>\n <div\n v-if=\"tooltipText\"\n ref=\"tooltipRef\"\n class=\"node-tooltip\"\n :style=\"{ left, top }\"\n >\n {{ tooltipText }}\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport { nextTick, ref } from 'vue'\n\nimport { st } from '@/i18n'\nimport {\n LiteGraph,\n isOverNodeInput,\n isOverNodeOutput\n} from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { app as comfyApp } from '@/scripts/app'\nimport { isDOMWidget } from '@/scripts/domWidget'\nimport { useNodeDefStore } from '@/stores/nodeDefStore'\nimport { normalizeI18nKey } from '@/utils/formatUtil'\n\nlet idleTimeout: number\nconst nodeDefStore = useNodeDefStore()\nconst settingStore = useSettingStore()\nconst tooltipRef = ref<HTMLDivElement | undefined>()\nconst tooltipText = ref('')\nconst left = ref<string>()\nconst top = ref<string>()\n\nfunction hideTooltip() {\n return (tooltipText.value = '')\n}\n\nasync function showTooltip(tooltip: string | null | undefined) {\n if (!tooltip) return\n\n left.value = comfyApp.canvas.mouse[0] + 'px'\n top.value = comfyApp.canvas.mouse[1] + 'px'\n tooltipText.value = tooltip\n\n await nextTick()\n\n const rect = tooltipRef.value?.getBoundingClientRect()\n if (!rect) return\n\n if (rect.right > window.innerWidth) {\n left.value = comfyApp.canvas.mouse[0] - rect.width + 'px'\n }\n\n if (rect.top < 0) {\n top.value = comfyApp.canvas.mouse[1] + rect.height + 'px'\n }\n}\n\nfunction onIdle() {\n const { canvas } = comfyApp\n const node = canvas?.node_over\n if (!node) return\n\n const ctor = node.constructor as { title_mode?: 0 | 1 | 2 | 3 }\n const nodeDef = nodeDefStore.nodeDefsByName[node.type ?? '']\n\n if (\n ctor.title_mode !== LiteGraph.NO_TITLE &&\n canvas.graph_mouse[1] < node.pos[1] // If we are over a node, but not within the node then we are on its title\n ) {\n return showTooltip(nodeDef?.description)\n }\n\n if (node.flags?.collapsed) return\n\n const inputSlot = isOverNodeInput(\n node,\n canvas.graph_mouse[0],\n canvas.graph_mouse[1],\n [0, 0]\n )\n if (inputSlot !== -1) {\n const inputName = node.inputs[inputSlot].name\n const translatedTooltip = st(\n `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(inputName)}.tooltip`,\n nodeDef?.inputs[inputName]?.tooltip ?? ''\n )\n return showTooltip(translatedTooltip)\n }\n\n const outputSlot = isOverNodeOutput(\n node,\n canvas.graph_mouse[0],\n canvas.graph_mouse[1],\n [0, 0]\n )\n if (outputSlot !== -1) {\n const translatedTooltip = st(\n `nodeDefs.${normalizeI18nKey(node.type ?? '')}.outputs.${outputSlot}.tooltip`,\n nodeDef?.outputs[outputSlot]?.tooltip ?? ''\n )\n return showTooltip(translatedTooltip)\n }\n\n const widget = comfyApp.canvas.getWidgetAtCursor()\n // Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these\n if (widget && !isDOMWidget(widget)) {\n const translatedTooltip = st(\n `nodeDefs.${normalizeI18nKey(node.type ?? '')}.inputs.${normalizeI18nKey(widget.name)}.tooltip`,\n nodeDef?.inputs[widget.name]?.tooltip ?? ''\n )\n // Widget tooltip can be set dynamically, current translation collection does not support this.\n return showTooltip(widget.tooltip ?? translatedTooltip)\n }\n}\n\nconst onMouseMove = (e: MouseEvent) => {\n hideTooltip()\n clearTimeout(idleTimeout)\n\n if ((e.target as Node).nodeName !== 'CANVAS') return\n idleTimeout = window.setTimeout(\n onIdle,\n settingStore.get('LiteGraph.Node.TooltipDelay')\n )\n}\n\nuseEventListener(window, 'mousemove', onMouseMove)\nuseEventListener(window, 'click', hideTooltip)\n</script>\n\n<style lang=\"css\" scoped>\n.node-tooltip {\n pointer-events: none;\n background: var(--comfy-input-bg);\n border-radius: 5px;\n box-shadow: 0 0 5px rgb(0 0 0 / 0.4);\n color: var(--input-text);\n left: 0;\n max-width: 30vw;\n padding: 4px 8px;\n position: absolute;\n top: 0;\n transform: translate(5px, calc(-100% - 5px));\n white-space: pre-wrap;\n z-index: 99999;\n}\n</style>\n","<template>\n <Button\n v-tooltip.top=\"{\n value: $t('commands.Comfy_Canvas_ToggleSelectedNodes_Bypass.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('commands.Comfy_Canvas_ToggleSelectedNodes_Bypass.label')\"\n data-testid=\"bypass-button\"\n class=\"hover:bg-secondary-background\"\n @click=\"toggleBypass\"\n >\n <i class=\"icon-[lucide--redo-dot] size-4\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst commandStore = useCommandStore()\n\nconst toggleBypass = async () => {\n await commandStore.execute('Comfy.Canvas.ToggleSelectedNodes.Bypass')\n}\n</script>\n","<template>\n <div class=\"relative\">\n <Button\n v-tooltip.top=\"{\n value: localizedCurrentColorName ?? t('color.noColor'),\n showDelay: 1000\n }\"\n data-testid=\"color-picker-button\"\n variant=\"muted-textonly\"\n :aria-label=\"t('g.color')\"\n @click=\"() => (showColorPicker = !showColorPicker)\"\n >\n <div class=\"flex items-center gap-1 px-0\">\n <i class=\"pi pi-circle-fill\" :style=\"{ color: currentColor ?? '' }\" />\n <i class=\"icon-[lucide--chevron-down]\" />\n </div>\n </Button>\n <div\n v-if=\"showColorPicker\"\n class=\"color-picker-container absolute -top-10 left-1/2\"\n >\n <SelectButton\n :model-value=\"selectedColorOption\"\n :options=\"colorOptions\"\n option-label=\"name\"\n data-key=\"value\"\n @update:model-value=\"applyColor\"\n >\n <template #option=\"{ option }\">\n <i\n v-tooltip.top=\"option.localizedName\"\n class=\"pi pi-circle-fill\"\n :style=\"{\n color: isLightTheme ? option.value.light : option.value.dark\n }\"\n :data-testid=\"option.name\"\n />\n </template>\n </SelectButton>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport SelectButton from 'primevue/selectbutton'\nimport type { Raw } from 'vue'\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport type {\n ColorOption as CanvasColorOption,\n Positionable\n} from '@/lib/litegraph/src/litegraph'\nimport {\n LGraphCanvas,\n LiteGraph,\n isColorable\n} from '@/lib/litegraph/src/litegraph'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { adjustColor } from '@/utils/colorUtil'\nimport { getItemsColorOption } from '@/utils/litegraphUtil'\n\nconst { t } = useI18n()\nconst canvasStore = useCanvasStore()\nconst colorPaletteStore = useColorPaletteStore()\nconst workflowStore = useWorkflowStore()\nconst isLightTheme = computed(\n () => colorPaletteStore.completedActivePalette.light_theme\n)\nconst toLightThemeColor = (color: string) =>\n adjustColor(color, { lightness: 0.5 })\n\nconst showColorPicker = ref(false)\n\ntype ColorOption = {\n name: string\n localizedName: string\n value: {\n dark: string\n light: string\n }\n}\n\nconst NO_COLOR_OPTION: ColorOption = {\n name: 'noColor',\n localizedName: t('color.noColor'),\n value: {\n dark: LiteGraph.NODE_DEFAULT_BGCOLOR,\n light: toLightThemeColor(LiteGraph.NODE_DEFAULT_BGCOLOR)\n }\n}\nconst colorOptions: ColorOption[] = [\n NO_COLOR_OPTION,\n ...Object.entries(LGraphCanvas.node_colors).map(([name, color]) => ({\n name,\n localizedName: t(`color.${name}`),\n value: {\n dark: color.bgcolor,\n light: toLightThemeColor(color.bgcolor)\n }\n }))\n]\n\nconst selectedColorOption = ref<ColorOption | null>(null)\nconst applyColor = (colorOption: ColorOption | null) => {\n const colorName = colorOption?.name ?? NO_COLOR_OPTION.name\n const canvasColorOption =\n colorName === NO_COLOR_OPTION.name\n ? null\n : LGraphCanvas.node_colors[colorName]\n\n for (const item of canvasStore.selectedItems) {\n if (isColorable(item)) {\n item.setColorOption(canvasColorOption)\n }\n }\n\n canvasStore.canvas?.setDirty(true, true)\n currentColorOption.value = canvasColorOption\n showColorPicker.value = false\n workflowStore.activeWorkflow?.changeTracker.checkState()\n}\n\nconst currentColorOption = ref<CanvasColorOption | null>(null)\nconst currentColor = computed(() =>\n currentColorOption.value\n ? isLightTheme.value\n ? toLightThemeColor(currentColorOption.value?.bgcolor)\n : currentColorOption.value?.bgcolor\n : null\n)\n\nconst localizedCurrentColorName = computed(() => {\n if (!currentColorOption.value?.bgcolor) return null\n const colorOption = colorOptions.find(\n (option) =>\n option.value.dark === currentColorOption.value?.bgcolor ||\n option.value.light === currentColorOption.value?.bgcolor\n )\n return colorOption?.localizedName ?? NO_COLOR_OPTION.localizedName\n})\nconst updateColorSelectionFromNode = (\n newSelectedItems: Raw<Positionable[]>\n) => {\n showColorPicker.value = false\n selectedColorOption.value = null\n currentColorOption.value = getItemsColorOption(newSelectedItems)\n}\nwatch(\n () => canvasStore.selectedItems,\n (newSelectedItems) => {\n updateColorSelectionFromNode(newSelectedItems)\n },\n { immediate: true }\n)\n</script>\n\n<style scoped>\n@reference '../../../assets/css/style.css';\n\n.color-picker-container {\n transform: translateX(-50%);\n}\n\n:deep(.p-togglebutton) {\n @apply py-2 px-1;\n}\n</style>\n","<template>\n <Button\n v-tooltip.top=\"{\n value: $t('commands.Comfy_Graph_EditSubgraphWidgets.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('commands.Comfy_Graph_EditSubgraphWidgets.label')\"\n @click=\"handleClick\"\n >\n <i class=\"icon-[lucide--settings-2]\" />\n </Button>\n</template>\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'\n\nconst rightSidePanelStore = useRightSidePanelStore()\n\nconst handleClick = () => {\n rightSidePanelStore.openPanel('subgraph')\n}\n</script>\n","<template>\n <Button\n v-if=\"isUnpackVisible\"\n v-tooltip.top=\"{\n value: $t('commands.Comfy_Graph_UnpackSubgraph.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('commands.Comfy_Graph_UnpackSubgraph.label')\"\n data-testid=\"convert-to-subgraph-button\"\n @click=\"() => commandStore.execute('Comfy.Graph.UnpackSubgraph')\"\n >\n <i class=\"icon-[lucide--expand] size-4\" />\n </Button>\n <Button\n v-else-if=\"isConvertVisible\"\n v-tooltip.top=\"{\n value: $t('commands.Comfy_Graph_ConvertToSubgraph.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n size=\"icon\"\n :aria-label=\"$t('commands.Comfy_Graph_ConvertToSubgraph.label')\"\n data-testid=\"convert-to-subgraph-button\"\n @click=\"() => commandStore.execute('Comfy.Graph.ConvertToSubgraph')\"\n >\n <i class=\"icon-[lucide--shrink] size-4\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSelectionState } from '@/composables/graph/useSelectionState'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst commandStore = useCommandStore()\nconst { isSingleSubgraph, hasAnySelection } = useSelectionState()\n\nconst isUnpackVisible = isSingleSubgraph\nconst isConvertVisible = computed(\n () => hasAnySelection.value && !isSingleSubgraph.value\n)\n</script>\n","<template>\n <Button\n v-show=\"isDeletable\"\n v-tooltip.top=\"{\n value: $t('commands.Comfy_Canvas_DeleteSelectedItems.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('commands.Comfy_Canvas_DeleteSelectedItems.label')\"\n data-testid=\"delete-button\"\n @click=\"() => commandStore.execute('Comfy.Canvas.DeleteSelectedItems')\"\n >\n <i class=\"icon-[lucide--trash-2]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSelectionState } from '@/composables/graph/useSelectionState'\nimport type { Positionable } from '@/lib/litegraph/src/interfaces'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst commandStore = useCommandStore()\nconst { selectedItems } = useSelectionState()\n\nconst isDeletable = computed(() =>\n selectedItems.value.some((x: Positionable) => x.removable !== false)\n)\n</script>\n","<template>\n <Button\n v-tooltip.top=\"{\n value: t('selectionToolbox.executeButton.tooltip'),\n showDelay: 1000\n }\"\n variant=\"primary\"\n :aria-label=\"t('selectionToolbox.executeButton.tooltip')\"\n @mouseenter=\"() => handleMouseEnter()\"\n @mouseleave=\"() => handleMouseLeave()\"\n @click=\"handleClick\"\n >\n <i class=\"icon-[lucide--play]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSelectionState } from '@/composables/graph/useSelectionState'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { isLGraphNode } from '@/utils/litegraphUtil'\nimport { isOutputNode } from '@/utils/nodeFilterUtil'\n\nconst { t } = useI18n()\nconst commandStore = useCommandStore()\nconst canvasStore = useCanvasStore()\nconst { selectedNodes } = useSelectionState()\n\nconst canvas = canvasStore.getCanvas()\nconst buttonHovered = ref(false)\nconst selectedOutputNodes = computed(() =>\n selectedNodes.value.filter(isLGraphNode).filter(isOutputNode)\n)\n\nfunction outputNodeStokeStyle(this: LGraphNode) {\n if (\n this.selected &&\n this.constructor.nodeData?.output_node &&\n buttonHovered.value\n ) {\n return { color: 'orange', lineWidth: 2, padding: 10 }\n }\n}\n\nconst handleMouseEnter = () => {\n buttonHovered.value = true\n for (const node of selectedOutputNodes.value) {\n node.strokeStyles['outputNode'] = outputNodeStokeStyle\n }\n canvas.setDirty(true)\n}\n\nconst handleMouseLeave = () => {\n buttonHovered.value = false\n canvas.setDirty(true)\n}\n\nconst handleClick = async () => {\n await commandStore.execute('Comfy.QueueSelectedOutputNodes')\n}\n</script>\n","<template>\n <Button\n v-tooltip.top=\"{\n value:\n st(`commands.${normalizeI18nKey(command.id)}.label`, '') || undefined,\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"st(`commands.${normalizeI18nKey(command.id)}.label`, '')\"\n @click=\"() => commandStore.execute(command.id)\"\n >\n <i\n :class=\"[\n typeof command.icon === 'function' ? command.icon() : command.icon\n ]\"\n />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { st } from '@/i18n'\nimport type { ComfyCommand } from '@/stores/commandStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { normalizeI18nKey } from '@/utils/formatUtil'\n\ndefineProps<{\n command: ComfyCommand\n}>()\n\nconst commandStore = useCommandStore()\n</script>\n","<template>\n <Button\n v-tooltip.top=\"{\n value: $t('g.info'),\n showDelay: 1000\n }\"\n data-testid=\"info-button\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('g.info')\"\n @click=\"onInfoClick\"\n >\n <i class=\"icon-[lucide--info]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'\n\nconst rightSidePanelStore = useRightSidePanelStore()\n\n/**\n * Track node info button click and toggle node help.\n */\nconst onInfoClick = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'selection_toolbox_node_info_opened'\n })\n rightSidePanelStore.openPanel('info')\n}\n</script>\n","<template>\n <Button\n v-tooltip.top=\"{\n value: $t('commands.Comfy_3DViewer_Open3DViewer.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('commands.Comfy_3DViewer_Open3DViewer.label')\"\n @click=\"open3DViewer\"\n >\n <i class=\"icon-[lucide--pencil]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst commandStore = useCommandStore()\n\nconst open3DViewer = () => {\n void commandStore.execute('Comfy.3DViewer.Open3DViewer')\n}\n</script>\n","<template>\n <Button\n v-show=\"isSingleImageNode\"\n v-tooltip.top=\"{\n value: $t('commands.Comfy_MaskEditor_OpenMaskEditor.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('commands.Comfy_MaskEditor_OpenMaskEditor.label')\"\n @click=\"openMaskEditor\"\n >\n <i class=\"icon-[comfy--mask]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSelectionState } from '@/composables/graph/useSelectionState'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst commandStore = useCommandStore()\nconst { isSingleImageNode } = useSelectionState()\n\nconst openMaskEditor = () => {\n void commandStore.execute('Comfy.MaskEditor.OpenMaskEditor')\n}\n</script>\n","import { computed, ref, watchEffect } from 'vue'\n\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { isLGraphNode } from '@/utils/litegraphUtil'\n\ninterface RefreshableItem {\n refresh: () => Promise<void> | void\n}\n\nconst isRefreshableWidget = (widget: unknown): widget is RefreshableItem =>\n widget != null &&\n typeof widget === 'object' &&\n 'refresh' in widget &&\n typeof widget.refresh === 'function'\n\n/**\n * Tracks selected nodes and their refreshable widgets\n */\nexport const useRefreshableSelection = () => {\n const graphStore = useCanvasStore()\n const selectedNodes = ref<LGraphNode[]>([])\n\n watchEffect(() => {\n selectedNodes.value = graphStore.selectedItems.filter(isLGraphNode)\n })\n\n const refreshableWidgets = computed<RefreshableItem[]>(() =>\n selectedNodes.value.flatMap((node) => {\n if (!node.widgets) return []\n const items: RefreshableItem[] = []\n for (const widget of node.widgets) {\n if (isRefreshableWidget(widget)) {\n items.push(widget)\n }\n }\n return items\n })\n )\n\n const isRefreshable = computed(() => refreshableWidgets.value.length > 0)\n\n async function refreshSelected() {\n if (!isRefreshable.value) return\n\n await Promise.all(refreshableWidgets.value.map((item) => item.refresh()))\n }\n\n return {\n isRefreshable,\n refreshSelected\n }\n}\n","<template>\n <Button\n v-show=\"isRefreshable\"\n v-tooltip.top=\"t('g.refreshNode')\"\n variant=\"muted-textonly\"\n :aria-label=\"t('g.refreshNode')\"\n data-testid=\"refresh-button\"\n @click=\"refreshSelected\"\n >\n <i class=\"icon-[lucide--refresh-cw]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useRefreshableSelection } from '@/composables/useRefreshableSelection'\n\nconst { t } = useI18n()\nconst { isRefreshable, refreshSelected } = useRefreshableSelection()\n</script>\n","<template>\n <Button\n v-show=\"isVisible\"\n v-tooltip.top=\"{\n value: $t('commands.Comfy_PublishSubgraph.label'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('commands.Comfy_PublishSubgraph.label')\"\n @click=\"() => commandStore.execute('Comfy.PublishSubgraph')\"\n >\n <i class=\"icon-[lucide--book-open]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { SubgraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCommandStore } from '@/stores/commandStore'\n\nconst commandStore = useCommandStore()\nconst canvasStore = useCanvasStore()\n\nconst isVisible = computed(() => {\n return (\n canvasStore.selectedItems?.length === 1 &&\n canvasStore.selectedItems[0] instanceof SubgraphNode\n )\n})\n</script>\n","import { useElementBounding, useRafFn } from '@vueuse/core'\nimport { computed, onUnmounted, ref, watch, watchEffect } from 'vue'\nimport type { Ref } from 'vue'\n\nimport { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'\nimport { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'\nimport type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'\nimport {\n LGraphGroup,\n LGraphNode,\n LiteGraph\n} from '@/lib/litegraph/src/litegraph'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { isLGraphGroup, isLGraphNode } from '@/utils/litegraphUtil'\nimport { computeUnionBounds } from '@/utils/mathUtil'\n\n/**\n * Manages the position of the selection toolbox independently.\n * Uses CSS custom properties for performant transform updates.\n */\n\n// Shared signals for auxiliary UI (e.g., MoreOptions) to coordinate hide/restore\nconst moreOptionsOpen = ref(false)\nconst forceCloseMoreOptionsSignal = ref(0)\nconst restoreMoreOptionsSignal = ref(0)\nconst moreOptionsRestorePending = ref(false)\nlet moreOptionsWasOpenBeforeDrag = false\nlet moreOptionsSelectionSignature: string | null = null\n\nfunction buildSelectionSignature(\n store: ReturnType<typeof useCanvasStore>\n): string | null {\n const c = store.canvas\n if (!c) return null\n const items = Array.from(c.selectedItems)\n if (items.length !== 1) return null\n const item = items[0]\n if (isLGraphNode(item)) return `N:${item.id}`\n if (isLGraphGroup(item)) return `G:${item.id}`\n return null\n}\n\nfunction currentSelectionMatchesSignature(\n store: ReturnType<typeof useCanvasStore>\n) {\n if (!moreOptionsSelectionSignature) return false\n return buildSelectionSignature(store) === moreOptionsSelectionSignature\n}\n\nexport function useSelectionToolboxPosition(\n toolboxRef: Ref<HTMLElement | undefined>\n) {\n const canvasStore = useCanvasStore()\n const lgCanvas = canvasStore.getCanvas()\n const { getSelectableItems } = useSelectedLiteGraphItems()\n const { shouldRenderVueNodes } = useVueFeatureFlags()\n\n // World position of selection center\n const worldPosition = ref({ x: 0, y: 0 })\n\n const visible = ref(false)\n\n // Use VueUse to reactively track canvas bounding rect\n const { left: canvasLeft, top: canvasTop } = useElementBounding(\n lgCanvas.canvas\n )\n\n // Unified dragging state - combines both LiteGraph and Vue node dragging\n const isDragging = computed((): boolean => {\n const litegraphDragging = canvasStore.canvas?.state?.draggingItems ?? false\n const vueNodeDragging =\n shouldRenderVueNodes.value && layoutStore.isDraggingVueNodes.value\n return litegraphDragging || vueNodeDragging\n })\n\n /**\n * Update position based on selection\n */\n const updateSelectionBounds = () => {\n const selectableItems = getSelectableItems()\n\n if (!selectableItems.size) {\n visible.value = false\n return\n }\n\n // Don't show toolbox while dragging\n if (isDragging.value) {\n visible.value = false\n return\n }\n\n visible.value = true\n\n // Get bounds for all selected items\n const allBounds: ReadOnlyRect[] = []\n for (const item of selectableItems) {\n // Skip items without valid IDs\n if (item.id == null) continue\n\n if (shouldRenderVueNodes.value && typeof item.id === 'string') {\n // Use layout store for Vue nodes (only works with string IDs)\n const layout = layoutStore.getNodeLayoutRef(item.id).value\n if (layout) {\n allBounds.push([\n layout.bounds.x,\n layout.bounds.y,\n layout.bounds.width,\n layout.bounds.height\n ])\n }\n } else {\n // Fallback to LiteGraph bounds for regular nodes or non-string IDs\n if (item instanceof LGraphNode || item instanceof LGraphGroup) {\n allBounds.push([\n item.pos[0],\n item.pos[1] - LiteGraph.NODE_TITLE_HEIGHT,\n item.size[0],\n item.size[1] + LiteGraph.NODE_TITLE_HEIGHT\n ])\n }\n }\n }\n\n // Compute union bounds\n const unionBounds = computeUnionBounds(allBounds)\n if (!unionBounds) return\n\n worldPosition.value = {\n x: unionBounds.x + unionBounds.width / 2,\n // createBounds() applied a default padding of 10px\n // so adjust Y to maintain visual consistency\n y: unionBounds.y - 10\n }\n\n updateTransform()\n }\n\n const updateTransform = () => {\n if (!visible.value) return\n\n const { scale, offset } = lgCanvas.ds\n\n const screenX =\n (worldPosition.value.x + offset[0]) * scale + canvasLeft.value\n const screenY =\n (worldPosition.value.y + offset[1]) * scale + canvasTop.value\n\n // Update CSS custom properties directly for best performance\n if (toolboxRef.value) {\n toolboxRef.value.style.setProperty('--tb-x', `${screenX}px`)\n toolboxRef.value.style.setProperty('--tb-y', `${screenY}px`)\n }\n }\n\n // Sync with canvas transform\n const { resume: startSync, pause: stopSync } = useRafFn(updateTransform)\n\n watchEffect(() => {\n if (visible.value) {\n startSync()\n } else {\n stopSync()\n }\n })\n\n // Watch for selection changes\n watch(\n () => canvasStore.getCanvas().state.selectionChanged,\n (changed) => {\n if (changed) {\n if (moreOptionsRestorePending.value || moreOptionsSelectionSignature) {\n moreOptionsRestorePending.value = false\n moreOptionsWasOpenBeforeDrag = false\n if (!moreOptionsOpen.value) {\n moreOptionsSelectionSignature = null\n } else {\n moreOptionsSelectionSignature = buildSelectionSignature(canvasStore)\n }\n }\n updateSelectionBounds()\n canvasStore.getCanvas().state.selectionChanged = false\n }\n },\n { immediate: true }\n )\n watch(\n () => moreOptionsOpen.value,\n (v) => {\n if (v) {\n moreOptionsSelectionSignature = buildSelectionSignature(canvasStore)\n } else if (!canvasStore.canvas?.state?.draggingItems) {\n moreOptionsSelectionSignature = null\n if (moreOptionsRestorePending.value)\n moreOptionsRestorePending.value = false\n }\n }\n )\n\n const handleDragStateChange = (dragging: boolean) => {\n if (dragging) {\n handleDragStart()\n return\n }\n\n handleDragEnd()\n }\n\n const handleDragStart = () => {\n visible.value = false\n\n // Early return if more options wasn't open\n if (!moreOptionsOpen.value) {\n moreOptionsRestorePending.value = false\n moreOptionsWasOpenBeforeDrag = false\n return\n }\n\n // Handle more options cleanup\n const currentSig = buildSelectionSignature(canvasStore)\n const selectionChanged = currentSig !== moreOptionsSelectionSignature\n\n if (selectionChanged) {\n moreOptionsSelectionSignature = null\n }\n moreOptionsOpen.value = false\n moreOptionsWasOpenBeforeDrag = true\n moreOptionsRestorePending.value = !!moreOptionsSelectionSignature\n\n if (moreOptionsRestorePending.value) {\n forceCloseMoreOptionsSignal.value++\n return\n }\n\n moreOptionsWasOpenBeforeDrag = false\n }\n\n const handleDragEnd = () => {\n requestAnimationFrame(() => {\n updateSelectionBounds()\n\n const selectionMatches = currentSelectionMatchesSignature(canvasStore)\n const shouldRestore =\n moreOptionsWasOpenBeforeDrag &&\n visible.value &&\n moreOptionsRestorePending.value &&\n selectionMatches\n\n // Single point of assignment for each ref\n moreOptionsRestorePending.value =\n shouldRestore && moreOptionsRestorePending.value\n moreOptionsWasOpenBeforeDrag = false\n\n if (shouldRestore) {\n restoreMoreOptionsSignal.value++\n }\n })\n }\n\n watch(isDragging, handleDragStateChange)\n\n onUnmounted(() => {\n resetMoreOptionsState()\n })\n\n return {\n visible\n }\n}\n\n// External cleanup utility to be called when SelectionToolbox component unmounts\nfunction resetMoreOptionsState() {\n moreOptionsOpen.value = false\n moreOptionsRestorePending.value = false\n moreOptionsWasOpenBeforeDrag = false\n moreOptionsSelectionSignature = null\n}\n","import { default as DOMPurify } from 'dompurify'\n\nimport { LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport type {\n IContextMenuValue,\n LGraphNode,\n IContextMenuOptions,\n ContextMenu\n} from '@/lib/litegraph/src/litegraph'\n\nimport type { MenuOption, SubMenuOption } from './useMoreOptionsMenu'\nimport type { ContextMenuDivElement } from '@/lib/litegraph/src/interfaces'\n\n/**\n * Hard blacklist - items that should NEVER be included\n */\nconst HARD_BLACKLIST = new Set([\n 'Properties', // Never include Properties submenu\n 'Colors', // Use singular \"Color\" instead\n 'Shapes', // Use singular \"Shape\" instead\n 'Title',\n 'Mode',\n 'Properties Panel',\n 'Copy (Clipspace)'\n])\n\n/**\n * Core menu items - items that should appear in the main menu, not under Extensions\n * Includes both LiteGraph base menu items and ComfyUI built-in functionality\n */\nconst CORE_MENU_ITEMS = new Set([\n // Basic operations\n 'Rename',\n 'Copy',\n 'Duplicate',\n 'Clone',\n // Node state operations\n 'Run Branch',\n 'Pin',\n 'Unpin',\n 'Bypass',\n 'Remove Bypass',\n 'Mute',\n // Structure operations\n 'Convert to Subgraph',\n 'Frame selection',\n 'Minimize Node',\n 'Expand',\n 'Collapse',\n // Info and adjustments\n 'Node Info',\n 'Resize',\n 'Title',\n 'Properties Panel',\n 'Adjust Size',\n // Visual\n 'Color',\n 'Colors',\n 'Shape',\n 'Shapes',\n 'Mode',\n // Built-in node operations (node-specific)\n 'Open Image',\n 'Copy Image',\n 'Save Image',\n 'Open in Mask Editor',\n 'Edit Subgraph Widgets',\n 'Unpack Subgraph',\n 'Copy (Clipspace)',\n 'Paste (Clipspace)',\n // Selection and alignment\n 'Align Selected To',\n 'Distribute Nodes',\n // Deletion\n 'Delete',\n 'Remove',\n // LiteGraph base items\n 'Show Advanced',\n 'Hide Advanced'\n])\n\n/**\n * Normalize menu item label for duplicate detection\n * Handles variations like Colors/Color, Shapes/Shape, Pin/Unpin, Remove/Delete\n */\nfunction normalizeLabel(label: string): string {\n return label\n .toLowerCase()\n .replace(/^un/, '') // Remove 'un' prefix (Unpin -> Pin)\n .trim()\n}\n\n/**\n * Check if a similar menu item already exists in the results\n * Returns true if an item with the same normalized label exists\n */\nfunction isDuplicateItem(label: string, existingItems: MenuOption[]): boolean {\n const normalizedLabel = normalizeLabel(label)\n\n // Map of equivalent items\n const equivalents: Record<string, string[]> = {\n color: ['color', 'colors'],\n shape: ['shape', 'shapes'],\n pin: ['pin', 'unpin'],\n delete: ['remove', 'delete'],\n duplicate: ['clone', 'duplicate']\n }\n\n return existingItems.some((item) => {\n if (!item.label) return false\n\n const existingNormalized = normalizeLabel(item.label)\n\n // Check direct match\n if (existingNormalized === normalizedLabel) return true\n\n // Check if they're in the same equivalence group\n for (const values of Object.values(equivalents)) {\n if (\n values.includes(normalizedLabel) &&\n values.includes(existingNormalized)\n ) {\n return true\n }\n }\n\n return false\n })\n}\n\n/**\n * Check if a menu item is a core menu item (not an extension)\n * Core items include LiteGraph base items and ComfyUI built-in functionality\n */\nfunction isCoreMenuItem(label: string): boolean {\n return CORE_MENU_ITEMS.has(label)\n}\n\n/**\n * Filter out duplicate menu items based on label\n * Gives precedence to Vue hardcoded options over LiteGraph options\n */\nfunction removeDuplicateMenuOptions(options: MenuOption[]): MenuOption[] {\n // Group items by label\n const itemsByLabel = new Map<string, MenuOption[]>()\n const itemsWithoutLabel: MenuOption[] = []\n\n for (const opt of options) {\n // Always keep dividers and category items\n if (opt.type === 'divider' || opt.type === 'category') {\n itemsWithoutLabel.push(opt)\n continue\n }\n\n // Items without labels are kept as-is\n if (!opt.label) {\n itemsWithoutLabel.push(opt)\n continue\n }\n\n // Group by label\n if (!itemsByLabel.has(opt.label)) {\n itemsByLabel.set(opt.label, [])\n }\n itemsByLabel.get(opt.label)!.push(opt)\n }\n\n // Select best item for each label (prefer vue over litegraph)\n const result: MenuOption[] = []\n const seenLabels = new Set<string>()\n\n for (const opt of options) {\n // Add non-labeled items in original order\n if (opt.type === 'divider' || opt.type === 'category' || !opt.label) {\n if (itemsWithoutLabel.includes(opt)) {\n result.push(opt)\n const idx = itemsWithoutLabel.indexOf(opt)\n itemsWithoutLabel.splice(idx, 1)\n }\n continue\n }\n\n // Skip if we already processed this label\n if (seenLabels.has(opt.label)) {\n continue\n }\n seenLabels.add(opt.label)\n\n // Get all items with this label\n const duplicates = itemsByLabel.get(opt.label)!\n\n // If only one item, add it\n if (duplicates.length === 1) {\n result.push(duplicates[0])\n continue\n }\n\n // Multiple items: prefer vue source over litegraph\n const vueItem = duplicates.find((item) => item.source === 'vue')\n if (vueItem) {\n result.push(vueItem)\n } else {\n // No vue item, just take the first one\n result.push(duplicates[0])\n }\n }\n\n return result\n}\n\n/**\n * Order groups for menu items - defines the display order of sections\n */\nconst MENU_ORDER: string[] = [\n // Section 1: Basic operations\n 'Rename',\n 'Copy',\n 'Duplicate',\n // Section 2: Node actions\n 'Run Branch',\n 'Pin',\n 'Unpin',\n 'Bypass',\n 'Remove Bypass',\n 'Mute',\n // Section 3: Structure operations\n 'Convert to Subgraph',\n 'Frame selection',\n 'Minimize Node',\n 'Expand',\n 'Collapse',\n 'Resize',\n 'Clone',\n // Section 4: Node properties\n 'Node Info',\n 'Color',\n // Section 5: Node-specific operations\n 'Open in Mask Editor',\n 'Open Image',\n 'Copy Image',\n 'Save Image',\n 'Copy (Clipspace)',\n 'Paste (Clipspace)',\n // Fallback for other core items\n 'Convert to Group Node (Deprecated)'\n]\n\n/**\n * Get the order index for a menu item (lower = earlier in menu)\n */\nfunction getMenuItemOrder(label: string): number {\n const index = MENU_ORDER.indexOf(label)\n return index === -1 ? 999 : index\n}\n\n/**\n * Build structured menu with core items first, then extensions under a labeled section\n * Ensures Delete always appears at the bottom\n */\nexport function buildStructuredMenu(options: MenuOption[]): MenuOption[] {\n // First, remove duplicates (giving precedence to Vue hardcoded options)\n const deduplicated = removeDuplicateMenuOptions(options)\n const coreItemsMap = new Map<string, MenuOption>()\n const extensionItems: MenuOption[] = []\n let deleteItem: MenuOption | undefined\n\n // Separate items into core and extension categories\n for (const option of deduplicated) {\n // Skip dividers for now - we'll add them between sections later\n if (option.type === 'divider') {\n continue\n }\n\n // Skip category labels (they'll be added separately)\n if (option.type === 'category') {\n continue\n }\n\n // Check if this is the Delete/Remove item - save it for the end\n const isDeleteItem = option.label === 'Delete' || option.label === 'Remove'\n if (isDeleteItem && !option.hasSubmenu) {\n deleteItem = option\n continue\n }\n\n // Categorize based on label\n if (option.label && isCoreMenuItem(option.label)) {\n coreItemsMap.set(option.label, option)\n } else {\n extensionItems.push(option)\n }\n }\n // Build ordered core items based on MENU_ORDER\n const orderedCoreItems: MenuOption[] = []\n const coreLabels = Array.from(coreItemsMap.keys())\n coreLabels.sort((a, b) => getMenuItemOrder(a) - getMenuItemOrder(b))\n\n // Section boundaries based on MENU_ORDER indices\n // Section 1: 0-2 (Rename, Copy, Duplicate)\n // Section 2: 3-8 (Run Branch, Pin, Unpin, Bypass, Remove Bypass, Mute)\n // Section 3: 9-15 (Convert to Subgraph, Frame selection, Minimize Node, Expand, Collapse, Resize, Clone)\n // Section 4: 16-17 (Node Info, Color)\n // Section 5: 18+ (Image operations and fallback items)\n const getSectionNumber = (index: number): number => {\n if (index <= 2) return 1\n if (index <= 8) return 2\n if (index <= 15) return 3\n if (index <= 17) return 4\n return 5\n }\n\n let lastSection = 0\n for (const label of coreLabels) {\n const item = coreItemsMap.get(label)!\n const itemIndex = getMenuItemOrder(label)\n const currentSection = getSectionNumber(itemIndex)\n\n // Add divider when moving to a new section\n if (lastSection > 0 && currentSection !== lastSection) {\n orderedCoreItems.push({ type: 'divider' })\n }\n\n orderedCoreItems.push(item)\n lastSection = currentSection\n }\n\n // Build the final menu structure\n const result: MenuOption[] = []\n\n // Add ordered core items with their dividers\n result.push(...orderedCoreItems)\n\n // Add extensions section if there are extension items\n if (extensionItems.length > 0) {\n // Add divider before Extensions section\n result.push({ type: 'divider' })\n\n // Add non-clickable Extensions label\n result.push({\n label: 'Extensions',\n type: 'category',\n disabled: true\n })\n\n // Add extension items\n result.push(...extensionItems)\n }\n\n // Add Delete at the bottom if it exists\n if (deleteItem) {\n result.push({ type: 'divider' })\n result.push(deleteItem)\n }\n\n return result\n}\n\n/**\n * Convert LiteGraph IContextMenuValue items to Vue MenuOption format\n * Used to bridge LiteGraph context menus into Vue node menus\n * @param items - The LiteGraph menu items to convert\n * @param node - The node context (optional)\n * @param applyStructuring - Whether to apply menu structuring (core/extensions separation). Defaults to true.\n */\nexport function convertContextMenuToOptions(\n items: (IContextMenuValue | null)[],\n node?: LGraphNode,\n applyStructuring: boolean = true\n): MenuOption[] {\n const result: MenuOption[] = []\n\n for (const item of items) {\n // Null items are separators in LiteGraph\n if (item === null) {\n result.push({ type: 'divider' })\n continue\n }\n\n // Skip items without content (shouldn't happen, but be safe)\n if (!item.content) {\n continue\n }\n\n // Skip hard blacklisted items\n if (HARD_BLACKLIST.has(item.content)) {\n continue\n }\n\n // Skip if a similar item already exists in results\n if (isDuplicateItem(item.content, result)) {\n continue\n }\n\n const option: MenuOption = {\n label: item.content,\n source: 'litegraph'\n }\n\n // Pass through disabled state\n if (item.disabled) {\n option.disabled = true\n }\n\n // Handle submenus\n if (item.has_submenu) {\n // Static submenu with pre-defined options\n if (item.submenu?.options) {\n option.hasSubmenu = true\n option.submenu = convertSubmenuToOptions(item.submenu.options)\n }\n // Dynamic submenu - callback creates it on-demand\n else if (item.callback && !item.disabled) {\n option.hasSubmenu = true\n // Intercept the callback to capture dynamic submenu items\n const capturedSubmenu = captureDynamicSubmenu(item, node)\n if (capturedSubmenu) {\n option.submenu = capturedSubmenu\n } else {\n console.warn(\n '[ContextMenuConverter] Failed to capture submenu for:',\n item.content\n )\n }\n }\n }\n // Handle callback (only if not disabled and not a submenu)\n else if (item.callback && !item.disabled) {\n // Wrap the callback to match the () => void signature\n option.action = () => {\n try {\n void item.callback?.call(\n item as unknown as ContextMenuDivElement,\n item.value,\n {},\n undefined,\n undefined,\n item\n )\n } catch (error) {\n console.error('Error executing context menu callback:', error)\n }\n }\n }\n\n result.push(option)\n }\n\n // Apply structured menu with core items and extensions section (if requested)\n if (applyStructuring) {\n return buildStructuredMenu(result)\n }\n\n return result\n}\n\n/**\n * Capture submenu items from a dynamic submenu callback\n * Intercepts ContextMenu constructor to extract items without creating HTML menu\n */\nfunction captureDynamicSubmenu(\n item: IContextMenuValue,\n node?: LGraphNode\n): SubMenuOption[] | undefined {\n let capturedItems: readonly (IContextMenuValue | string | null)[] | undefined\n let capturedOptions: IContextMenuOptions | undefined\n\n // Store original ContextMenu constructor\n const OriginalContextMenu = LiteGraph.ContextMenu\n\n try {\n // Mock ContextMenu constructor to capture submenu items and options\n LiteGraph.ContextMenu = function (\n items: readonly (IContextMenuValue | string | null)[],\n options?: IContextMenuOptions\n ) {\n // Capture both items and options\n capturedItems = items\n capturedOptions = options\n // Return a minimal mock object to prevent errors\n return {\n close: () => {},\n root: document.createElement('div')\n } as unknown as ContextMenu\n } as unknown as typeof ContextMenu\n\n // Execute the callback to trigger submenu creation\n try {\n // Create a mock MouseEvent for the callback\n const mockEvent = new MouseEvent('click', {\n bubbles: true,\n cancelable: true,\n clientX: 0,\n clientY: 0\n })\n\n // Create a mock parent menu\n const mockMenu = {\n close: () => {},\n root: document.createElement('div')\n } as unknown as ContextMenu\n\n // Call the callback which should trigger ContextMenu constructor\n // Callback signature varies, but typically: (value, options, event, menu, node)\n void item.callback?.call(\n item as unknown as ContextMenuDivElement,\n item.value,\n {},\n mockEvent,\n mockMenu,\n node // Pass the node context for callbacks that need it\n )\n } catch (error) {\n console.warn(\n '[ContextMenuConverter] Error executing callback for:',\n item.content,\n error\n )\n }\n } finally {\n // Always restore original constructor\n LiteGraph.ContextMenu = OriginalContextMenu\n }\n\n // Convert captured items to Vue submenu format\n if (capturedItems) {\n const converted = convertSubmenuToOptions(capturedItems, capturedOptions)\n return converted\n }\n\n console.warn('[ContextMenuConverter] No items captured for:', item.content)\n return undefined\n}\n\n/**\n * Convert LiteGraph submenu items to Vue SubMenuOption format\n */\nfunction convertSubmenuToOptions(\n items: readonly (IContextMenuValue | string | null)[],\n options?: IContextMenuOptions\n): SubMenuOption[] {\n const result: SubMenuOption[] = []\n\n for (const item of items) {\n // Skip null separators\n if (item === null) {\n continue\n }\n\n // Handle string items (simple labels like in Mode/Shapes menus)\n if (typeof item === 'string') {\n const subOption: SubMenuOption = {\n label: item,\n action: () => {\n try {\n // Call the options callback with the string value\n if (options?.callback) {\n void options.callback.call(\n null,\n item,\n options,\n undefined,\n undefined,\n options.extra\n )\n }\n } catch (error) {\n console.error('Error executing string item callback:', error)\n }\n }\n }\n result.push(subOption)\n continue\n }\n\n // Handle object items\n if (!item.content) {\n continue\n }\n\n // Extract text content from HTML if present\n const content = stripHtmlTags(item.content)\n\n const subOption: SubMenuOption = {\n label: content,\n action: () => {\n try {\n void item.callback?.call(\n item as unknown as ContextMenuDivElement,\n item.value,\n {},\n undefined,\n undefined,\n item\n )\n } catch (error) {\n console.error('Error executing submenu callback:', error)\n }\n }\n }\n\n // Pass through disabled state\n if (item.disabled) {\n subOption.disabled = true\n }\n\n result.push(subOption)\n }\n return result\n}\n\n/**\n * Strip HTML tags from content string safely\n * LiteGraph menu items often include HTML for styling\n */\nfunction stripHtmlTags(html: string): string {\n // Use DOMPurify to sanitize and strip all HTML tags\n const sanitized = DOMPurify.sanitize(html, { ALLOWED_TAGS: [] })\n const result = sanitized.trim()\n return result || html.replace(/<[^>]*>/g, '').trim() || html\n}\n","// call nextTick on all changeTracker\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\n/**\n * Composable for refreshing nodes in the graph\n * */\nexport function useCanvasRefresh() {\n const canvasStore = useCanvasStore()\n const workflowStore = useWorkflowStore()\n const refreshCanvas = () => {\n canvasStore.canvas?.emitBeforeChange()\n canvasStore.canvas?.setDirty(true, true)\n canvasStore.canvas?.graph?.afterChange()\n canvasStore.canvas?.emitAfterChange()\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n\n return {\n refreshCanvas\n }\n}\n","import { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport {\n LGraphCanvas,\n LGraphNode,\n LiteGraph,\n RenderShape,\n isColorable\n} from '@/lib/litegraph/src/litegraph'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { adjustColor } from '@/utils/colorUtil'\n\nimport { useCanvasRefresh } from './useCanvasRefresh'\n\ninterface ColorOption {\n name: string\n localizedName: string\n value: {\n dark: string\n light: string\n }\n}\n\ninterface ShapeOption {\n name: string\n localizedName: string\n value: RenderShape\n}\n\n/**\n * Composable for handling node color and shape customization\n */\nexport function useNodeCustomization() {\n const { t } = useI18n()\n const canvasStore = useCanvasStore()\n const colorPaletteStore = useColorPaletteStore()\n const canvasRefresh = useCanvasRefresh()\n const isLightTheme = computed(\n () => colorPaletteStore.completedActivePalette.light_theme\n )\n\n const toLightThemeColor = (color: string) =>\n adjustColor(color, { lightness: 0.5 })\n\n // Color options\n const NO_COLOR_OPTION: ColorOption = {\n name: 'noColor',\n localizedName: t('color.noColor'),\n value: {\n dark: LiteGraph.NODE_DEFAULT_BGCOLOR,\n light: toLightThemeColor(LiteGraph.NODE_DEFAULT_BGCOLOR)\n }\n }\n\n const colorOptions: ColorOption[] = [\n NO_COLOR_OPTION,\n ...Object.entries(LGraphCanvas.node_colors).map(([name, color]) => ({\n name,\n localizedName: t(`color.${name}`),\n value: {\n dark: color.bgcolor,\n light: toLightThemeColor(color.bgcolor)\n }\n }))\n ]\n\n // Shape options\n const shapeOptions: ShapeOption[] = [\n {\n name: 'default',\n localizedName: t('shape.default'),\n value: RenderShape.ROUND\n },\n {\n name: 'box',\n localizedName: t('shape.box'),\n value: RenderShape.BOX\n },\n {\n name: 'card',\n localizedName: t('shape.CARD'),\n value: RenderShape.CARD\n }\n ]\n\n const applyColor = (colorOption: ColorOption | null) => {\n const colorName = colorOption?.name ?? NO_COLOR_OPTION.name\n const canvasColorOption =\n colorName === NO_COLOR_OPTION.name\n ? null\n : LGraphCanvas.node_colors[colorName]\n\n for (const item of canvasStore.selectedItems) {\n if (isColorable(item)) {\n item.setColorOption(canvasColorOption)\n }\n }\n\n canvasRefresh.refreshCanvas()\n }\n\n const applyShape = (shapeOption: ShapeOption) => {\n const selectedNodes = Array.from(canvasStore.selectedItems).filter(\n (item): item is LGraphNode => item instanceof LGraphNode\n )\n\n if (selectedNodes.length === 0) {\n return\n }\n\n selectedNodes.forEach((node) => {\n node.shape = shapeOption.value\n })\n\n canvasRefresh.refreshCanvas()\n }\n\n const getCurrentColor = (): ColorOption | null => {\n const selectedItems = Array.from(canvasStore.selectedItems)\n if (selectedItems.length === 0) return null\n\n // Get color from first colorable item\n const firstColorableItem = selectedItems.find((item) => isColorable(item))\n if (!firstColorableItem || !isColorable(firstColorableItem)) return null\n\n // Get the current color option from the colorable item\n const currentColorOption = firstColorableItem.getColorOption()\n const currentBgColor = currentColorOption?.bgcolor ?? null\n\n // Find matching color option\n return (\n colorOptions.find(\n (option) =>\n option.value.dark === currentBgColor ||\n option.value.light === currentBgColor\n ) ?? NO_COLOR_OPTION\n )\n }\n\n const getCurrentShape = (): ShapeOption | null => {\n const selectedNodes = Array.from(canvasStore.selectedItems).filter(\n (item): item is LGraphNode => item instanceof LGraphNode\n )\n\n if (selectedNodes.length === 0) return null\n\n const firstNode = selectedNodes[0]\n const currentShape = firstNode.shape ?? RenderShape.ROUND\n\n return (\n shapeOptions.find((option) => option.value === currentShape) ??\n shapeOptions[0]\n )\n }\n\n return {\n colorOptions,\n shapeOptions,\n applyColor,\n applyShape,\n getCurrentColor,\n getCurrentShape,\n isLightTheme\n }\n}\n","import { useI18n } from 'vue-i18n'\n\nimport { LGraphEventMode } from '@/lib/litegraph/src/litegraph'\nimport type { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\nimport { useCanvasRefresh } from './useCanvasRefresh'\nimport type { MenuOption } from './useMoreOptionsMenu'\nimport { useNodeCustomization } from './useNodeCustomization'\n\n/**\n * Composable for group-related menu operations\n */\nexport function useGroupMenuOptions() {\n const { t } = useI18n()\n const canvasStore = useCanvasStore()\n const workflowStore = useWorkflowStore()\n const settingStore = useSettingStore()\n const canvasRefresh = useCanvasRefresh()\n const { shapeOptions, colorOptions, isLightTheme } = useNodeCustomization()\n\n const getFitGroupToNodesOption = (groupContext: LGraphGroup): MenuOption => ({\n label: 'Fit Group To Nodes',\n icon: 'icon-[lucide--move-diagonal-2]',\n action: () => {\n try {\n groupContext.recomputeInsideNodes()\n } catch (e) {\n console.warn('Failed to recompute group nodes:', e)\n return\n }\n\n const padding = settingStore.get('Comfy.GroupSelectedNodes.Padding')\n groupContext.resizeTo(groupContext.children, padding)\n groupContext.graph?.change()\n canvasStore.canvas?.setDirty(true, true)\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n })\n\n const getGroupShapeOptions = (\n groupContext: LGraphGroup,\n bump: () => void\n ): MenuOption => ({\n label: t('contextMenu.Shape'),\n icon: 'icon-[lucide--box]',\n hasSubmenu: true,\n submenu: shapeOptions.map((shape) => ({\n label: shape.localizedName,\n action: () => {\n const nodes = (groupContext.nodes || []) as LGraphNode[]\n nodes.forEach((node) => (node.shape = shape.value))\n canvasRefresh.refreshCanvas()\n bump()\n }\n }))\n })\n\n const getGroupColorOptions = (\n groupContext: LGraphGroup,\n bump: () => void\n ): MenuOption => ({\n label: t('contextMenu.Color'),\n icon: 'icon-[lucide--palette]',\n hasSubmenu: true,\n submenu: colorOptions.map((colorOption) => ({\n label: colorOption.localizedName,\n color: isLightTheme.value\n ? colorOption.value.light\n : colorOption.value.dark,\n action: () => {\n groupContext.color = isLightTheme.value\n ? colorOption.value.light\n : colorOption.value.dark\n canvasRefresh.refreshCanvas()\n bump()\n }\n }))\n })\n\n const getGroupModeOptions = (\n groupContext: LGraphGroup,\n bump: () => void\n ): MenuOption[] => {\n const options: MenuOption[] = []\n\n try {\n groupContext.recomputeInsideNodes()\n } catch (e) {\n console.warn('Failed to recompute group nodes for mode options:', e)\n return options\n }\n\n const groupNodes = (groupContext.nodes || []) as LGraphNode[]\n if (!groupNodes.length) return options\n\n // Check if all nodes have the same mode\n let allSame = true\n for (let i = 1; i < groupNodes.length; i++) {\n if (groupNodes[i].mode !== groupNodes[0].mode) {\n allSame = false\n break\n }\n }\n\n const createModeAction = (label: string, mode: LGraphEventMode) => ({\n label: t(`selectionToolbox.${label}`),\n icon:\n mode === LGraphEventMode.BYPASS\n ? 'icon-[lucide--ban]'\n : mode === LGraphEventMode.NEVER\n ? 'icon-[lucide--zap-off]'\n : 'icon-[lucide--play]',\n action: () => {\n groupNodes.forEach((n) => {\n n.mode = mode\n })\n canvasStore.canvas?.setDirty(true, true)\n groupContext.graph?.change()\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n bump()\n }\n })\n\n if (allSame) {\n const current = groupNodes[0].mode\n switch (current) {\n case LGraphEventMode.ALWAYS:\n options.push(\n createModeAction('Set Group Nodes to Never', LGraphEventMode.NEVER)\n )\n options.push(\n createModeAction('Bypass Group Nodes', LGraphEventMode.BYPASS)\n )\n break\n case LGraphEventMode.NEVER:\n options.push(\n createModeAction(\n 'Set Group Nodes to Always',\n LGraphEventMode.ALWAYS\n )\n )\n options.push(\n createModeAction('Bypass Group Nodes', LGraphEventMode.BYPASS)\n )\n break\n case LGraphEventMode.BYPASS:\n options.push(\n createModeAction(\n 'Set Group Nodes to Always',\n LGraphEventMode.ALWAYS\n )\n )\n options.push(\n createModeAction('Set Group Nodes to Never', LGraphEventMode.NEVER)\n )\n break\n default:\n options.push(\n createModeAction(\n 'Set Group Nodes to Always',\n LGraphEventMode.ALWAYS\n )\n )\n options.push(\n createModeAction('Set Group Nodes to Never', LGraphEventMode.NEVER)\n )\n options.push(\n createModeAction('Bypass Group Nodes', LGraphEventMode.BYPASS)\n )\n break\n }\n } else {\n options.push(\n createModeAction('Set Group Nodes to Always', LGraphEventMode.ALWAYS)\n )\n options.push(\n createModeAction('Set Group Nodes to Never', LGraphEventMode.NEVER)\n )\n options.push(\n createModeAction('Bypass Group Nodes', LGraphEventMode.BYPASS)\n )\n }\n\n return options\n }\n\n return {\n getFitGroupToNodesOption,\n getGroupShapeOptions,\n getGroupColorOptions,\n getGroupModeOptions\n }\n}\n","import { useI18n } from 'vue-i18n'\n\nimport { downloadFile } from '@/base/common/downloadUtil'\nimport { useCommandStore } from '@/stores/commandStore'\n\nimport type { MenuOption } from './useMoreOptionsMenu'\n\n/**\n * Composable for image-related menu operations\n */\nexport function useImageMenuOptions() {\n const { t } = useI18n()\n\n const openMaskEditor = () => {\n const commandStore = useCommandStore()\n void commandStore.execute('Comfy.MaskEditor.OpenMaskEditor')\n }\n\n const openImage = (node: any) => {\n if (!node?.imgs?.length) return\n const img = node.imgs[node.imageIndex ?? 0]\n if (!img) return\n const url = new URL(img.src)\n url.searchParams.delete('preview')\n window.open(url.toString(), '_blank')\n }\n\n const copyImage = async (node: any) => {\n if (!node?.imgs?.length) return\n const img = node.imgs[node.imageIndex ?? 0]\n if (!img) return\n\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n if (!ctx) return\n\n canvas.width = img.naturalWidth\n canvas.height = img.naturalHeight\n ctx.drawImage(img, 0, 0)\n\n try {\n const blob = await new Promise<Blob | null>((resolve) => {\n canvas.toBlob(resolve, 'image/png')\n })\n\n if (!blob) {\n console.warn('Failed to create image blob')\n return\n }\n\n // Check if clipboard API is available\n if (!navigator.clipboard?.write) {\n console.warn('Clipboard API not available')\n return\n }\n\n await navigator.clipboard.write([\n new ClipboardItem({ 'image/png': blob })\n ])\n } catch (error) {\n console.error('Failed to copy image to clipboard:', error)\n }\n }\n\n const saveImage = (node: any) => {\n if (!node?.imgs?.length) return\n const img = node.imgs[node.imageIndex ?? 0]\n if (!img) return\n\n try {\n const url = new URL(img.src)\n url.searchParams.delete('preview')\n downloadFile(url.toString())\n } catch (error) {\n console.error('Failed to save image:', error)\n }\n }\n\n const getImageMenuOptions = (node: any): MenuOption[] => {\n if (!node?.imgs?.length) return []\n\n return [\n {\n label: t('contextMenu.Open in Mask Editor'),\n action: () => openMaskEditor()\n },\n {\n label: t('contextMenu.Open Image'),\n icon: 'icon-[lucide--external-link]',\n action: () => openImage(node)\n },\n {\n label: t('contextMenu.Copy Image'),\n icon: 'icon-[lucide--copy]',\n action: () => copyImage(node)\n },\n {\n label: t('contextMenu.Save Image'),\n icon: 'icon-[lucide--download]',\n action: () => saveImage(node)\n }\n ]\n }\n\n return {\n getImageMenuOptions\n }\n}\n","import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'\nimport { LGraphEventMode } from '@/lib/litegraph/src/litegraph'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { app } from '@/scripts/app'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { filterOutputNodes } from '@/utils/nodeFilterUtil'\n\n/**\n * Composable for handling node information and utility operations\n */\nexport function useSelectedNodeActions() {\n const { getSelectedNodes, toggleSelectedNodesMode } =\n useSelectedLiteGraphItems()\n const commandStore = useCommandStore()\n const workflowStore = useWorkflowStore()\n\n const adjustNodeSize = () => {\n const selectedNodes = getSelectedNodes()\n\n selectedNodes.forEach((node) => {\n const optimalSize = node.computeSize()\n node.setSize([optimalSize[0], optimalSize[1]])\n })\n\n app.canvas.setDirty(true, true)\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n\n const toggleNodeCollapse = () => {\n const selectedNodes = getSelectedNodes()\n selectedNodes.forEach((node) => {\n node.collapse()\n })\n\n app.canvas.setDirty(true, true)\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n\n const toggleNodePin = () => {\n const selectedNodes = getSelectedNodes()\n selectedNodes.forEach((node) => {\n node.pin(!node.pinned)\n })\n\n app.canvas.setDirty(true, true)\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n\n const toggleNodeBypass = () => {\n toggleSelectedNodesMode(LGraphEventMode.BYPASS)\n app.canvas.setDirty(true, true)\n }\n\n const runBranch = async () => {\n const selectedNodes = getSelectedNodes()\n const selectedOutputNodes = filterOutputNodes(selectedNodes)\n if (selectedOutputNodes.length === 0) return\n await commandStore.execute('Comfy.QueueSelectedOutputNodes')\n }\n\n return {\n adjustNodeSize,\n toggleNodeCollapse,\n toggleNodePin,\n toggleNodeBypass,\n runBranch\n }\n}\n","import { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport type { MenuOption } from './useMoreOptionsMenu'\nimport { useNodeCustomization } from './useNodeCustomization'\nimport { useSelectedNodeActions } from './useSelectedNodeActions'\nimport type { NodeSelectionState } from './useSelectionState'\n\n/**\n * Composable for node-related menu operations\n */\nexport function useNodeMenuOptions() {\n const { t } = useI18n()\n const { shapeOptions, applyShape, applyColor, colorOptions, isLightTheme } =\n useNodeCustomization()\n const {\n adjustNodeSize,\n toggleNodeCollapse,\n toggleNodePin,\n toggleNodeBypass,\n runBranch\n } = useSelectedNodeActions()\n\n const shapeSubmenu = computed(() =>\n shapeOptions.map((shape) => ({\n label: shape.localizedName,\n action: () => applyShape(shape)\n }))\n )\n\n const colorSubmenu = computed(() => {\n return colorOptions.map((colorOption) => ({\n label: colorOption.localizedName,\n color: isLightTheme.value\n ? colorOption.value.light\n : colorOption.value.dark,\n action: () =>\n applyColor(colorOption.name === 'noColor' ? null : colorOption)\n }))\n })\n\n const getAdjustSizeOption = (): MenuOption => ({\n label: t('contextMenu.Adjust Size'),\n icon: 'icon-[lucide--move-diagonal-2]',\n action: adjustNodeSize\n })\n\n const getNodeVisualOptions = (\n states: NodeSelectionState,\n bump: () => void\n ): MenuOption[] => [\n {\n label: states.collapsed\n ? t('contextMenu.Expand Node')\n : t('contextMenu.Minimize Node'),\n icon: states.collapsed\n ? 'icon-[lucide--maximize-2]'\n : 'icon-[lucide--minimize-2]',\n action: () => {\n toggleNodeCollapse()\n bump()\n }\n },\n {\n label: t('contextMenu.Shape'),\n icon: 'icon-[lucide--box]',\n hasSubmenu: true,\n submenu: shapeSubmenu.value,\n action: () => {}\n },\n {\n label: t('contextMenu.Color'),\n icon: 'icon-[lucide--palette]',\n hasSubmenu: true,\n submenu: colorSubmenu.value,\n isColorPicker: true,\n action: () => {}\n }\n ]\n\n const getPinOption = (\n states: NodeSelectionState,\n bump: () => void\n ): MenuOption => ({\n label: states.pinned ? t('contextMenu.Unpin') : t('contextMenu.Pin'),\n icon: states.pinned ? 'icon-[lucide--pin-off]' : 'icon-[lucide--pin]',\n action: () => {\n toggleNodePin()\n bump()\n }\n })\n\n const getBypassOption = (\n states: NodeSelectionState,\n bump: () => void\n ): MenuOption => ({\n label: states.bypassed\n ? t('contextMenu.Remove Bypass')\n : t('contextMenu.Bypass'),\n icon: 'icon-[lucide--redo-dot]',\n shortcut: 'Ctrl+B',\n action: () => {\n toggleNodeBypass()\n bump()\n }\n })\n\n const getRunBranchOption = (): MenuOption => ({\n label: t('contextMenu.Run Branch'),\n icon: 'icon-[lucide--play]',\n action: runBranch\n })\n\n const getNodeInfoOption = (showNodeHelp: () => void): MenuOption => ({\n label: t('contextMenu.Node Info'),\n icon: 'icon-[lucide--info]',\n action: showNodeHelp\n })\n\n return {\n getNodeInfoOption,\n getAdjustSizeOption,\n getNodeVisualOptions,\n getPinOption,\n getBypassOption,\n getRunBranchOption,\n colorSubmenu\n }\n}\n","import { computed } from 'vue'\n\nimport { useSelectionState } from '@/composables/graph/useSelectionState'\nimport { LGraphGroup } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTitleEditorStore } from '@/renderer/core/canvas/canvasStore'\nimport { app } from '@/scripts/app'\n\n/**\n * Composable encapsulating logic for framing currently selected nodes into a group.\n */\nexport function useFrameNodes() {\n const settingStore = useSettingStore()\n const titleEditorStore = useTitleEditorStore()\n const { hasMultipleSelection } = useSelectionState()\n\n const canFrame = computed(() => hasMultipleSelection.value)\n\n const frameNodes = () => {\n const { canvas } = app\n if (!canvas.selectedItems?.size) return\n const group = new LGraphGroup()\n const padding = settingStore.get('Comfy.GroupSelectedNodes.Padding')\n group.resizeTo(canvas.selectedItems, padding)\n canvas.graph?.add(group)\n titleEditorStore.titleEditorTarget = group\n }\n\n return { frameNodes, canFrame }\n}\n","import { useI18n } from 'vue-i18n'\n\nimport type { Direction } from '@/lib/litegraph/src/interfaces'\nimport { alignNodes, distributeNodes } from '@/lib/litegraph/src/utils/arrange'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { isLGraphNode } from '@/utils/litegraphUtil'\n\nimport { useCanvasRefresh } from './useCanvasRefresh'\n\ninterface AlignOption {\n name: string\n localizedName: string\n value: Direction\n icon: string\n}\n\ninterface DistributeOption {\n name: string\n localizedName: string\n value: boolean // true for horizontal, false for vertical\n icon: string\n}\n\n/**\n * Composable for handling node alignment and distribution\n */\nexport function useNodeArrangement() {\n const { t } = useI18n()\n const canvasStore = useCanvasStore()\n const canvasRefresh = useCanvasRefresh()\n const alignOptions: AlignOption[] = [\n {\n name: 'top',\n localizedName: t('contextMenu.Top'),\n value: 'top',\n icon: 'icon-[lucide--align-start-vertical]'\n },\n {\n name: 'bottom',\n localizedName: t('contextMenu.Bottom'),\n value: 'bottom',\n icon: 'icon-[lucide--align-end-vertical]'\n },\n {\n name: 'left',\n localizedName: t('contextMenu.Left'),\n value: 'left',\n icon: 'icon-[lucide--align-start-horizontal]'\n },\n {\n name: 'right',\n localizedName: t('contextMenu.Right'),\n value: 'right',\n icon: 'icon-[lucide--align-end-horizontal]'\n }\n ]\n\n const distributeOptions: DistributeOption[] = [\n {\n name: 'horizontal',\n localizedName: t('contextMenu.Horizontal'),\n value: true,\n icon: 'icon-[lucide--align-center-horizontal]'\n },\n {\n name: 'vertical',\n localizedName: t('contextMenu.Vertical'),\n value: false,\n icon: 'icon-[lucide--align-center-vertical]'\n }\n ]\n\n const applyAlign = (alignOption: AlignOption) => {\n const selectedNodes = Array.from(canvasStore.selectedItems).filter((item) =>\n isLGraphNode(item)\n )\n\n if (selectedNodes.length === 0) {\n return\n }\n\n const newPositions = alignNodes(selectedNodes, alignOption.value)\n canvasStore.canvas?.repositionNodesVueMode(newPositions)\n\n canvasRefresh.refreshCanvas()\n }\n\n const applyDistribute = (distributeOption: DistributeOption) => {\n const selectedNodes = Array.from(canvasStore.selectedItems).filter((item) =>\n isLGraphNode(item)\n )\n\n if (selectedNodes.length < 2) {\n return\n }\n\n const newPositions = distributeNodes(selectedNodes, distributeOption.value)\n canvasStore.canvas?.repositionNodesVueMode(newPositions)\n canvasRefresh.refreshCanvas()\n }\n\n return {\n alignOptions,\n distributeOptions,\n applyAlign,\n applyDistribute\n }\n}\n","// import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems' // Unused for now\nimport { t } from '@/i18n'\nimport { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useToastStore } from '@/platform/updates/common/toastStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport {\n useCanvasStore,\n useTitleEditorStore\n} from '@/renderer/core/canvas/canvasStore'\nimport { app } from '@/scripts/app'\nimport { useDialogService } from '@/services/dialogService'\n\n/**\n * Composable for handling basic selection operations like copy, paste, duplicate, delete, rename\n */\nexport function useSelectionOperations() {\n // const { getSelectedNodes } = useSelectedLiteGraphItems() // Unused for now\n const canvasStore = useCanvasStore()\n const toastStore = useToastStore()\n const dialogService = useDialogService()\n const titleEditorStore = useTitleEditorStore()\n const workflowStore = useWorkflowStore()\n\n const copySelection = () => {\n const canvas = app.canvas\n if (!canvas.selectedItems || canvas.selectedItems.size === 0) {\n toastStore.add({\n severity: 'warn',\n summary: t('g.nothingToCopy'),\n detail: t('g.selectItemsToCopy'),\n life: 3000\n })\n return\n }\n\n canvas.copyToClipboard()\n toastStore.add({\n severity: 'success',\n summary: t('g.copied'),\n detail: t('g.itemsCopiedToClipboard'),\n life: 2000\n })\n }\n\n const pasteSelection = () => {\n const canvas = app.canvas\n canvas.pasteFromClipboard({ connectInputs: false })\n\n // Trigger change tracking\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n\n const duplicateSelection = () => {\n const canvas = app.canvas\n if (!canvas.selectedItems || canvas.selectedItems.size === 0) {\n toastStore.add({\n severity: 'warn',\n summary: t('g.nothingToDuplicate'),\n detail: t('g.selectItemsToDuplicate'),\n life: 3000\n })\n return\n }\n\n // Copy current selection\n canvas.copyToClipboard()\n\n // Clear selection to avoid confusion\n canvas.selectedItems.clear()\n canvasStore.updateSelectedItems()\n\n // Paste to create duplicates\n canvas.pasteFromClipboard({ connectInputs: false })\n\n // Trigger change tracking\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n\n const deleteSelection = () => {\n const canvas = app.canvas\n if (!canvas.selectedItems || canvas.selectedItems.size === 0) {\n toastStore.add({\n severity: 'warn',\n summary: t('g.nothingToDelete'),\n detail: t('g.selectItemsToDelete'),\n life: 3000\n })\n return\n }\n\n canvas.deleteSelected()\n canvas.setDirty(true, true)\n\n // Trigger change tracking\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n\n const renameSelection = async () => {\n const selectedItems = Array.from(canvasStore.selectedItems)\n\n // Handle single node selection\n if (selectedItems.length === 1) {\n const item = selectedItems[0]\n\n // For nodes, use the title editor\n if (item instanceof LGraphNode) {\n titleEditorStore.titleEditorTarget = item\n return\n }\n\n // For other items like groups, use prompt dialog\n const currentTitle = 'title' in item ? (item.title as string) : ''\n const newTitle = await dialogService.prompt({\n title: t('g.rename'),\n message: t('g.enterNewName'),\n defaultValue: currentTitle\n })\n\n if (newTitle && newTitle !== currentTitle) {\n if ('title' in item) {\n // Type-safe assignment for items with title property\n const titledItem = item as { title: string }\n titledItem.title = newTitle\n app.canvas.setDirty(true, true)\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n }\n return\n }\n\n // Handle multiple selections - batch rename\n if (selectedItems.length > 1) {\n const baseTitle = await dialogService.prompt({\n title: t('g.batchRename'),\n message: t('g.enterBaseName'),\n defaultValue: 'Item'\n })\n\n if (baseTitle) {\n selectedItems.forEach((item, index) => {\n if ('title' in item) {\n // Type-safe assignment for items with title property\n const titledItem = item as { title: string }\n titledItem.title = `${baseTitle} ${index + 1}`\n }\n })\n app.canvas.setDirty(true, true)\n workflowStore.activeWorkflow?.changeTracker?.checkState()\n }\n return\n }\n\n toastStore.add({\n severity: 'warn',\n summary: t('g.nothingToRename'),\n detail: t('g.selectItemsToRename'),\n life: 3000\n })\n }\n\n return {\n copySelection,\n pasteSelection,\n duplicateSelection,\n deleteSelection,\n renameSelection\n }\n}\n","import { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useCommandStore } from '@/stores/commandStore'\n\nimport { useFrameNodes } from './useFrameNodes'\nimport { BadgeVariant } from './useMoreOptionsMenu'\nimport type { MenuOption } from './useMoreOptionsMenu'\nimport { useNodeArrangement } from './useNodeArrangement'\nimport { useSelectionOperations } from './useSelectionOperations'\nimport { useSubgraphOperations } from './useSubgraphOperations'\n\n/**\n * Composable for selection-related menu operations\n */\nexport function useSelectionMenuOptions() {\n const { t } = useI18n()\n const {\n copySelection,\n duplicateSelection,\n deleteSelection,\n renameSelection\n } = useSelectionOperations()\n\n const { alignOptions, distributeOptions, applyAlign, applyDistribute } =\n useNodeArrangement()\n\n const { convertToSubgraph, unpackSubgraph, addSubgraphToLibrary } =\n useSubgraphOperations()\n\n const { frameNodes } = useFrameNodes()\n\n const alignSubmenu = computed(() =>\n alignOptions.map((align) => ({\n label: align.localizedName,\n icon: align.icon,\n action: () => applyAlign(align)\n }))\n )\n\n const distributeSubmenu = computed(() =>\n distributeOptions.map((distribute) => ({\n label: distribute.localizedName,\n icon: distribute.icon,\n action: () => applyDistribute(distribute)\n }))\n )\n\n const getBasicSelectionOptions = (): MenuOption[] => [\n {\n label: t('contextMenu.Rename'),\n action: renameSelection\n },\n {\n label: t('contextMenu.Copy'),\n shortcut: 'Ctrl+C',\n action: copySelection\n },\n {\n label: t('contextMenu.Duplicate'),\n shortcut: 'Ctrl+D',\n action: duplicateSelection\n }\n ]\n\n const getSubgraphOptions = ({\n hasSubgraphs,\n hasMultipleSelection\n }: {\n hasSubgraphs: boolean\n hasMultipleSelection: boolean\n }): MenuOption[] => {\n const convertOption: MenuOption = {\n label: t('contextMenu.Convert to Subgraph'),\n icon: 'icon-[lucide--shrink]',\n action: convertToSubgraph,\n badge: BadgeVariant.NEW\n }\n\n const options: MenuOption[] = []\n const showConvertOption = !hasSubgraphs || hasMultipleSelection\n\n if (showConvertOption) {\n options.push(convertOption)\n }\n\n if (hasSubgraphs) {\n options.push(\n {\n label: t('contextMenu.Add Subgraph to Library'),\n icon: 'icon-[lucide--folder-plus]',\n action: addSubgraphToLibrary\n },\n {\n label: t('contextMenu.Unpack Subgraph'),\n icon: 'icon-[lucide--expand]',\n action: unpackSubgraph\n }\n )\n }\n\n return options\n }\n\n const getMultipleNodesOptions = (): MenuOption[] => {\n const convertToGroupNodes = () => {\n const commandStore = useCommandStore()\n void commandStore.execute(\n 'Comfy.GroupNode.ConvertSelectedNodesToGroupNode'\n )\n }\n\n return [\n {\n label: t('contextMenu.Convert to Group Node'),\n icon: 'icon-[lucide--group]',\n action: convertToGroupNodes,\n badge: BadgeVariant.DEPRECATED\n },\n {\n label: t('g.frameNodes'),\n icon: 'icon-[lucide--frame]',\n action: frameNodes\n }\n ]\n }\n\n const getAlignmentOptions = (): MenuOption[] => [\n {\n label: t('contextMenu.Align Selected To'),\n icon: 'icon-[lucide--align-start-horizontal]',\n hasSubmenu: true,\n submenu: alignSubmenu.value,\n action: () => {}\n },\n {\n label: t('contextMenu.Distribute Nodes'),\n icon: 'icon-[lucide--align-center-horizontal]',\n hasSubmenu: true,\n submenu: distributeSubmenu.value,\n action: () => {}\n }\n ]\n\n const getDeleteOption = (): MenuOption => ({\n label: t('contextMenu.Delete'),\n icon: 'icon-[lucide--trash-2]',\n shortcut: 'Delete',\n action: deleteSelection\n })\n\n return {\n getBasicSelectionOptions,\n getSubgraphOptions,\n getMultipleNodesOptions,\n getDeleteOption,\n getAlignmentOptions,\n alignSubmenu,\n distributeSubmenu\n }\n}\n","import { computed, ref } from 'vue'\nimport type { Ref } from 'vue'\n\nimport type { LGraphGroup } from '@/lib/litegraph/src/litegraph'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { isLGraphGroup } from '@/utils/litegraphUtil'\n\nimport {\n buildStructuredMenu,\n convertContextMenuToOptions\n} from './contextMenuConverter'\nimport { useGroupMenuOptions } from './useGroupMenuOptions'\nimport { useImageMenuOptions } from './useImageMenuOptions'\nimport { useNodeMenuOptions } from './useNodeMenuOptions'\nimport { useSelectionMenuOptions } from './useSelectionMenuOptions'\nimport { useSelectionState } from './useSelectionState'\n\nexport interface MenuOption {\n label?: string\n icon?: string\n shortcut?: string\n hasSubmenu?: boolean\n type?: 'divider' | 'category'\n action?: () => void\n submenu?: SubMenuOption[]\n badge?: BadgeVariant\n disabled?: boolean\n source?: 'litegraph' | 'vue'\n isColorPicker?: boolean\n}\n\nexport interface SubMenuOption {\n label: string\n icon?: string\n action: () => void\n color?: string\n disabled?: boolean\n}\n\nexport enum BadgeVariant {\n NEW = 'new',\n DEPRECATED = 'deprecated'\n}\n\n// Global singleton for NodeOptions component reference\nlet nodeOptionsInstance: null | NodeOptionsInstance = null\n\n/**\n * Toggle the node options popover\n * @param event - The trigger event\n */\nexport function toggleNodeOptions(event: Event) {\n if (nodeOptionsInstance?.toggle) {\n nodeOptionsInstance.toggle(event)\n }\n}\n\n/**\n * Show the node options popover (always shows, doesn't toggle)\n * Use this for contextmenu events where we always want to show at the new position\n * @param event - The trigger event (must be MouseEvent for position)\n */\nexport function showNodeOptions(event: MouseEvent) {\n if (nodeOptionsInstance?.show) {\n nodeOptionsInstance.show(event)\n }\n}\n\n/**\n * Hide the node options popover\n */\ninterface NodeOptionsInstance {\n toggle: (event: Event) => void\n show: (event: MouseEvent) => void\n hide: () => void\n isOpen: Ref<boolean>\n}\n\n/**\n * Register the NodeOptions component instance\n * @param instance - The NodeOptions component instance\n */\nexport function registerNodeOptionsInstance(\n instance: null | NodeOptionsInstance\n) {\n nodeOptionsInstance = instance\n}\n\n/**\n * Mark menu options as coming from Vue hardcoded menu\n */\nfunction markAsVueOptions(options: MenuOption[]): MenuOption[] {\n return options.map((opt) => {\n // Don't mark dividers or category labels\n if (opt.type === 'divider' || opt.type === 'category') {\n return opt\n }\n return { ...opt, source: 'vue' }\n })\n}\n\n/**\n * Composable for managing the More Options menu configuration\n * Refactored to use smaller, focused composables for better maintainability\n */\nexport function useMoreOptionsMenu() {\n const {\n selectedItems,\n selectedNodes,\n nodeDef,\n showNodeHelp,\n hasSubgraphs: hasSubgraphsComputed,\n hasImageNode,\n hasOutputNodesSelected,\n hasMultipleSelection,\n computeSelectionFlags\n } = useSelectionState()\n\n const canvasStore = useCanvasStore()\n\n const { getImageMenuOptions } = useImageMenuOptions()\n const {\n getNodeInfoOption,\n getNodeVisualOptions,\n getPinOption,\n getBypassOption,\n getRunBranchOption\n } = useNodeMenuOptions()\n const {\n getFitGroupToNodesOption,\n getGroupColorOptions,\n getGroupModeOptions\n } = useGroupMenuOptions()\n const {\n getBasicSelectionOptions,\n getSubgraphOptions,\n getMultipleNodesOptions\n } = useSelectionMenuOptions()\n\n const hasSubgraphs = hasSubgraphsComputed\n const hasMultipleNodes = hasMultipleSelection\n\n // Internal version to force menu rebuild after state mutations\n const optionsVersion = ref(0)\n const bump = () => {\n optionsVersion.value++\n }\n\n const menuOptions = computed((): MenuOption[] => {\n // Reference selection flags to ensure re-computation when they change\n\n optionsVersion.value\n const states = computeSelectionFlags()\n\n // Detect single group selection context (and no nodes explicitly selected)\n const selectedGroups = selectedItems.value.filter(\n isLGraphGroup\n ) as LGraphGroup[]\n const groupContext: LGraphGroup | null =\n selectedGroups.length === 1 && selectedNodes.value.length === 0\n ? selectedGroups[0]\n : null\n const hasSubgraphsSelected = hasSubgraphs.value\n\n // For single node selection, also get LiteGraph menu items to merge\n const litegraphOptions: MenuOption[] = []\n if (\n selectedNodes.value.length === 1 &&\n !groupContext &&\n canvasStore.canvas\n ) {\n try {\n const node = selectedNodes.value[0]\n const rawItems = canvasStore.canvas.getNodeMenuOptions(node)\n // Don't apply structuring yet - we'll do it after merging with Vue options\n litegraphOptions.push(\n ...convertContextMenuToOptions(rawItems, node, false)\n )\n } catch (error) {\n console.error('Error getting LiteGraph menu items:', error)\n }\n }\n\n const options: MenuOption[] = []\n\n // Section 1: Basic selection operations (Rename, Copy, Duplicate)\n const basicOps = getBasicSelectionOptions()\n options.push(...basicOps)\n options.push({ type: 'divider' })\n\n // Section 2: Node actions (Run Branch, Pin, Bypass, Mute)\n if (hasOutputNodesSelected.value) {\n const runBranch = getRunBranchOption()\n options.push(runBranch)\n }\n if (!groupContext) {\n const pin = getPinOption(states, bump)\n const bypass = getBypassOption(states, bump)\n options.push(pin)\n options.push(bypass)\n }\n if (groupContext) {\n const groupModes = getGroupModeOptions(groupContext, bump)\n options.push(...groupModes)\n }\n options.push({ type: 'divider' })\n\n // Section 3: Structure operations (Convert to Subgraph, Frame selection, Minimize Node)\n options.push(\n ...getSubgraphOptions({\n hasSubgraphs: hasSubgraphsSelected,\n hasMultipleSelection: hasMultipleNodes.value\n })\n )\n if (hasMultipleNodes.value) {\n options.push(...getMultipleNodesOptions())\n }\n if (groupContext) {\n options.push(getFitGroupToNodesOption(groupContext))\n } else {\n // Node context: Expand/Minimize\n const visualOptions = getNodeVisualOptions(states, bump)\n if (visualOptions.length > 0) {\n options.push(visualOptions[0]) // Expand/Minimize (index 0)\n }\n }\n options.push({ type: 'divider' })\n\n // Section 4: Node properties (Node Info, Shape, Color)\n if (nodeDef.value) {\n options.push(getNodeInfoOption(showNodeHelp))\n }\n if (groupContext) {\n options.push(getGroupColorOptions(groupContext, bump))\n } else {\n // Add shape and color options\n const visualOptions = getNodeVisualOptions(states, bump)\n if (visualOptions.length > 1) {\n options.push(visualOptions[1]) // Shape (index 1)\n }\n if (visualOptions.length > 2) {\n options.push(visualOptions[2]) // Color (index 2)\n }\n }\n options.push({ type: 'divider' })\n\n // Section 5: Image operations (if image node)\n if (hasImageNode.value && selectedNodes.value.length > 0) {\n options.push(...getImageMenuOptions(selectedNodes.value[0]))\n options.push({ type: 'divider' })\n }\n // Section 6 & 7: Extensions and Delete are handled by buildStructuredMenu\n\n // Mark all Vue options with source\n const markedVueOptions = markAsVueOptions(options)\n\n if (litegraphOptions.length > 0) {\n // Merge: LiteGraph options first, then Vue options (Vue will win in dedup)\n const merged = [...litegraphOptions, ...markedVueOptions]\n return buildStructuredMenu(merged)\n }\n // For other cases, structure the Vue options\n const result = buildStructuredMenu(markedVueOptions)\n return result\n })\n\n // Computed property to get only menu items with submenus\n const menuOptionsWithSubmenu = computed(() =>\n menuOptions.value.filter((option) => option.hasSubmenu && option.submenu)\n )\n\n return {\n menuOptions,\n menuOptionsWithSubmenu,\n bump,\n hasSubgraphs,\n registerNodeOptionsInstance\n }\n}\n","<template>\n <Popover\n ref=\"popoverRef\"\n :auto-z-index=\"true\"\n :base-z-index=\"1100\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :pt=\"{\n root: {\n class: 'absolute z-60'\n },\n content: {\n class: [\n 'text-base-foreground rounded-lg',\n 'shadow-lg border border-base-background',\n 'bg-interface-panel-surface'\n ]\n }\n }\"\n >\n <div\n :class=\"\n isColorSubmenu\n ? 'flex flex-col gap-1 p-2'\n : 'flex flex-col p-2 min-w-40'\n \"\n >\n <div\n v-for=\"subOption in option.submenu\"\n :key=\"subOption.label\"\n :class=\"\n cn(\n 'hover:bg-secondary-background-hover rounded cursor-pointer',\n isColorSubmenu\n ? 'w-7 h-7 flex items-center justify-center'\n : 'flex items-center gap-2 px-3 py-1.5 text-sm',\n subOption.disabled\n ? 'cursor-not-allowed pointer-events-none text-node-icon-disabled'\n : 'hover:bg-secondary-background-hover'\n )\n \"\n :title=\"subOption.label\"\n @click=\"handleSubmenuClick(subOption)\"\n >\n <div\n v-if=\"subOption.color\"\n class=\"size-5 rounded-full border border-border-default\"\n :style=\"{ backgroundColor: subOption.color }\"\n />\n <template v-else-if=\"!subOption.color\">\n <i\n v-if=\"isShapeSelected(subOption)\"\n class=\"icon-[lucide--check] size-4 flex-shrink-0\"\n />\n <div v-else class=\"w-4 flex-shrink-0\" />\n <span>{{ subOption.label }}</span>\n </template>\n </div>\n </div>\n </Popover>\n</template>\n\n<script setup lang=\"ts\">\nimport { cn } from '@comfyorg/tailwind-utils'\nimport Popover from 'primevue/popover'\nimport { computed, ref } from 'vue'\n\nimport type {\n MenuOption,\n SubMenuOption\n} from '@/composables/graph/useMoreOptionsMenu'\nimport { useNodeCustomization } from '@/composables/graph/useNodeCustomization'\n\ninterface Props {\n option: MenuOption\n}\n\ninterface Emits {\n (e: 'submenu-click', subOption: SubMenuOption): void\n}\n\nconst props = defineProps<Props>()\nconst emit = defineEmits<Emits>()\n\nconst { getCurrentShape } = useNodeCustomization()\n\nconst popoverRef = ref<InstanceType<typeof Popover>>()\n\nconst toggle = (event: Event, target?: HTMLElement) => {\n popoverRef.value?.toggle(event, target)\n}\ndefineExpose({\n toggle\n})\n\nconst handleSubmenuClick = (subOption: SubMenuOption) => {\n if (subOption.disabled) {\n return\n }\n emit('submenu-click', subOption)\n popoverRef.value?.hide()\n}\n\nconst isShapeSelected = (subOption: SubMenuOption): boolean => {\n if (subOption.color) return false\n\n const currentShape = getCurrentShape()\n if (!currentShape) return false\n\n return currentShape.localizedName === subOption.label\n}\n\nconst isColorSubmenu = computed(() => {\n return (\n props.option.submenu &&\n props.option.submenu.length > 0 &&\n props.option.submenu.every((item) => item.color && !item.icon)\n )\n})\n</script>\n","<template>\n <ContextMenu\n ref=\"contextMenu\"\n :model=\"menuItems\"\n class=\"max-h-[80vh] md:max-h-none overflow-y-auto md:overflow-y-visible\"\n @show=\"onMenuShow\"\n @hide=\"onMenuHide\"\n >\n <template #item=\"{ item, props, hasSubmenu }\">\n <a\n v-bind=\"props.action\"\n class=\"flex items-center gap-2 px-3 py-1.5\"\n @click=\"item.isColorSubmenu ? showColorPopover($event) : undefined\"\n >\n <i v-if=\"item.icon\" :class=\"[item.icon, 'size-4']\" />\n <span class=\"flex-1\">{{ item.label }}</span>\n <span\n v-if=\"item.shortcut\"\n class=\"flex h-3.5 min-w-3.5 items-center justify-center rounded bg-interface-menu-keybind-surface-default px-1 py-0 text-xs\"\n >\n {{ item.shortcut }}\n </span>\n <i\n v-if=\"hasSubmenu || item.isColorSubmenu\"\n class=\"icon-[lucide--chevron-right] size-4 opacity-60\"\n />\n </a>\n </template>\n </ContextMenu>\n\n <!-- Color picker menu (custom with color circles) -->\n <ColorPickerMenu\n v-if=\"colorOption\"\n ref=\"colorPickerMenu\"\n key=\"color-picker-menu\"\n :option=\"colorOption\"\n @submenu-click=\"handleColorSelect\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { useElementBounding, useEventListener, useRafFn } from '@vueuse/core'\nimport ContextMenu from 'primevue/contextmenu'\nimport type { MenuItem } from 'primevue/menuitem'\nimport { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'\n\nimport {\n registerNodeOptionsInstance,\n useMoreOptionsMenu\n} from '@/composables/graph/useMoreOptionsMenu'\nimport type {\n MenuOption,\n SubMenuOption\n} from '@/composables/graph/useMoreOptionsMenu'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\nimport ColorPickerMenu from './selectionToolbox/ColorPickerMenu.vue'\n\ninterface ExtendedMenuItem extends MenuItem {\n isColorSubmenu?: boolean\n shortcut?: string\n originalOption?: MenuOption\n}\n\nconst contextMenu = ref<InstanceType<typeof ContextMenu>>()\nconst colorPickerMenu = ref<InstanceType<typeof ColorPickerMenu>>()\nconst isOpen = ref(false)\n\nconst { menuOptions, bump } = useMoreOptionsMenu()\nconst canvasStore = useCanvasStore()\n\n// World position (canvas coordinates) where menu was opened\nconst worldPosition = ref({ x: 0, y: 0 })\n\n// Get canvas bounding rect reactively\nconst lgCanvas = canvasStore.getCanvas()\nconst { left: canvasLeft, top: canvasTop } = useElementBounding(lgCanvas.canvas)\n\n// Track last canvas transform to detect actual changes\nlet lastScale = 0\nlet lastOffsetX = 0\nlet lastOffsetY = 0\n\n// Update menu position based on canvas transform\nconst updateMenuPosition = () => {\n if (!isOpen.value) return\n\n const menuInstance = contextMenu.value as unknown as {\n container?: HTMLElement\n }\n const menuEl = menuInstance?.container\n if (!menuEl) return\n\n const { scale, offset } = lgCanvas.ds\n\n // Only update if canvas transform actually changed\n if (\n scale === lastScale &&\n offset[0] === lastOffsetX &&\n offset[1] === lastOffsetY\n ) {\n return\n }\n\n lastScale = scale\n lastOffsetX = offset[0]\n lastOffsetY = offset[1]\n\n // Convert world position to screen position\n const screenX = (worldPosition.value.x + offset[0]) * scale + canvasLeft.value\n const screenY = (worldPosition.value.y + offset[1]) * scale + canvasTop.value\n\n // Update menu position\n menuEl.style.left = `${screenX}px`\n menuEl.style.top = `${screenY}px`\n}\n\n// Sync with canvas transform using requestAnimationFrame\nconst { resume: startSync, pause: stopSync } = useRafFn(updateMenuPosition, {\n immediate: false\n})\n\n// Start/stop syncing based on menu visibility\nwatchEffect(() => {\n if (isOpen.value) {\n startSync()\n } else {\n stopSync()\n }\n})\n\n// Close on touch outside to handle mobile devices where click might be swallowed\nuseEventListener(\n window,\n 'touchstart',\n (event: TouchEvent) => {\n if (!isOpen.value || !contextMenu.value) return\n\n const target = event.target as Node\n const contextMenuInstance = contextMenu.value as unknown as {\n container?: HTMLElement\n $el?: HTMLElement\n }\n const menuEl = contextMenuInstance.container || contextMenuInstance.$el\n\n if (menuEl && !menuEl.contains(target)) {\n hide()\n }\n },\n { passive: true }\n)\n\n// Find color picker option\nconst colorOption = computed(() =>\n menuOptions.value.find((opt) => opt.isColorPicker)\n)\n\n// Check if option is the color picker\nfunction isColorOption(option: MenuOption): boolean {\n return Boolean(option.isColorPicker)\n}\n\n// Convert MenuOption to PrimeVue MenuItem\nfunction convertToMenuItem(option: MenuOption): ExtendedMenuItem {\n if (option.type === 'divider') return { separator: true }\n\n const isColor = isColorOption(option)\n\n const item: ExtendedMenuItem = {\n label: option.label,\n icon: option.icon,\n disabled: option.disabled,\n shortcut: option.shortcut,\n isColorSubmenu: isColor,\n originalOption: option\n }\n\n // Native submenus for non-color options\n if (option.hasSubmenu && option.submenu && !isColor) {\n item.items = option.submenu.map((sub) => ({\n label: sub.label,\n icon: sub.icon,\n disabled: sub.disabled,\n command: () => {\n sub.action()\n hide()\n }\n }))\n }\n\n // Regular action items\n if (!option.hasSubmenu && option.action) {\n item.command = () => {\n option.action?.()\n hide()\n }\n }\n\n return item\n}\n\n// Build menu items\nconst menuItems = computed<ExtendedMenuItem[]>(() =>\n menuOptions.value.map(convertToMenuItem)\n)\n\n// Show context menu\nfunction show(event: MouseEvent) {\n bump()\n\n // Convert screen position to world coordinates\n // Screen position relative to canvas = event position - canvas offset\n const screenX = event.clientX - canvasLeft.value\n const screenY = event.clientY - canvasTop.value\n\n // Convert to world coordinates using canvas transform\n const { scale, offset } = lgCanvas.ds\n worldPosition.value = {\n x: screenX / scale - offset[0],\n y: screenY / scale - offset[1]\n }\n\n // Initialize last* values to current transform to prevent updateMenuPosition\n // from overwriting PrimeVue's flip-adjusted position on the first RAF tick\n lastScale = scale\n lastOffsetX = offset[0]\n lastOffsetY = offset[1]\n\n isOpen.value = true\n contextMenu.value?.show(event)\n}\n\n// Hide context menu\nfunction hide() {\n contextMenu.value?.hide()\n}\n\nfunction toggle(event: Event) {\n if (isOpen.value) {\n hide()\n } else {\n show(event as MouseEvent)\n }\n}\n\ndefineExpose({ toggle, hide, isOpen, show })\n\nfunction showColorPopover(event: MouseEvent) {\n event.stopPropagation()\n event.preventDefault()\n const target = Array.from((event.currentTarget as HTMLElement).children).find(\n (el) => el.classList.contains('icon-[lucide--chevron-right]')\n ) as HTMLElement\n colorPickerMenu.value?.toggle(event, target)\n}\n\n// Handle color selection\nfunction handleColorSelect(subOption: SubMenuOption) {\n subOption.action()\n hide()\n}\n\nfunction onMenuShow() {\n isOpen.value = true\n}\n\nfunction onMenuHide() {\n isOpen.value = false\n}\n\nonMounted(() => {\n registerNodeOptionsInstance({ toggle, show, hide, isOpen })\n})\n\nonUnmounted(() => {\n registerNodeOptionsInstance(null)\n})\n</script>\n","<template>\n <Button\n v-tooltip.top=\"{\n value: $t('g.frameNodes'),\n showDelay: 1000\n }\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('g.frameNodes')\"\n @click=\"frameNodes\"\n >\n <i class=\"icon-[lucide--frame]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFrameNodes } from '@/composables/graph/useFrameNodes'\n\nconst { frameNodes } = useFrameNodes()\n</script>\n","<template>\n <Button\n v-tooltip.top=\"{\n value: $t('g.moreOptions'),\n showDelay: 1000\n }\"\n data-testid=\"more-options-button\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('g.moreOptions')\"\n @click=\"handleClick\"\n >\n <i class=\"icon-[lucide--more-vertical]\" />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'\n\nconst handleClick = (event: Event) => {\n toggleNodeOptions(event)\n}\n</script>\n","<template>\n <div class=\"h-6 w-px self-center bg-border-default\" />\n</template>\n","<template>\n <div\n ref=\"toolboxRef\"\n style=\"transform: translate(var(--tb-x), var(--tb-y))\"\n class=\"pointer-events-none fixed top-0 left-0 z-40\"\n >\n <Transition name=\"slide-up\">\n <Panel\n v-if=\"visible\"\n class=\"selection-toolbox pointer-events-auto rounded-lg border border-interface-stroke bg-interface-panel-surface\"\n :pt=\"{\n header: 'hidden',\n content: 'p-2 h-12 flex flex-row gap-1'\n }\"\n @wheel=\"canvasInteractions.forwardEventToCanvas\"\n >\n <DeleteButton v-if=\"showDelete\" />\n <VerticalDivider v-if=\"showInfoButton && showAnyPrimaryActions\" />\n <InfoButton v-if=\"showInfoButton\" />\n\n <ColorPickerButton v-if=\"showColorPicker\" />\n <FrameNodes v-if=\"showFrameNodes\" />\n <ConvertToSubgraphButton v-if=\"showConvertToSubgraph\" />\n <ConfigureSubgraph v-if=\"showSubgraphButtons\" />\n <PublishSubgraphButton v-if=\"showSubgraphButtons\" />\n <MaskEditorButton v-if=\"showMaskEditor\" />\n <VerticalDivider\n v-if=\"showAnyPrimaryActions && showAnyControlActions\"\n />\n\n <BypassButton v-if=\"showBypass\" />\n <RefreshSelectionButton v-if=\"showRefresh\" />\n <Load3DViewerButton v-if=\"showLoad3DViewer\" />\n\n <ExtensionCommandButton\n v-for=\"command in extensionToolboxCommands\"\n :key=\"command.id\"\n :command=\"command\"\n />\n <ExecuteButton v-if=\"showExecute\" />\n <NodeOptionsButton />\n </Panel>\n </Transition>\n </div>\n <NodeContextMenu />\n</template>\n\n<script setup lang=\"ts\">\nimport Panel from 'primevue/panel'\nimport { computed, ref } from 'vue'\n\nimport BypassButton from '@/components/graph/selectionToolbox/BypassButton.vue'\nimport ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue'\nimport ConfigureSubgraph from '@/components/graph/selectionToolbox/ConfigureSubgraph.vue'\nimport ConvertToSubgraphButton from '@/components/graph/selectionToolbox/ConvertToSubgraphButton.vue'\nimport DeleteButton from '@/components/graph/selectionToolbox/DeleteButton.vue'\nimport ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue'\nimport ExtensionCommandButton from '@/components/graph/selectionToolbox/ExtensionCommandButton.vue'\nimport InfoButton from '@/components/graph/selectionToolbox/InfoButton.vue'\nimport Load3DViewerButton from '@/components/graph/selectionToolbox/Load3DViewerButton.vue'\nimport MaskEditorButton from '@/components/graph/selectionToolbox/MaskEditorButton.vue'\nimport RefreshSelectionButton from '@/components/graph/selectionToolbox/RefreshSelectionButton.vue'\nimport PublishSubgraphButton from '@/components/graph/selectionToolbox/SaveToSubgraphLibrary.vue'\nimport { useSelectionToolboxPosition } from '@/composables/canvas/useSelectionToolboxPosition'\nimport { useSelectionState } from '@/composables/graph/useSelectionState'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'\nimport { useExtensionService } from '@/services/extensionService'\nimport { useCommandStore } from '@/stores/commandStore'\nimport type { ComfyCommandImpl } from '@/stores/commandStore'\n\nimport NodeContextMenu from './NodeContextMenu.vue'\nimport FrameNodes from './selectionToolbox/FrameNodes.vue'\nimport NodeOptionsButton from './selectionToolbox/NodeOptionsButton.vue'\nimport VerticalDivider from './selectionToolbox/VerticalDivider.vue'\n\nconst commandStore = useCommandStore()\nconst canvasStore = useCanvasStore()\nconst extensionService = useExtensionService()\nconst canvasInteractions = useCanvasInteractions()\n\nconst toolboxRef = ref<HTMLElement | undefined>()\nconst { visible } = useSelectionToolboxPosition(toolboxRef)\n\nconst extensionToolboxCommands = computed<ComfyCommandImpl[]>(() => {\n const commandIds = new Set<string>(\n canvasStore.selectedItems\n .map(\n (item) =>\n extensionService\n .invokeExtensions('getSelectionToolboxCommands', item)\n .flat() as string[]\n )\n .flat()\n )\n return Array.from(commandIds)\n .map((commandId) => commandStore.getCommand(commandId))\n .filter((command): command is ComfyCommandImpl => command !== undefined)\n})\n\nconst {\n hasAnySelection,\n hasMultipleSelection,\n isSingleNode,\n isSingleSubgraph,\n isSingleImageNode,\n hasAny3DNodeSelected,\n hasOutputNodesSelected,\n nodeDef\n} = useSelectionState()\nconst showInfoButton = computed(() => !!nodeDef.value)\n\nconst showColorPicker = computed(() => hasAnySelection.value)\nconst showConvertToSubgraph = computed(() => hasAnySelection.value)\nconst showFrameNodes = computed(() => hasMultipleSelection.value)\nconst showSubgraphButtons = computed(() => isSingleSubgraph.value)\n\nconst showBypass = computed(\n () =>\n isSingleNode.value || isSingleSubgraph.value || hasMultipleSelection.value\n)\nconst showLoad3DViewer = computed(() => hasAny3DNodeSelected.value)\nconst showMaskEditor = computed(() => isSingleImageNode.value)\n\nconst showDelete = computed(() => hasAnySelection.value)\nconst showRefresh = computed(() => hasAnySelection.value)\nconst showExecute = computed(() => hasOutputNodesSelected.value)\n\nconst showAnyPrimaryActions = computed(\n () =>\n showColorPicker.value ||\n showConvertToSubgraph.value ||\n showFrameNodes.value ||\n showSubgraphButtons.value\n)\n\nconst showAnyControlActions = computed(() => showBypass.value)\n</script>\n\n<style scoped>\n.selection-toolbox {\n transform: translateX(-50%) translateY(-120%);\n}\n\n@keyframes slideUp {\n 0% {\n transform: translateX(-50%) translateY(-100%);\n opacity: 0;\n }\n 50% {\n transform: translateX(-50%) translateY(-125%);\n opacity: 0.5;\n }\n 100% {\n transform: translateX(-50%) translateY(-120%);\n opacity: 1;\n }\n}\n\n.slide-up-enter-active {\n animation: slideUp 125ms ease-out;\n}\n\n.slide-up-leave-active {\n animation: slideUp 25ms ease-out reverse;\n}\n</style>\n","<template>\n <div\n v-if=\"showInput\"\n class=\"group-title-editor node-title-editor\"\n :style=\"inputStyle\"\n >\n <EditableText\n :is-editing=\"showInput\"\n :model-value=\"editedTitle\"\n @edit=\"onEdit\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport { computed, ref, watch } from 'vue'\nimport type { CSSProperties } from 'vue'\n\nimport EditableText from '@/components/common/EditableText.vue'\nimport { useAbsolutePosition } from '@/composables/element/useAbsolutePosition'\nimport {\n LGraphGroup,\n LGraphNode,\n LiteGraph\n} from '@/lib/litegraph/src/litegraph'\nimport type { LiteGraphCanvasEvent } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport {\n useCanvasStore,\n useTitleEditorStore\n} from '@/renderer/core/canvas/canvasStore'\nimport { app } from '@/scripts/app'\n\nconst settingStore = useSettingStore()\n\nconst showInput = ref(false)\nconst editedTitle = ref('')\nconst { style: inputPositionStyle, updatePosition } = useAbsolutePosition()\nconst inputFontStyle = ref<CSSProperties>({})\nconst inputStyle = computed<CSSProperties>(() => ({\n ...inputPositionStyle.value,\n ...inputFontStyle.value\n}))\n\nconst titleEditorStore = useTitleEditorStore()\nconst canvasStore = useCanvasStore()\nconst previousCanvasDraggable = ref(true)\n\nconst onEdit = (newValue: string) => {\n if (titleEditorStore.titleEditorTarget && newValue?.trim()) {\n const trimmedTitle = newValue.trim()\n titleEditorStore.titleEditorTarget.title = trimmedTitle\n\n // If this is a subgraph node, sync the runtime subgraph name for breadcrumb reactivity\n const target = titleEditorStore.titleEditorTarget\n if (target instanceof LGraphNode && target.isSubgraphNode?.()) {\n target.subgraph.name = trimmedTitle\n }\n\n app.canvas.setDirty(true, true)\n }\n showInput.value = false\n titleEditorStore.titleEditorTarget = null\n canvasStore.canvas!.allow_dragcanvas = previousCanvasDraggable.value\n}\n\nwatch(\n () => titleEditorStore.titleEditorTarget,\n (target) => {\n if (target === null) {\n return\n }\n editedTitle.value = target.title\n showInput.value = true\n const canvas = canvasStore.canvas!\n previousCanvasDraggable.value = canvas.allow_dragcanvas\n canvas.allow_dragcanvas = false\n const scale = canvas.ds.scale\n\n if (target instanceof LGraphGroup) {\n const group = target\n updatePosition({\n pos: group.pos,\n size: [group.size[0], group.titleHeight]\n })\n inputFontStyle.value = { fontSize: `${group.font_size * scale}px` }\n } else if (target instanceof LGraphNode) {\n const node = target\n const [x, y] = node.getBounding()\n updatePosition({\n pos: [x, y],\n size: [node.width, LiteGraph.NODE_TITLE_HEIGHT]\n })\n inputFontStyle.value = { fontSize: `${12 * scale}px` }\n }\n }\n)\n\nconst canvasEventHandler = (event: LiteGraphCanvasEvent) => {\n if (event.detail.subType === 'group-double-click') {\n if (!settingStore.get('Comfy.Group.DoubleClickTitleToEdit')) {\n return\n }\n\n const group: LGraphGroup = event.detail.group\n const [_, y] = group.pos\n\n const e = event.detail.originalEvent\n const relativeY = e.canvasY - y\n // Only allow editing if the click is on the title bar\n if (relativeY <= group.titleHeight) {\n titleEditorStore.titleEditorTarget = group\n }\n } else if (event.detail.subType === 'node-double-click') {\n if (!settingStore.get('Comfy.Node.DoubleClickTitleToEdit')) {\n return\n }\n\n const node: LGraphNode = event.detail.node\n const [_, y] = node.pos\n\n const e = event.detail.originalEvent\n const relativeY = e.canvasY - y\n // Only allow editing if the click is on the title bar\n if (relativeY <= 0) {\n titleEditorStore.titleEditorTarget = node\n }\n }\n}\n\nuseEventListener(document, 'litegraph:canvas', canvasEventHandler)\n</script>\n\n<style scoped>\n.group-title-editor.node-title-editor {\n z-index: 9999;\n padding: 0.25rem;\n}\n\n:deep(.editable-text) {\n width: 100%;\n height: 100%;\n}\n\n:deep(.editable-text input) {\n width: 100%;\n height: 100%;\n /* Override the default font size */\n font-size: inherit;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { whenever } from '@vueuse/core'\nimport { computed } from 'vue'\n\nimport NodeHelpContent from '@/components/node/NodeHelpContent.vue'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useNodeDefStore } from '@/stores/nodeDefStore'\nimport { useNodeHelpStore } from '@/stores/workspace/nodeHelpStore'\n\nconst { nodes } = defineProps<{\n nodes: LGraphNode[]\n}>()\nconst node = computed(() => nodes[0])\n\nconst nodeDefStore = useNodeDefStore()\nconst nodeHelpStore = useNodeHelpStore()\n\nconst nodeInfo = computed(() => {\n return nodeDefStore.fromLGraphNode(node.value)\n})\n\n// Open node help when the selected node changes\nwhenever(\n nodeInfo,\n (info) => {\n nodeHelpStore.openHelp(info)\n },\n { immediate: true }\n)\n</script>\n\n<template>\n <div v-if=\"nodeInfo\" class=\"p-3\">\n <NodeHelpContent :node=\"nodeInfo\" />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { refDebounced } from '@vueuse/core'\nimport { ref, toRef, toValue, watch } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { searcher = async () => {}, updateKey } = defineProps<{\n searcher?: (\n query: string,\n onCleanup: (cleanupFn: () => void) => void\n ) => Promise<void>\n updateKey?: MaybeRefOrGetter<unknown>\n}>()\n\nconst searchQuery = defineModel<string>({ default: '' })\n\nconst isQuerying = ref(false)\nconst debouncedSearchQuery = refDebounced(searchQuery, 100, {\n maxWait: 100\n})\nwatch(searchQuery, (value) => {\n isQuerying.value = value !== debouncedSearchQuery.value\n})\nconst updateKeyRef = toRef(() => toValue(updateKey))\n\nwatch(\n [debouncedSearchQuery, updateKeyRef],\n (_, __, onCleanup) => {\n let isCleanup = false\n let cleanupFn: undefined | (() => void)\n onCleanup(() => {\n isCleanup = true\n cleanupFn?.()\n })\n\n void searcher(debouncedSearchQuery.value, (cb) => (cleanupFn = cb))\n .catch((error) => {\n console.error('[SidePanelSearch] searcher failed', error)\n })\n .finally(() => {\n if (!isCleanup) isQuerying.value = false\n })\n },\n { immediate: true }\n)\n</script>\n\n<template>\n <label\n :class=\"\n cn(\n 'mt-1 py-1.5 bg-secondary-background rounded-lg transition-all duration-150',\n 'flex-1 flex gap-2 px-2 items-center',\n 'text-base-foreground border-0',\n 'focus-within:ring focus-within:ring-component-node-widget-background-highlighted/80'\n )\n \"\n >\n <i\n :class=\"\n cn(\n 'size-4 text-muted-foreground',\n isQuerying\n ? 'icon-[lucide--loader-circle] animate-spin'\n : 'icon-[lucide--search]'\n )\n \"\n />\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n class=\"bg-transparent border-0 outline-0 ring-0 h-5\"\n :placeholder=\"$t('g.searchPlaceholder')\"\n />\n </label>\n</template>\n","/**\n * Simplified widget interface for Vue-based node rendering\n * Removes all DOM manipulation and positioning concerns\n */\nimport type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'\n\n/** Valid types for widget values */\nexport type WidgetValue =\n | string\n | number\n | boolean\n | object\n | undefined\n | null\n | void\n | File[]\n\nconst CONTROL_OPTIONS = [\n 'fixed',\n 'increment',\n 'decrement',\n 'randomize'\n] as const\nexport type ControlOptions = (typeof CONTROL_OPTIONS)[number]\n\nfunction isControlOption(val: WidgetValue): val is ControlOptions {\n return CONTROL_OPTIONS.includes(val as ControlOptions)\n}\n\nexport function normalizeControlOption(val: WidgetValue): ControlOptions {\n if (isControlOption(val)) return val\n return 'randomize'\n}\n\nexport type SafeControlWidget = {\n value: ControlOptions\n update: (value: WidgetValue) => void\n}\n\nexport interface SimplifiedWidget<\n T extends WidgetValue = WidgetValue,\n O = Record<string, any>\n> {\n /** Display name of the widget */\n name: string\n\n /** Widget type identifier (e.g., 'STRING', 'INT', 'COMBO') */\n type: string\n\n /** Current value of the widget */\n value: T\n\n borderStyle?: string\n\n /** Callback fired when value changes */\n callback?: (value: T) => void\n\n /** Optional method to compute widget size requirements */\n computeSize?: () => { minHeight: number; maxHeight?: number }\n\n /** Localized display label (falls back to name if not provided) */\n label?: string\n\n /** Widget options including filtered PrimeVue props */\n options?: O\n\n /** Override for use with subgraph promoted asset widgets*/\n nodeType?: string\n\n /** Optional serialization method for custom value handling */\n serializeValue?: () => any\n\n /** Optional input specification backing this widget */\n spec?: InputSpecV2\n\n controlWidget?: SafeControlWidget\n}\n\nexport interface SimplifiedControlWidget<\n T extends WidgetValue = WidgetValue,\n O = Record<string, any>\n> extends SimplifiedWidget<T, O> {\n controlWidget: SafeControlWidget\n}\n","/**\n * Vue node lifecycle management for LiteGraph integration\n * Provides event-driven reactivity with performance optimizations\n */\nimport { reactiveComputed } from '@vueuse/core'\nimport { customRef, reactive, shallowReactive } from 'vue'\n\nimport { useChainCallback } from '@/composables/functional/useChainCallback'\nimport { isProxyWidget } from '@/core/graph/subgraph/proxyWidget'\nimport type {\n INodeInputSlot,\n INodeOutputSlot\n} from '@/lib/litegraph/src/interfaces'\nimport type {\n IBaseWidget,\n IWidgetOptions\n} from '@/lib/litegraph/src/types/widgets'\nimport { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'\nimport { LayoutSource } from '@/renderer/core/layout/types'\nimport type { NodeId } from '@/renderer/core/layout/types'\nimport type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'\nimport { isDOMWidget } from '@/scripts/domWidget'\nimport { useNodeDefStore } from '@/stores/nodeDefStore'\nimport type { WidgetValue, SafeControlWidget } from '@/types/simplifiedWidget'\nimport { normalizeControlOption } from '@/types/simplifiedWidget'\n\nimport type {\n LGraph,\n LGraphBadge,\n LGraphNode,\n LGraphTriggerAction,\n LGraphTriggerEvent,\n LGraphTriggerParam\n} from '@/lib/litegraph/src/litegraph'\nimport type { TitleMode } from '@/lib/litegraph/src/types/globalEnums'\nimport { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'\nimport { app } from '@/scripts/app'\n\nexport interface WidgetSlotMetadata {\n index: number\n linked: boolean\n}\n\nexport interface SafeWidgetData {\n name: string\n type: string\n value: WidgetValue\n borderStyle?: string\n callback?: ((value: unknown) => void) | undefined\n controlWidget?: SafeControlWidget\n hasLayoutSize?: boolean\n isDOMWidget?: boolean\n label?: string\n nodeType?: string\n options?: IWidgetOptions<unknown>\n spec?: InputSpec\n slotMetadata?: WidgetSlotMetadata\n}\n\nexport interface VueNodeData {\n executing: boolean\n id: NodeId\n mode: number\n selected: boolean\n title: string\n type: string\n apiNode?: boolean\n badges?: (LGraphBadge | (() => LGraphBadge))[]\n bgcolor?: string\n color?: string\n flags?: {\n collapsed?: boolean\n pinned?: boolean\n }\n hasErrors?: boolean\n inputs?: INodeInputSlot[]\n outputs?: INodeOutputSlot[]\n resizable?: boolean\n shape?: number\n subgraphId?: string | null\n titleMode?: TitleMode\n widgets?: SafeWidgetData[]\n}\n\nexport interface GraphNodeManager {\n // Reactive state - safe data extracted from LiteGraph nodes\n vueNodeData: ReadonlyMap<string, VueNodeData>\n\n // Access to original LiteGraph nodes (non-reactive)\n getNode(id: string): LGraphNode | undefined\n\n // Lifecycle methods\n cleanup(): void\n}\n\nfunction widgetWithVueTrack(\n widget: IBaseWidget\n): asserts widget is IBaseWidget & { vueTrack: () => void } {\n if (widget.vueTrack) return\n\n customRef((track, trigger) => {\n widget.callback = useChainCallback(widget.callback, trigger)\n widget.vueTrack = track\n return { get() {}, set() {} }\n })\n}\nexport function useReactiveWidgetValue(widget: IBaseWidget) {\n widgetWithVueTrack(widget)\n widget.vueTrack()\n return widget.value\n}\n\nfunction getControlWidget(widget: IBaseWidget): SafeControlWidget | undefined {\n const cagWidget = widget.linkedWidgets?.find(\n (w) => w.name == 'control_after_generate'\n )\n if (!cagWidget) return\n return {\n value: normalizeControlOption(cagWidget.value),\n update: (value) => (cagWidget.value = normalizeControlOption(value))\n }\n}\nfunction getNodeType(node: LGraphNode, widget: IBaseWidget) {\n if (!node.isSubgraphNode() || !isProxyWidget(widget)) return undefined\n const subNode = node.subgraph.getNodeById(widget._overlay.nodeId)\n return subNode?.type\n}\n\n/**\n * Validates that a value is a valid WidgetValue type\n */\nconst normalizeWidgetValue = (value: unknown): WidgetValue => {\n if (value === null || value === undefined || value === void 0) {\n return undefined\n }\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n return value\n }\n if (typeof value === 'object') {\n // Check if it's a File array\n if (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((item): item is File => item instanceof File)\n ) {\n return value\n }\n // Otherwise it's a generic object\n return value\n }\n // If none of the above, return undefined\n console.warn(`Invalid widget value type: ${typeof value}`, value)\n return undefined\n}\n\nexport function safeWidgetMapper(\n node: LGraphNode,\n slotMetadata: Map<string, WidgetSlotMetadata>\n): (widget: IBaseWidget) => SafeWidgetData {\n const nodeDefStore = useNodeDefStore()\n return function (widget) {\n try {\n const spec = nodeDefStore.getInputSpecForWidget(node, widget.name)\n const slotInfo = slotMetadata.get(widget.name)\n const borderStyle = widget.promoted\n ? 'ring ring-component-node-widget-promoted'\n : widget.advanced\n ? 'ring ring-component-node-widget-advanced'\n : undefined\n const callback = (v: unknown) => {\n const value = normalizeWidgetValue(v)\n widget.value = value ?? undefined\n // Match litegraph callback signature: (value, canvas, node, pos, event)\n // Some extensions (e.g., Impact Pack) expect node as the 3rd parameter\n widget.callback?.(value, app.canvas, node)\n // Trigger redraw for all legacy widgets on this node (e.g., mask preview)\n // This ensures widgets that depend on other widget values get updated\n node.widgets?.forEach((w) => w.triggerDraw?.())\n }\n\n return {\n name: widget.name,\n type: widget.type,\n value: useReactiveWidgetValue(widget),\n borderStyle,\n callback,\n controlWidget: getControlWidget(widget),\n hasLayoutSize: typeof widget.computeLayoutSize === 'function',\n isDOMWidget: isDOMWidget(widget),\n label: widget.label,\n nodeType: getNodeType(node, widget),\n options: widget.options,\n spec,\n slotMetadata: slotInfo\n }\n } catch (error) {\n return {\n name: widget.name || 'unknown',\n type: widget.type || 'text',\n value: undefined\n }\n }\n }\n}\n\nexport function isValidWidgetValue(value: unknown): value is WidgetValue {\n return (\n value === null ||\n value === undefined ||\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n typeof value === 'object'\n )\n}\n\nexport function useGraphNodeManager(graph: LGraph): GraphNodeManager {\n // Get layout mutations composable\n const { createNode, deleteNode, setSource } = useLayoutMutations()\n // Safe reactive data extracted from LiteGraph nodes\n const vueNodeData = reactive(new Map<string, VueNodeData>())\n\n // Non-reactive storage for original LiteGraph nodes\n const nodeRefs = new Map<string, LGraphNode>()\n\n const refreshNodeSlots = (nodeId: string) => {\n const nodeRef = nodeRefs.get(nodeId)\n const currentData = vueNodeData.get(nodeId)\n\n if (!nodeRef || !currentData) return\n\n // Only extract slot-related data instead of full node re-extraction\n const slotMetadata = new Map<string, WidgetSlotMetadata>()\n\n nodeRef.inputs?.forEach((input, index) => {\n if (!input?.widget?.name) return\n slotMetadata.set(input.widget.name, {\n index,\n linked: input.link != null\n })\n })\n\n // Update only widgets with new slot metadata, keeping other widget data intact\n for (const widget of currentData.widgets ?? []) {\n const slotInfo = slotMetadata.get(widget.name)\n if (slotInfo) widget.slotMetadata = slotInfo\n }\n }\n\n // Extract safe data from LiteGraph node for Vue consumption\n function extractVueNodeData(node: LGraphNode): VueNodeData {\n // Determine subgraph ID - null for root graph, string for subgraphs\n const subgraphId =\n node.graph && 'id' in node.graph && node.graph !== node.graph.rootGraph\n ? String(node.graph.id)\n : null\n // Extract safe widget data\n const slotMetadata = new Map<string, WidgetSlotMetadata>()\n\n const reactiveWidgets = shallowReactive<IBaseWidget[]>(node.widgets ?? [])\n Object.defineProperty(node, 'widgets', {\n get() {\n return reactiveWidgets\n },\n set(v) {\n reactiveWidgets.splice(0, reactiveWidgets.length, ...v)\n }\n })\n const reactiveInputs = shallowReactive<INodeInputSlot[]>(node.inputs ?? [])\n Object.defineProperty(node, 'inputs', {\n get() {\n return reactiveInputs\n },\n set(v) {\n reactiveInputs.splice(0, reactiveInputs.length, ...v)\n }\n })\n\n const safeWidgets = reactiveComputed<SafeWidgetData[]>(() => {\n node.inputs?.forEach((input, index) => {\n if (!input?.widget?.name) return\n slotMetadata.set(input.widget.name, {\n index,\n linked: input.link != null\n })\n })\n return node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? []\n })\n\n const nodeType =\n node.type ||\n node.constructor?.comfyClass ||\n node.constructor?.title ||\n node.constructor?.name ||\n 'Unknown'\n\n const apiNode = node.constructor?.nodeData?.api_node ?? false\n const badges = node.badges\n\n return {\n id: String(node.id),\n title: typeof node.title === 'string' ? node.title : '',\n type: nodeType,\n mode: node.mode || 0,\n titleMode: node.title_mode,\n selected: node.selected || false,\n executing: false, // Will be updated separately based on execution state\n subgraphId,\n apiNode,\n badges,\n hasErrors: !!node.has_errors,\n widgets: safeWidgets,\n inputs: reactiveInputs,\n outputs: node.outputs ? [...node.outputs] : undefined,\n flags: node.flags ? { ...node.flags } : undefined,\n color: node.color || undefined,\n bgcolor: node.bgcolor || undefined,\n resizable: node.resizable,\n shape: node.shape\n }\n }\n\n // Get access to original LiteGraph node (non-reactive)\n const getNode = (id: string): LGraphNode | undefined => {\n return nodeRefs.get(id)\n }\n\n const syncWithGraph = () => {\n if (!graph?._nodes) return\n\n const currentNodes = new Set(graph._nodes.map((n) => String(n.id)))\n\n // Remove deleted nodes\n for (const id of Array.from(vueNodeData.keys())) {\n if (!currentNodes.has(id)) {\n nodeRefs.delete(id)\n vueNodeData.delete(id)\n }\n }\n\n // Add/update existing nodes\n graph._nodes.forEach((node) => {\n const id = String(node.id)\n\n // Store non-reactive reference\n nodeRefs.set(id, node)\n\n // Extract and store safe data for Vue\n vueNodeData.set(id, extractVueNodeData(node))\n })\n }\n\n /**\n * Handles node addition to the graph - sets up Vue state and spatial indexing\n * Defers position extraction until after potential configure() calls\n */\n const handleNodeAdded = (\n node: LGraphNode,\n originalCallback?: (node: LGraphNode) => void\n ) => {\n const id = String(node.id)\n\n // Store non-reactive reference to original node\n nodeRefs.set(id, node)\n\n // Extract initial data for Vue (may be incomplete during graph configure)\n vueNodeData.set(id, extractVueNodeData(node))\n\n const initializeVueNodeLayout = () => {\n // Extract actual positions after configure() has potentially updated them\n const nodePosition = { x: node.pos[0], y: node.pos[1] }\n const nodeSize = { width: node.size[0], height: node.size[1] }\n\n // Add node to layout store with final positions\n setSource(LayoutSource.Canvas)\n void createNode(id, {\n position: nodePosition,\n size: nodeSize,\n zIndex: node.order || 0,\n visible: true\n })\n }\n\n // Check if we're in the middle of configuring the graph (workflow loading)\n if (window.app?.configuringGraph) {\n // During workflow loading - defer layout initialization until configure completes\n // Chain our callback with any existing onAfterGraphConfigured callback\n node.onAfterGraphConfigured = useChainCallback(\n node.onAfterGraphConfigured,\n () => {\n // Re-extract data now that configure() has populated title/slots/widgets/etc.\n vueNodeData.set(id, extractVueNodeData(node))\n initializeVueNodeLayout()\n }\n )\n } else {\n // Not during workflow loading - initialize layout immediately\n // This handles individual node additions during normal operation\n initializeVueNodeLayout()\n }\n\n // Call original callback if provided\n if (originalCallback) {\n void originalCallback(node)\n }\n }\n\n /**\n * Handles node removal from the graph - cleans up all references\n */\n const handleNodeRemoved = (\n node: LGraphNode,\n originalCallback?: (node: LGraphNode) => void\n ) => {\n const id = String(node.id)\n\n // Remove node from layout store\n setSource(LayoutSource.Canvas)\n void deleteNode(id)\n\n // Clean up all tracking references\n nodeRefs.delete(id)\n vueNodeData.delete(id)\n\n // Call original callback if provided\n if (originalCallback) {\n originalCallback(node)\n }\n }\n\n /**\n * Creates cleanup function for event listeners and state\n */\n const createCleanupFunction = (\n originalOnNodeAdded: ((node: LGraphNode) => void) | undefined,\n originalOnNodeRemoved: ((node: LGraphNode) => void) | undefined,\n originalOnTrigger: ((event: LGraphTriggerEvent) => void) | undefined\n ) => {\n return () => {\n // Restore original callbacks\n graph.onNodeAdded = originalOnNodeAdded || undefined\n graph.onNodeRemoved = originalOnNodeRemoved || undefined\n graph.onTrigger = originalOnTrigger || undefined\n\n // Clear all state maps\n nodeRefs.clear()\n vueNodeData.clear()\n }\n }\n\n /**\n * Sets up event listeners - now simplified with extracted handlers\n */\n const setupEventListeners = (): (() => void) => {\n // Store original callbacks\n const originalOnNodeAdded = graph.onNodeAdded\n const originalOnNodeRemoved = graph.onNodeRemoved\n const originalOnTrigger = graph.onTrigger\n\n // Set up graph event handlers\n graph.onNodeAdded = (node: LGraphNode) => {\n handleNodeAdded(node, originalOnNodeAdded)\n }\n\n graph.onNodeRemoved = (node: LGraphNode) => {\n handleNodeRemoved(node, originalOnNodeRemoved)\n }\n\n const triggerHandlers: {\n [K in LGraphTriggerAction]: (event: LGraphTriggerParam<K>) => void\n } = {\n 'node:property:changed': (propertyEvent) => {\n const nodeId = String(propertyEvent.nodeId)\n const currentData = vueNodeData.get(nodeId)\n\n if (currentData) {\n switch (propertyEvent.property) {\n case 'title':\n vueNodeData.set(nodeId, {\n ...currentData,\n title: String(propertyEvent.newValue)\n })\n break\n case 'flags.collapsed':\n vueNodeData.set(nodeId, {\n ...currentData,\n flags: {\n ...currentData.flags,\n collapsed: Boolean(propertyEvent.newValue)\n }\n })\n break\n case 'flags.pinned':\n vueNodeData.set(nodeId, {\n ...currentData,\n flags: {\n ...currentData.flags,\n pinned: Boolean(propertyEvent.newValue)\n }\n })\n break\n case 'mode':\n vueNodeData.set(nodeId, {\n ...currentData,\n mode:\n typeof propertyEvent.newValue === 'number'\n ? propertyEvent.newValue\n : 0\n })\n break\n case 'color':\n vueNodeData.set(nodeId, {\n ...currentData,\n color:\n typeof propertyEvent.newValue === 'string'\n ? propertyEvent.newValue\n : undefined\n })\n break\n case 'bgcolor':\n vueNodeData.set(nodeId, {\n ...currentData,\n bgcolor:\n typeof propertyEvent.newValue === 'string'\n ? propertyEvent.newValue\n : undefined\n })\n break\n case 'shape':\n vueNodeData.set(nodeId, {\n ...currentData,\n shape:\n typeof propertyEvent.newValue === 'number'\n ? propertyEvent.newValue\n : undefined\n })\n }\n }\n },\n 'node:slot-errors:changed': (slotErrorsEvent) => {\n refreshNodeSlots(String(slotErrorsEvent.nodeId))\n },\n 'node:slot-links:changed': (slotLinksEvent) => {\n if (slotLinksEvent.slotType === NodeSlotType.INPUT) {\n refreshNodeSlots(String(slotLinksEvent.nodeId))\n }\n }\n }\n\n graph.onTrigger = (event: LGraphTriggerEvent) => {\n switch (event.type) {\n case 'node:property:changed':\n triggerHandlers['node:property:changed'](event)\n break\n case 'node:slot-errors:changed':\n triggerHandlers['node:slot-errors:changed'](event)\n break\n case 'node:slot-links:changed':\n triggerHandlers['node:slot-links:changed'](event)\n break\n }\n\n // Chain to original handler\n originalOnTrigger?.(event)\n }\n\n // Initialize state\n syncWithGraph()\n\n // Return cleanup function\n return createCleanupFunction(\n originalOnNodeAdded || undefined,\n originalOnNodeRemoved || undefined,\n originalOnTrigger || undefined\n )\n }\n\n // Set up event listeners immediately\n const cleanup = setupEventListeners()\n\n // Process any existing nodes after event listeners are set up\n if (graph._nodes && graph._nodes.length > 0) {\n graph._nodes.forEach((node: LGraphNode) => {\n if (graph.onNodeAdded) {\n graph.onNodeAdded(node)\n }\n })\n }\n\n return {\n vueNodeData,\n getNode,\n cleanup\n }\n}\n","<script lang=\"ts\" setup>\nimport { cn } from '@/utils/tailwindUtil'\n\ndefineProps<{\n isEmpty?: boolean\n}>()\n\nconst isCollapse = defineModel<boolean>('collapse', { default: false })\n</script>\n\n<template>\n <div class=\"flex flex-col bg-interface-panel-surface\">\n <div\n class=\"sticky top-0 z-10 flex items-center justify-between backdrop-blur-xl bg-inherit\"\n >\n <button\n v-tooltip=\"\n isEmpty\n ? {\n value: $t('rightSidePanel.inputsNoneTooltip'),\n showDelay: 1_000\n }\n : undefined\n \"\n type=\"button\"\n :class=\"\n cn(\n 'group min-h-12 bg-transparent border-0 outline-0 ring-0 w-full text-left flex items-center justify-between pl-4 pr-3',\n !isEmpty && 'cursor-pointer'\n )\n \"\n :disabled=\"isEmpty\"\n @click=\"isCollapse = !isCollapse\"\n >\n <span class=\"text-sm font-semibold line-clamp-2\">\n <slot name=\"label\" />\n </span>\n\n <i\n v-if=\"!isEmpty\"\n :class=\"\n cn(\n 'text-muted-foreground group-hover:text-base-foreground group-focus:text-base-foreground icon-[lucide--chevron-up] size-4 transition-all',\n isCollapse && '-rotate-180'\n )\n \"\n />\n </button>\n </div>\n <div v-if=\"!isCollapse && !isEmpty\" class=\"pb-4\">\n <slot />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, provide } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useReactiveWidgetValue } from '@/composables/graph/useGraphNodeManager'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport WidgetLegacy from '@/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue'\nimport {\n getComponent,\n shouldExpand\n} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'\nimport { cn } from '@/utils/tailwindUtil'\n\nimport PropertiesAccordionItem from '../layout/PropertiesAccordionItem.vue'\n\nconst { label, widgets } = defineProps<{\n label?: string\n widgets: { widget: IBaseWidget; node: LGraphNode }[]\n}>()\n\nprovide('hideLayoutField', true)\n\nconst canvasStore = useCanvasStore()\nconst { t } = useI18n()\n\nfunction getWidgetComponent(widget: IBaseWidget) {\n const component = getComponent(widget.type, widget.name)\n return component || WidgetLegacy\n}\n\nfunction onWidgetValueChange(\n widget: IBaseWidget,\n value: string | number | boolean | object\n) {\n widget.value = value\n widget.callback?.(value)\n canvasStore.canvas?.setDirty(true, true)\n}\n\nconst isEmpty = computed(() => widgets.length === 0)\n\nconst displayLabel = computed(\n () =>\n label ??\n (isEmpty.value\n ? t('rightSidePanel.inputsNone')\n : t('rightSidePanel.inputs'))\n)\n</script>\n\n<template>\n <PropertiesAccordionItem :is-empty>\n <template #label>\n <slot name=\"label\">\n {{ displayLabel }}\n </slot>\n </template>\n\n <div v-if=\"!isEmpty\" class=\"space-y-4 rounded-lg bg-interface-surface px-4\">\n <div\n v-for=\"({ widget, node }, index) in widgets\"\n :key=\"`widget-${index}-${widget.name}`\"\n class=\"widget-item gap-1.5 col-span-full grid grid-cols-subgrid\"\n >\n <div class=\"min-h-8\">\n <p v-if=\"widget.name\" class=\"text-sm leading-8 p-0 m-0 line-clamp-1\">\n {{ widget.label || widget.name }}\n </p>\n </div>\n <component\n :is=\"getWidgetComponent(widget)\"\n :widget=\"widget\"\n :model-value=\"useReactiveWidgetValue(widget)\"\n :node-id=\"String(node.id)\"\n :node-type=\"node.type\"\n :class=\"cn('col-span-1', shouldExpand(widget.type) && 'min-h-36')\"\n @update:model-value=\"\n (value: string | number | boolean | object) =>\n onWidgetValueChange(widget, value)\n \"\n />\n </div>\n </div>\n </PropertiesAccordionItem>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, shallowRef } from 'vue'\n\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'\n\nimport SidePanelSearch from '../layout/SidePanelSearch.vue'\nimport SectionWidgets from './SectionWidgets.vue'\n\nconst { nodes } = defineProps<{\n nodes: LGraphNode[]\n}>()\n\ntype NodeWidgetsList = Array<{ node: LGraphNode; widget: IBaseWidget }>\ntype NodeWidgetsListList = Array<{\n node: LGraphNode\n widgets: NodeWidgetsList\n}>\n\nconst widgetsSectionDataList = computed((): NodeWidgetsListList => {\n return nodes.map((node) => {\n const { widgets = [] } = node\n const shownWidgets = widgets\n .filter((w) => !(w.options?.canvasOnly || w.options?.hidden))\n .map((widget) => ({ node, widget }))\n return {\n widgets: shownWidgets,\n node\n }\n })\n})\n\nconst searchedWidgetsSectionDataList = shallowRef<NodeWidgetsListList>([])\n\n/**\n * Searches widgets in all selected nodes and returns search results.\n * Filters by name, localized label, type, and user-input value.\n * Performs basic tokenization of the query string.\n */\nasync function searcher(query: string) {\n if (query.trim() === '') {\n searchedWidgetsSectionDataList.value = widgetsSectionDataList.value\n return\n }\n const words = query.trim().toLowerCase().split(' ')\n searchedWidgetsSectionDataList.value = widgetsSectionDataList.value\n .map((item) => {\n return {\n ...item,\n widgets: item.widgets.filter(({ widget }) => {\n const label = widget.label?.toLowerCase()\n const name = widget.name.toLowerCase()\n const type = widget.type.toLowerCase()\n const value = widget.value?.toString().toLowerCase()\n return words.every(\n (word) =>\n name.includes(word) ||\n label?.includes(word) ||\n type?.includes(word) ||\n value?.includes(word)\n )\n })\n }\n })\n .filter((item) => item.widgets.length > 0)\n}\n</script>\n\n<template>\n <div class=\"px-4 pb-4 flex gap-2 border-b border-interface-stroke\">\n <SidePanelSearch :searcher :update-key=\"widgetsSectionDataList\" />\n </div>\n <SectionWidgets\n v-for=\"section in searchedWidgetsSectionDataList\"\n :key=\"section.node.id\"\n :label=\"widgetsSectionDataList.length > 1 ? section.node.title : undefined\"\n :widgets=\"section.widgets\"\n :default-collapse=\"\n widgetsSectionDataList.length > 1 &&\n widgetsSectionDataList === searchedWidgetsSectionDataList\n \"\n class=\"border-b border-interface-stroke\"\n />\n</template>\n","<template>\n <div\n :class=\"\n cn(\n WidgetInputBaseClass,\n 'p-1 inline-flex justify-center items-center gap-1'\n )\n \"\n >\n <button\n v-for=\"(option, index) in options\"\n :key=\"getOptionValue(option, index)\"\n :class=\"\n cn(\n 'flex-1 h-6 px-5 py-[5px] rounded flex justify-center items-center gap-1 transition-all duration-150 ease-in-out truncate min-w-[4ch]',\n 'bg-transparent border-none',\n 'text-center text-xs font-normal',\n {\n 'bg-interface-menu-component-surface-selected':\n isSelected(index) && !disabled,\n 'hover:bg-interface-menu-component-surface-selected/50':\n !isSelected(index) && !disabled,\n 'opacity-50 cursor-not-allowed': disabled,\n 'cursor-pointer': !disabled\n },\n isSelected(index) && !disabled\n ? 'text-text-primary'\n : 'text-text-secondary'\n )\n \"\n :disabled=\"disabled\"\n @click=\"handleSelect(index)\"\n >\n {{ getOptionLabel(option) }}\n </button>\n </div>\n</template>\n\n<script\n setup\n lang=\"ts\"\n generic=\"\n T extends string | number | { label: string; value: string | number }\n \"\n>\nimport { cn } from '@/utils/tailwindUtil'\n\nimport { WidgetInputBaseClass } from '../layout'\n\ntype ModelValue = T extends object ? T['value'] : T\n\ninterface Props {\n modelValue: ModelValue | null | undefined\n options: T[]\n optionLabel?: string // PrimeVue compatible prop\n optionValue?: string // PrimeVue compatible prop\n disabled?: boolean\n}\n\ninterface Emits {\n 'update:modelValue': [value: ModelValue]\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n optionLabel: 'label',\n optionValue: 'value'\n})\n\nconst emit = defineEmits<Emits>()\n\n// handle both string/number arrays and object arrays with PrimeVue compatibility\nconst getOptionValue = (option: T, index: number): ModelValue => {\n if (typeof option !== 'object') {\n return option as ModelValue\n }\n\n const valueField = props.optionValue\n const value =\n (option as any)[valueField] ??\n option.value ??\n (option as any).name ??\n option.label ??\n index\n return value\n}\n\n// for display with PrimeVue compatibility\nconst getOptionLabel = (option: T): string => {\n if (typeof option === 'object' && option !== null) {\n const labelField = props.optionLabel\n return (\n (option as any)[labelField] ??\n option.label ??\n (option as any).name ??\n option.value ??\n String(option)\n )\n }\n return String(option)\n}\n\nconst isSelected = (index: number): boolean => {\n const optionValue = getOptionValue(props.options[index], index)\n return String(optionValue) === String(props.modelValue ?? '')\n}\n\nconst handleSelect = (index: number) => {\n if (props.disabled) return\n\n const optionValue = getOptionValue(props.options[index], index)\n emit('update:modelValue', optionValue)\n}\n</script>\n","<template>\n <div class=\"space-y-4 p-3 text-sm text-muted-foreground\">\n <!-- Node State -->\n <div class=\"flex flex-col gap-2\">\n <span>\n {{ t('rightSidePanel.nodeState') }}\n </span>\n <FormSelectButton\n v-model=\"nodeState\"\n class=\"w-full\"\n :options=\"[\n {\n label: t('rightSidePanel.normal'),\n value: LGraphEventMode.ALWAYS\n },\n {\n label: t('rightSidePanel.bypass'),\n value: LGraphEventMode.BYPASS\n },\n {\n label: t('rightSidePanel.mute'),\n value: LGraphEventMode.NEVER\n }\n ]\"\n />\n </div>\n\n <!-- Color Picker -->\n <div class=\"flex flex-col gap-2\">\n <span>\n {{ t('rightSidePanel.color') }}\n </span>\n <div\n class=\"bg-secondary-background border-none rounded-lg p-1 grid grid-cols-5 gap-1 justify-items-center\"\n >\n <button\n v-for=\"option of colorOptions\"\n :key=\"option.name\"\n :class=\"\n cn(\n 'size-8 rounded-lg bg-transparent border-0 outline-0 ring-0 text-left flex justify-center items-center cursor-pointer',\n option.name === nodeColor\n ? 'bg-interface-menu-component-surface-selected'\n : 'hover:bg-interface-menu-component-surface-selected'\n )\n \"\n @click=\"nodeColor = option.name\"\n >\n <div\n v-tooltip.top=\"option.localizedName()\"\n :class=\"cn('size-4 rounded-full ring-2 ring-gray-500/10')\"\n :style=\"{\n backgroundColor: isLightTheme\n ? option.value.light\n : option.value.dark,\n '--tw-ring-color':\n option.name === nodeColor\n ? isLightTheme\n ? option.value.ringLight\n : option.value.ringDark\n : undefined\n }\"\n :data-testid=\"option.name\"\n />\n </button>\n </div>\n </div>\n\n <!-- Pinned Toggle -->\n <div class=\"flex items-center justify-between\">\n <span>\n {{ t('rightSidePanel.pinned') }}\n </span>\n <ToggleSwitch v-model=\"isPinned\" />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport ToggleSwitch from 'primevue/toggleswitch'\nimport { computed, shallowRef, triggerRef, watchEffect } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport type { ColorOption, LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { LGraphEventMode } from '@/lib/litegraph/src/types/globalEnums'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport FormSelectButton from '@/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.vue'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { adjustColor } from '@/utils/colorUtil'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst props = defineProps<{\n nodes?: LGraphNode[]\n}>()\n\n/**\n * This is not random writing. It is very important.\n * Otherwise, the UI cannot be updated correctly.\n */\nconst targetNodes = shallowRef<LGraphNode[]>([])\nwatchEffect(() => {\n if (props.nodes) {\n targetNodes.value = props.nodes\n } else {\n targetNodes.value = []\n }\n})\n\nconst { t } = useI18n()\n\nconst canvasStore = useCanvasStore()\nconst colorPaletteStore = useColorPaletteStore()\nconst isLightTheme = computed(\n () => colorPaletteStore.completedActivePalette.light_theme\n)\n\nconst nodeState = computed({\n get() {\n let mode: LGraphNode['mode'] | null = null\n const nodes = targetNodes.value\n\n if (nodes.length === 0) return null\n\n // For multiple nodes, if all nodes have the same mode, return that mode, otherwise return null\n if (nodes.length > 1) {\n mode = nodes[0].mode\n if (!nodes.every((node) => node.mode === mode)) {\n mode = null\n }\n } else {\n mode = nodes[0].mode\n }\n\n return mode\n },\n set(value: LGraphNode['mode']) {\n targetNodes.value.forEach((node) => {\n node.mode = value\n })\n /*\n * This is not random writing. It is very important.\n * Otherwise, the UI cannot be updated correctly.\n */\n triggerRef(targetNodes)\n canvasStore.canvas?.setDirty(true, true)\n }\n})\n\n// Pinned state\nconst isPinned = computed<boolean>({\n get() {\n return targetNodes.value.some((node) => node.pinned)\n },\n set(value) {\n targetNodes.value.forEach((node) => node.pin(value))\n /*\n * This is not random writing. It is very important.\n * Otherwise, the UI cannot be updated correctly.\n */\n triggerRef(targetNodes)\n canvasStore.canvas?.setDirty(true, true)\n }\n})\n\ntype NodeColorOption = {\n name: string\n localizedName: () => string\n value: {\n dark: string\n light: string\n ringDark: string\n ringLight: string\n }\n}\n\nfunction getColorValue(color: string): NodeColorOption['value'] {\n return {\n dark: adjustColor(color, { lightness: 0.3 }),\n light: adjustColor(color, { lightness: 0.4 }),\n ringDark: adjustColor(color, { lightness: 0.5 }),\n ringLight: adjustColor(color, { lightness: 0.1 })\n }\n}\n\nconst NO_COLOR_OPTION: NodeColorOption = {\n name: 'noColor',\n localizedName: () => t('color.noColor'),\n value: getColorValue(LiteGraph.NODE_DEFAULT_BGCOLOR)\n}\n\nconst nodeColorEntries = Object.entries(LGraphCanvas.node_colors)\n\nconst colorOptions: NodeColorOption[] = [\n NO_COLOR_OPTION,\n ...nodeColorEntries.map(([name, color]) => ({\n name,\n localizedName: () => t(`color.${name}`),\n value: getColorValue(color.bgcolor)\n }))\n]\n\nconst nodeColor = computed<NodeColorOption['name'] | null>({\n get() {\n if (targetNodes.value.length === 0) return null\n const theColorOptions = targetNodes.value.map((item) =>\n item.getColorOption()\n )\n\n let colorOption: ColorOption | null | false = theColorOptions[0]\n if (!theColorOptions.every((option) => option === colorOption)) {\n colorOption = false\n }\n\n if (colorOption === false) return null\n if (colorOption == null || (!colorOption.bgcolor && !colorOption.color))\n return NO_COLOR_OPTION.name\n return (\n nodeColorEntries.find(\n ([_, color]) =>\n color.bgcolor === colorOption.bgcolor &&\n color.color === colorOption.color\n )?.[0] ?? null\n )\n },\n set(colorName) {\n if (colorName === null) return\n\n const canvasColorOption =\n colorName === NO_COLOR_OPTION.name\n ? null\n : LGraphCanvas.node_colors[colorName]\n\n for (const item of targetNodes.value) {\n item.setColorOption(canvasColorOption)\n }\n /*\n * This is not random writing. It is very important.\n * Otherwise, the UI cannot be updated correctly.\n */\n triggerRef(targetNodes)\n canvasStore.canvas?.setDirty(true, true)\n }\n})\n</script>\n","<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { cn } from '@/utils/tailwindUtil'\nimport type { ClassValue } from '@/utils/tailwindUtil'\n\nconst props = defineProps<{\n nodeTitle: string\n widgetName: string\n isDraggable?: boolean\n isPhysical?: boolean\n class?: ClassValue\n}>()\ndefineEmits<{\n (e: 'toggleVisibility'): void\n}>()\n\nfunction getIcon() {\n return props.isPhysical\n ? 'icon-[lucide--link]'\n : props.isDraggable\n ? 'icon-[lucide--eye]'\n : 'icon-[lucide--eye-off]'\n}\n</script>\n\n<template>\n <div\n :class=\"\n cn(\n 'flex py-1 px-2 break-all rounded items-center gap-1',\n 'bg-node-component-surface',\n props.isDraggable &&\n 'draggable-item drag-handle cursor-grab [&.is-draggable]:cursor-grabbing hover:ring-1 ring-accent-background',\n props.class\n )\n \"\n >\n <div class=\"pointer-events-none flex-1\">\n <div class=\"text-xs text-text-secondary line-clamp-1\">\n {{ nodeTitle }}\n </div>\n <div class=\"text-sm line-clamp-1 leading-8\">{{ widgetName }}</div>\n </div>\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n :disabled=\"isPhysical\"\n @click.stop=\"$emit('toggleVisibility')\"\n >\n <i :class=\"getIcon()\" />\n </Button>\n <div\n v-if=\"isDraggable\"\n class=\"size-4 pointer-events-none icon-[lucide--grip-vertical]\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { watchDebounced } from '@vueuse/core'\nimport {\n computed,\n customRef,\n onBeforeUnmount,\n onMounted,\n ref,\n triggerRef\n} from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport {\n demoteWidget,\n isRecommendedWidget,\n matchesPropertyItem,\n matchesWidgetItem,\n promoteWidget,\n pruneDisconnected,\n widgetItemToProperty\n} from '@/core/graph/subgraph/proxyWidgetUtils'\nimport type { WidgetItem } from '@/core/graph/subgraph/proxyWidgetUtils'\nimport { parseProxyWidgets } from '@/core/schemas/proxyWidget'\nimport type { ProxyWidgetsProperty } from '@/core/schemas/proxyWidget'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'\nimport type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { DraggableList } from '@/scripts/ui/draggableList'\nimport { useLitegraphService } from '@/services/litegraphService'\n\nimport SidePanelSearch from '../layout/SidePanelSearch.vue'\nimport SubgraphNodeWidget from './SubgraphNodeWidget.vue'\n\nconst canvasStore = useCanvasStore()\n\nconst draggableList = ref<DraggableList | undefined>(undefined)\nconst draggableItems = ref()\nconst searchQuery = ref<string>('')\nconst proxyWidgets = customRef<ProxyWidgetsProperty>((track, trigger) => ({\n get() {\n track()\n const node = activeNode.value\n if (!node) return []\n return parseProxyWidgets(node.properties.proxyWidgets)\n },\n set(value?: ProxyWidgetsProperty) {\n trigger()\n const node = activeNode.value\n if (!value) return\n if (!node) {\n console.error('Attempted to toggle widgets with no node selected')\n return\n }\n node.properties.proxyWidgets = value\n }\n}))\n\nasync function searcher(query: string) {\n searchQuery.value = query\n}\n\nconst activeNode = computed(() => {\n const node = canvasStore.selectedItems[0]\n if (node instanceof SubgraphNode) return node\n return undefined\n})\n\nconst activeWidgets = computed<WidgetItem[]>({\n get() {\n if (!activeNode.value) return []\n const node = activeNode.value\n function mapWidgets([id, name]: [string, string]): WidgetItem[] {\n if (id === '-1') {\n const widget = node.widgets.find((w) => w.name === name)\n if (!widget) return []\n return [[{ id: -1, title: '(Linked)', type: '' }, widget]]\n }\n const wNode = node.subgraph._nodes_by_id[id]\n if (!wNode?.widgets) return []\n const widget = wNode.widgets.find((w) => w.name === name)\n if (!widget) return []\n return [[wNode, widget]]\n }\n return proxyWidgets.value.flatMap(mapWidgets)\n },\n set(value: WidgetItem[]) {\n const node = activeNode.value\n if (!node) {\n console.error('Attempted to toggle widgets with no node selected')\n return\n }\n proxyWidgets.value = value.map(widgetItemToProperty)\n }\n})\n\nconst interiorWidgets = computed<WidgetItem[]>(() => {\n const node = activeNode.value\n if (!node) return []\n const { updatePreviews } = useLitegraphService()\n const interiorNodes = node.subgraph.nodes\n for (const node of interiorNodes) {\n node.updateComputedDisabled()\n updatePreviews(node)\n }\n return interiorNodes\n .flatMap(nodeWidgets)\n .filter(([_, w]: WidgetItem) => !w.computedDisabled)\n})\n\nconst candidateWidgets = computed<WidgetItem[]>(() => {\n const node = activeNode.value\n if (!node) return []\n const widgets = proxyWidgets.value\n return interiorWidgets.value.filter(\n (widgetItem: WidgetItem) => !widgets.some(matchesPropertyItem(widgetItem))\n )\n})\nconst filteredCandidates = computed<WidgetItem[]>(() => {\n const query = searchQuery.value.toLowerCase()\n if (!query) return candidateWidgets.value\n return candidateWidgets.value.filter(\n ([n, w]: WidgetItem) =>\n n.title.toLowerCase().includes(query) ||\n w.name.toLowerCase().includes(query)\n )\n})\n\nconst recommendedWidgets = computed(() => {\n const node = activeNode.value\n if (!node) return []\n return filteredCandidates.value.filter(isRecommendedWidget)\n})\n\nconst filteredActive = computed<WidgetItem[]>(() => {\n const query = searchQuery.value.toLowerCase()\n if (!query) return activeWidgets.value\n return activeWidgets.value.filter(\n ([n, w]: WidgetItem) =>\n n.title.toLowerCase().includes(query) ||\n w.name.toLowerCase().includes(query)\n )\n})\n\nfunction toKey(item: WidgetItem) {\n return `${item[0].id}: ${item[1].name}`\n}\nfunction nodeWidgets(n: LGraphNode): WidgetItem[] {\n if (!n.widgets) return []\n return n.widgets.map((w: IBaseWidget) => [n, w])\n}\nfunction demote([node, widget]: WidgetItem) {\n const subgraphNode = activeNode.value\n if (!subgraphNode) return []\n demoteWidget(node, widget, [subgraphNode])\n triggerRef(proxyWidgets)\n}\nfunction promote([node, widget]: WidgetItem) {\n const subgraphNode = activeNode.value\n if (!subgraphNode) return []\n promoteWidget(node, widget, [subgraphNode])\n triggerRef(proxyWidgets)\n}\nfunction showAll() {\n const node = activeNode.value\n if (!node) return\n const widgets = proxyWidgets.value\n const toAdd: ProxyWidgetsProperty =\n filteredCandidates.value.map(widgetItemToProperty)\n widgets.push(...toAdd)\n proxyWidgets.value = widgets\n}\nfunction hideAll() {\n const node = activeNode.value\n if (!node) return\n proxyWidgets.value = proxyWidgets.value.filter(\n (propertyItem) =>\n !filteredActive.value.some(matchesWidgetItem(propertyItem)) ||\n propertyItem[0] === '-1'\n )\n}\nfunction showRecommended() {\n const node = activeNode.value\n if (!node) return\n const widgets = proxyWidgets.value\n const toAdd: ProxyWidgetsProperty =\n recommendedWidgets.value.map(widgetItemToProperty)\n widgets.push(...toAdd)\n proxyWidgets.value = widgets\n}\n\nfunction setDraggableState() {\n draggableList.value?.dispose()\n if (searchQuery.value || !draggableItems.value?.children?.length) return\n draggableList.value = new DraggableList(\n draggableItems.value,\n '.draggable-item'\n )\n draggableList.value.applyNewItemsOrder = function () {\n const reorderedItems = []\n\n let oldPosition = -1\n this.getAllItems().forEach((item, index) => {\n if (item === this.draggableItem) {\n oldPosition = index\n return\n }\n if (!this.isItemToggled(item)) {\n reorderedItems[index] = item\n return\n }\n const newIndex = this.isItemAbove(item) ? index + 1 : index - 1\n reorderedItems[newIndex] = item\n })\n\n for (let index = 0; index < this.getAllItems().length; index++) {\n const item = reorderedItems[index]\n if (typeof item === 'undefined') {\n reorderedItems[index] = this.draggableItem\n }\n }\n const newPosition = reorderedItems.indexOf(this.draggableItem)\n const aw = activeWidgets.value\n const [w] = aw.splice(oldPosition, 1)\n aw.splice(newPosition, 0, w)\n activeWidgets.value = aw\n }\n}\nwatchDebounced(\n filteredActive,\n () => {\n setDraggableState()\n },\n { debounce: 100 }\n)\nonMounted(() => {\n setDraggableState()\n if (activeNode.value) pruneDisconnected(activeNode.value)\n})\nonBeforeUnmount(() => {\n draggableList.value?.dispose()\n})\n</script>\n\n<template>\n <div v-if=\"activeNode\" class=\"subgraph-edit-section flex h-full flex-col\">\n <div class=\"p-4 flex gap-2\">\n <SidePanelSearch :searcher />\n </div>\n\n <div class=\"flex-1\">\n <div\n v-if=\"filteredActive.length\"\n class=\"flex flex-col border-t border-interface-stroke\"\n >\n <div\n class=\"sticky top-0 z-10 flex items-center justify-between backdrop-blur-xl min-h-12 px-4\"\n >\n <div class=\"text-sm font-semibold uppercase line-clamp-1\">\n {{ $t('subgraphStore.shown') }}\n </div>\n <a\n class=\"cursor-pointer text-right text-xs font-normal text-text-secondary hover:text-azure-600 whitespace-nowrap\"\n @click.stop=\"hideAll\"\n >\n {{ $t('subgraphStore.hideAll') }}</a\n >\n </div>\n <div ref=\"draggableItems\" class=\"pb-2 px-2 space-y-0.5 mt-0.5\">\n <SubgraphNodeWidget\n v-for=\"[node, widget] in filteredActive\"\n :key=\"toKey([node, widget])\"\n class=\"bg-interface-panel-surface\"\n :node-title=\"node.title\"\n :widget-name=\"widget.name\"\n :is-shown=\"true\"\n :is-draggable=\"!searchQuery\"\n :is-physical=\"node.id === -1\"\n @toggle-visibility=\"demote([node, widget])\"\n />\n </div>\n </div>\n\n <div\n v-if=\"filteredCandidates.length\"\n class=\"flex flex-col border-t border-interface-stroke\"\n >\n <div\n class=\"sticky top-0 z-10 flex items-center justify-between backdrop-blur-xl min-h-12 px-4\"\n >\n <div class=\"text-sm font-semibold uppercase line-clamp-1\">\n {{ $t('subgraphStore.hidden') }}\n </div>\n <a\n class=\"cursor-pointer text-right text-xs font-normal text-text-secondary hover:text-azure-600 whitespace-nowrap\"\n @click.stop=\"showAll\"\n >\n {{ $t('subgraphStore.showAll') }}</a\n >\n </div>\n <div class=\"pb-2 px-2 space-y-0.5 mt-0.5\">\n <SubgraphNodeWidget\n v-for=\"[node, widget] in filteredCandidates\"\n :key=\"toKey([node, widget])\"\n class=\"bg-interface-panel-surface\"\n :node-title=\"node.title\"\n :widget-name=\"widget.name\"\n @toggle-visibility=\"promote([node, widget])\"\n />\n </div>\n </div>\n\n <div\n v-if=\"recommendedWidgets.length\"\n class=\"flex justify-center border-t border-interface-stroke py-4\"\n >\n <Button\n size=\"sm\"\n class=\"rounded border-none px-3 py-0.5\"\n @click.stop=\"showRecommended\"\n >\n {{ $t('subgraphStore.showRecommended') }}\n </Button>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { storeToRefs } from 'pinia'\nimport { computed, ref, toValue, watchEffect } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport EditableText from '@/components/common/EditableText.vue'\nimport Tab from '@/components/tab/Tab.vue'\nimport TabList from '@/components/tab/TabList.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { SubgraphNode } from '@/lib/litegraph/src/litegraph'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'\nimport type { RightSidePanelTab } from '@/stores/workspace/rightSidePanelStore'\nimport { isLGraphNode } from '@/utils/litegraphUtil'\nimport { cn } from '@/utils/tailwindUtil'\n\nimport TabInfo from './info/TabInfo.vue'\nimport TabParameters from './parameters/TabParameters.vue'\nimport TabSettings from './settings/TabSettings.vue'\nimport SubgraphEditor from './subgraph/SubgraphEditor.vue'\n\nconst canvasStore = useCanvasStore()\nconst rightSidePanelStore = useRightSidePanelStore()\nconst settingStore = useSettingStore()\nconst { t } = useI18n()\n\nconst { selectedItems } = storeToRefs(canvasStore)\nconst { activeTab, isEditingSubgraph } = storeToRefs(rightSidePanelStore)\n\nconst sidebarLocation = computed<'left' | 'right'>(() =>\n settingStore.get('Comfy.Sidebar.Location')\n)\n\n// Panel is on the left when sidebar is on the right, and vice versa\nconst panelIcon = computed(() =>\n sidebarLocation.value === 'right'\n ? 'icon-[lucide--panel-left]'\n : 'icon-[lucide--panel-right]'\n)\n\nconst hasSelection = computed(() => selectedItems.value.length > 0)\n\nconst selectedNodes = computed((): LGraphNode[] => {\n return selectedItems.value.filter(isLGraphNode)\n})\n\nconst isSubgraphNode = computed(() => {\n return selectedNode.value instanceof SubgraphNode\n})\n\nconst isSingleNodeSelected = computed(() => selectedNodes.value.length === 1)\n\nconst selectedNode = computed(() => {\n return isSingleNodeSelected.value ? selectedNodes.value[0] : null\n})\n\nconst selectionCount = computed(() => selectedItems.value.length)\n\nconst panelTitle = computed(() => {\n if (isSingleNodeSelected.value && selectedNode.value) {\n return selectedNode.value.title || selectedNode.value.type || 'Node'\n }\n return t('rightSidePanel.title', { count: selectionCount.value })\n})\n\nfunction closePanel() {\n rightSidePanelStore.closePanel()\n}\n\ntype RightSidePanelTabList = Array<{\n label: () => string\n value: RightSidePanelTab\n}>\n\nconst tabs = computed<RightSidePanelTabList>(() => {\n const list: RightSidePanelTabList = [\n {\n label: () => t('rightSidePanel.parameters'),\n value: 'parameters'\n },\n {\n label: () => t('g.settings'),\n value: 'settings'\n }\n ]\n if (\n !hasSelection.value ||\n (isSingleNodeSelected.value && !isSubgraphNode.value)\n ) {\n list.push({\n label: () => t('rightSidePanel.info'),\n value: 'info'\n })\n }\n return list\n})\n\n// Use global state for activeTab and ensure it's valid\nwatchEffect(() => {\n if (\n !tabs.value.some((tab) => tab.value === activeTab.value) &&\n !(activeTab.value === 'subgraph' && isSubgraphNode.value)\n ) {\n rightSidePanelStore.openPanel(tabs.value[0].value)\n }\n})\n\nconst isEditing = ref(false)\n\nfunction handleTitleEdit(newTitle: string) {\n isEditing.value = false\n\n const trimmedTitle = newTitle.trim()\n if (!trimmedTitle) return\n\n const node = toValue(selectedNode)\n if (!node) return\n\n if (trimmedTitle === node.title) return\n\n node.title = trimmedTitle\n canvasStore.canvas?.setDirty(true, false)\n}\n\nfunction handleTitleCancel() {\n isEditing.value = false\n}\n</script>\n\n<template>\n <div\n data-testid=\"properties-panel\"\n class=\"flex size-full flex-col bg-interface-panel-surface\"\n >\n <!-- Panel Header -->\n <section class=\"pt-1\">\n <div class=\"flex items-center justify-between pl-4 pr-3\">\n <h3 class=\"my-3.5 text-sm font-semibold line-clamp-2\">\n <EditableText\n v-if=\"isSingleNodeSelected\"\n :model-value=\"panelTitle\"\n :is-editing=\"isEditing\"\n :input-attrs=\"{ 'data-testid': 'node-title-input' }\"\n @edit=\"handleTitleEdit\"\n @cancel=\"handleTitleCancel\"\n @dblclick=\"isEditing = true\"\n />\n <template v-else>\n {{ panelTitle }}\n </template>\n </h3>\n\n <div class=\"flex gap-2\">\n <Button\n v-if=\"isSubgraphNode\"\n variant=\"secondary\"\n size=\"icon\"\n :class=\"cn(isEditingSubgraph && 'bg-secondary-background-selected')\"\n @click=\"\n rightSidePanelStore.openPanel(\n isEditingSubgraph ? 'parameters' : 'subgraph'\n )\n \"\n >\n <i class=\"icon-[lucide--settings-2]\" />\n </Button>\n <Button\n variant=\"secondary\"\n size=\"icon\"\n :aria-pressed=\"rightSidePanelStore.isOpen\"\n :aria-label=\"t('rightSidePanel.togglePanel')\"\n @click=\"closePanel\"\n >\n <i :class=\"cn(panelIcon, 'size-4')\" />\n </Button>\n </div>\n </div>\n <nav v-if=\"hasSelection\" class=\"px-4 pb-2 pt-1\">\n <TabList\n :model-value=\"activeTab\"\n @update:model-value=\"\n (newTab: RightSidePanelTab) => {\n rightSidePanelStore.openPanel(newTab)\n }\n \"\n >\n <Tab\n v-for=\"tab in tabs\"\n :key=\"tab.value\"\n class=\"text-sm py-1 px-2 font-inter\"\n :value=\"tab.value\"\n >\n {{ tab.label() }}\n </Tab>\n </TabList>\n </nav>\n </section>\n\n <!-- Panel Content -->\n <div class=\"scrollbar-thin flex-1 overflow-y-auto\">\n <div\n v-if=\"!hasSelection\"\n class=\"flex size-full p-4 items-start justify-start text-sm text-muted-foreground\"\n >\n {{ $t('rightSidePanel.noSelection') }}\n </div>\n <SubgraphEditor\n v-else-if=\"isSubgraphNode && isEditingSubgraph\"\n :node=\"selectedNode\"\n />\n <template v-else>\n <TabParameters\n v-if=\"activeTab === 'parameters'\"\n :nodes=\"selectedNodes\"\n />\n <TabInfo v-else-if=\"activeTab === 'info'\" :nodes=\"selectedNodes\" />\n <TabSettings\n v-else-if=\"activeTab === 'settings'\"\n :nodes=\"selectedNodes\"\n />\n </template>\n </div>\n </div>\n</template>\n","import { useMouse } from '@vueuse/core'\nimport { defineStore } from 'pinia'\nimport { computed, ref, shallowRef } from 'vue'\n\nimport type NodeSearchBoxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'\nimport type { CanvasPointerEvent } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\n\nexport const useSearchBoxStore = defineStore('searchBox', () => {\n const settingStore = useSettingStore()\n const { x, y } = useMouse()\n\n const newSearchBoxEnabled = computed(\n () => settingStore.get('Comfy.NodeSearchBoxImpl') === 'default'\n )\n\n const popoverRef = shallowRef<InstanceType<\n typeof NodeSearchBoxPopover\n > | null>(null)\n\n function setPopoverRef(\n popover: InstanceType<typeof NodeSearchBoxPopover> | null\n ) {\n popoverRef.value = popover\n }\n\n const visible = ref(false)\n function toggleVisible() {\n if (newSearchBoxEnabled.value) {\n visible.value = !visible.value\n return\n }\n if (!popoverRef.value) return\n popoverRef.value.showSearchBox(\n new MouseEvent('click', {\n clientX: x.value,\n clientY: y.value,\n // @ts-expect-error layerY is a nonstandard property\n layerY: y.value\n }) as unknown as CanvasPointerEvent\n )\n }\n\n return {\n newSearchBoxEnabled,\n setPopoverRef,\n toggleVisible,\n visible\n }\n})\n","<!-- Auto complete with extra event \"focused-option-changed\" -->\n<script>\nimport AutoComplete from 'primevue/autocomplete'\n\nexport default {\n name: 'AutoCompletePlus',\n extends: AutoComplete,\n emits: ['focused-option-changed'],\n data() {\n return {\n // Flag to determine if IME is active\n isComposing: false\n }\n },\n mounted() {\n if (typeof AutoComplete.mounted === 'function') {\n AutoComplete.mounted.call(this)\n }\n\n // Retrieve the actual <input> element and attach IME events\n const inputEl = this.$el.querySelector('input')\n if (inputEl) {\n inputEl.addEventListener('compositionstart', () => {\n this.isComposing = true\n })\n inputEl.addEventListener('compositionend', () => {\n this.isComposing = false\n })\n }\n // Add a watcher on the focusedOptionIndex property\n this.$watch(\n () => this.focusedOptionIndex,\n (newVal, oldVal) => {\n // Emit a custom event when focusedOptionIndex changes\n this.$emit('focused-option-changed', newVal)\n }\n )\n },\n methods: {\n // Override onKeyDown to block Enter when IME is active\n onKeyDown(event) {\n if (event.key === 'Enter' && this.isComposing) {\n event.preventDefault()\n event.stopPropagation()\n return\n }\n\n AutoComplete.methods.onKeyDown.call(this, event)\n }\n }\n}\n</script>\n","<template>\n <div\n class=\"option-container flex w-full cursor-pointer items-center justify-between overflow-hidden px-2 py-0\"\n >\n <div class=\"option-display-name flex flex-col font-semibold\">\n <div>\n <span v-if=\"isBookmarked\">\n <i class=\"pi pi-bookmark-fill mr-1 text-sm\" />\n </span>\n <span v-html=\"highlightQuery(nodeDef.display_name, currentQuery)\" />\n <span>&nbsp;</span>\n <Tag v-if=\"showIdName\" severity=\"secondary\">\n <span v-html=\"highlightQuery(nodeDef.name, currentQuery)\" />\n </Tag>\n </div>\n <div\n v-if=\"showCategory\"\n class=\"option-category truncate text-sm font-light text-muted\"\n >\n {{ nodeDef.category.replaceAll('/', ' > ') }}\n </div>\n </div>\n <div class=\"option-badges\">\n <Tag\n v-if=\"nodeDef.experimental\"\n :value=\"$t('g.experimental')\"\n severity=\"primary\"\n />\n <Tag\n v-if=\"nodeDef.deprecated\"\n :value=\"$t('g.deprecated')\"\n severity=\"danger\"\n />\n <Tag\n v-if=\"showNodeFrequency && nodeFrequency > 0\"\n :value=\"formatNumberWithSuffix(nodeFrequency, { roundToInt: true })\"\n severity=\"secondary\"\n />\n <Chip\n v-if=\"nodeDef.nodeSource.type !== NodeSourceType.Unknown\"\n class=\"text-sm font-light\"\n >\n {{ nodeDef.nodeSource.displayText }}\n </Chip>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Chip from 'primevue/chip'\nimport Tag from 'primevue/tag'\nimport { computed } from 'vue'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'\nimport type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'\nimport { useNodeFrequencyStore } from '@/stores/nodeDefStore'\nimport { NodeSourceType } from '@/types/nodeSource'\nimport { formatNumberWithSuffix, highlightQuery } from '@/utils/formatUtil'\n\nconst settingStore = useSettingStore()\nconst showCategory = computed(() =>\n settingStore.get('Comfy.NodeSearchBoxImpl.ShowCategory')\n)\nconst showIdName = computed(() =>\n settingStore.get('Comfy.NodeSearchBoxImpl.ShowIdName')\n)\nconst showNodeFrequency = computed(() =>\n settingStore.get('Comfy.NodeSearchBoxImpl.ShowNodeFrequency')\n)\nconst nodeFrequencyStore = useNodeFrequencyStore()\nconst nodeFrequency = computed(() =>\n nodeFrequencyStore.getNodeFrequency(props.nodeDef)\n)\n\nconst nodeBookmarkStore = useNodeBookmarkStore()\nconst isBookmarked = computed(() =>\n nodeBookmarkStore.isBookmarked(props.nodeDef)\n)\n\nconst props = defineProps<{\n nodeDef: ComfyNodeDefImpl\n currentQuery: string\n}>()\n</script>\n\n<style scoped>\n:deep(.highlight) {\n background-color: var(--p-primary-color);\n color: var(--p-primary-contrast-color);\n font-weight: 700;\n border-radius: 0.25rem;\n padding: 0 0.125rem;\n margin: -0.125rem 0.125rem;\n}\n</style>\n","<template>\n <div\n class=\"comfy-vue-node-search-container flex w-full min-w-96 items-center justify-center\"\n >\n <div\n v-if=\"enableNodePreview && hoveredSuggestion\"\n class=\"comfy-vue-node-preview-container absolute top-[50px] left-[-375px] z-50 cursor-pointer\"\n @mousedown.stop=\"onAddNode(hoveredSuggestion!)\"\n >\n <NodePreview\n :key=\"hoveredSuggestion?.name || ''\"\n :node-def=\"hoveredSuggestion\"\n />\n </div>\n\n <Button\n variant=\"secondary\"\n :aria-label=\"$t('g.addNodeFilterCondition')\"\n class=\"filter-button z-10\"\n @click=\"nodeSearchFilterVisible = true\"\n >\n <i class=\"pi pi-filter\" />\n </Button>\n <Dialog\n v-model:visible=\"nodeSearchFilterVisible\"\n class=\"min-w-96\"\n dismissable-mask\n modal\n @hide=\"reFocusInput\"\n >\n <template #header>\n <h3>{{ $t('g.addNodeFilterCondition') }}</h3>\n </template>\n <div class=\"_dialog-body\">\n <NodeSearchFilter @add-filter=\"onAddFilter\" />\n </div>\n </Dialog>\n\n <AutoCompletePlus\n ref=\"autoCompletePlus\"\n :model-value=\"filters\"\n class=\"comfy-vue-node-search-box z-10 grow\"\n scroll-height=\"40vh\"\n :placeholder=\"placeholder\"\n :input-id=\"inputId\"\n append-to=\"self\"\n :suggestions=\"suggestions\"\n :delay=\"100\"\n :loading=\"!nodeFrequencyStore.isLoaded\"\n complete-on-focus\n auto-option-focus\n force-selection\n multiple\n option-label=\"display_name\"\n @complete=\"search($event.query)\"\n @option-select=\"onAddNode($event.value)\"\n @focused-option-changed=\"setHoverSuggestion($event)\"\n >\n <template #option=\"{ option }\">\n <NodeSearchItem :node-def=\"option\" :current-query=\"currentQuery\" />\n </template>\n <!-- FilterAndValue -->\n <template #chip=\"{ value }\">\n <SearchFilterChip\n v-if=\"value.filterDef && value.value\"\n :key=\"`${value.filterDef.id}-${value.value}`\"\n :text=\"value.value\"\n :badge=\"value.filterDef.invokeSequence.toUpperCase()\"\n :badge-class=\"value.filterDef.invokeSequence + '-badge'\"\n @remove=\"\n onRemoveFilter(\n $event,\n value as FuseFilterWithValue<ComfyNodeDefImpl, string>\n )\n \"\n />\n </template>\n </AutoCompletePlus>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { debounce } from 'es-toolkit/compat'\nimport Dialog from 'primevue/dialog'\nimport { computed, nextTick, onMounted, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport NodePreview from '@/components/node/NodePreview.vue'\nimport AutoCompletePlus from '@/components/primevueOverride/AutoCompletePlus.vue'\nimport NodeSearchFilter from '@/components/searchbox/NodeSearchFilter.vue'\nimport NodeSearchItem from '@/components/searchbox/NodeSearchItem.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'\nimport { useNodeDefStore, useNodeFrequencyStore } from '@/stores/nodeDefStore'\nimport type { FuseFilterWithValue } from '@/utils/fuseUtil'\n\nimport SearchFilterChip from '../common/SearchFilterChip.vue'\n\nconst settingStore = useSettingStore()\nconst { t } = useI18n()\nconst telemetry = useTelemetry()\n\nconst enableNodePreview = computed(() =>\n settingStore.get('Comfy.NodeSearchBoxImpl.NodePreview')\n)\n\nconst { filters, searchLimit = 64 } = defineProps<{\n filters: FuseFilterWithValue<ComfyNodeDefImpl, string>[]\n searchLimit?: number\n}>()\n\nconst autoCompletePlus = ref()\nconst nodeSearchFilterVisible = ref(false)\nconst inputId = `comfy-vue-node-search-box-input-${Math.random()}`\nconst suggestions = ref<ComfyNodeDefImpl[]>([])\nconst hoveredSuggestion = ref<ComfyNodeDefImpl | null>(null)\nconst currentQuery = ref('')\nconst placeholder = computed(() => {\n return filters.length === 0 ? t('g.searchNodes') + '...' : ''\n})\n\nconst nodeDefStore = useNodeDefStore()\nconst nodeFrequencyStore = useNodeFrequencyStore()\n\n// Debounced search tracking (500ms as per implementation plan)\nconst debouncedTrackSearch = debounce((query: string) => {\n if (query.trim()) {\n telemetry?.trackNodeSearch({ query })\n }\n}, 500)\n\nconst search = (query: string) => {\n const queryIsEmpty = query === '' && filters.length === 0\n currentQuery.value = query\n suggestions.value = queryIsEmpty\n ? nodeFrequencyStore.topNodeDefs\n : [\n ...nodeDefStore.nodeSearchService.searchNode(query, filters, {\n limit: searchLimit\n })\n ]\n\n // Track search queries with debounce\n debouncedTrackSearch(query)\n}\n\nconst emit = defineEmits(['addFilter', 'removeFilter', 'addNode'])\n\n// Track node selection and emit addNode event\nconst onAddNode = (nodeDef: ComfyNodeDefImpl) => {\n telemetry?.trackNodeSearchResultSelected({\n node_type: nodeDef.name,\n last_query: currentQuery.value\n })\n emit('addNode', nodeDef)\n}\n\nlet inputElement: HTMLInputElement | null = null\nconst reFocusInput = async () => {\n inputElement ??= document.getElementById(inputId) as HTMLInputElement\n if (inputElement) {\n inputElement.blur()\n await nextTick(() => inputElement?.focus())\n }\n}\n\nonMounted(() => {\n inputElement ??= document.getElementById(inputId) as HTMLInputElement\n if (inputElement) inputElement.focus()\n autoCompletePlus.value.hide = () => search('')\n search('')\n autoCompletePlus.value.show()\n})\nconst onAddFilter = (\n filterAndValue: FuseFilterWithValue<ComfyNodeDefImpl, string>\n) => {\n nodeSearchFilterVisible.value = false\n emit('addFilter', filterAndValue)\n}\nconst onRemoveFilter = async (\n event: Event,\n filterAndValue: FuseFilterWithValue<ComfyNodeDefImpl, string>\n) => {\n event.stopPropagation()\n event.preventDefault()\n emit('removeFilter', filterAndValue)\n await reFocusInput()\n}\nconst setHoverSuggestion = (index: number) => {\n if (index === -1) {\n hoveredSuggestion.value = null\n return\n }\n const value = suggestions.value[index]\n hoveredSuggestion.value = value\n}\n</script>\n","<template>\n <div>\n <Dialog\n v-model:visible=\"visible\"\n modal\n :dismissable-mask=\"dismissable\"\n :pt=\"{\n root: {\n class: 'invisible-dialog-root',\n role: 'search'\n },\n mask: { class: 'node-search-box-dialog-mask' },\n transition: {\n enterFromClass: 'opacity-0 scale-75',\n // 100ms is the duration of the transition in the dialog component\n enterActiveClass: 'transition-all duration-100 ease-out',\n leaveActiveClass: 'transition-all duration-100 ease-in',\n leaveToClass: 'opacity-0 scale-75'\n }\n }\"\n @hide=\"clearFilters\"\n >\n <template #container>\n <NodeSearchBox\n :filters=\"nodeFilters\"\n @add-filter=\"addFilter\"\n @remove-filter=\"removeFilter\"\n @add-node=\"addNode\"\n />\n </template>\n </Dialog>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport { storeToRefs } from 'pinia'\nimport Dialog from 'primevue/dialog'\nimport { computed, ref, toRaw, watch, watchEffect } from 'vue'\n\nimport type { Point } from '@/lib/litegraph/src/interfaces'\nimport type { LiteGraphCanvasEvent } from '@/lib/litegraph/src/litegraph'\nimport { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useLitegraphService } from '@/services/litegraphService'\nimport type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'\nimport { useNodeDefStore } from '@/stores/nodeDefStore'\nimport { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'\nimport { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'\nimport type { FuseFilterWithValue } from '@/utils/fuseUtil'\n\nimport NodeSearchBox from './NodeSearchBox.vue'\n\nlet triggerEvent: CanvasPointerEvent | null = null\nlet listenerController: AbortController | null = null\nlet disconnectOnReset = false\n\nconst settingStore = useSettingStore()\nconst searchBoxStore = useSearchBoxStore()\nconst litegraphService = useLitegraphService()\n\nconst { visible, newSearchBoxEnabled } = storeToRefs(searchBoxStore)\nconst dismissable = ref(true)\nfunction getNewNodeLocation(): Point {\n return triggerEvent\n ? [triggerEvent.canvasX, triggerEvent.canvasY]\n : litegraphService.getCanvasCenter()\n}\nconst nodeFilters = ref<FuseFilterWithValue<ComfyNodeDefImpl, string>[]>([])\nfunction addFilter(filter: FuseFilterWithValue<ComfyNodeDefImpl, string>) {\n nodeFilters.value.push(filter)\n}\nfunction removeFilter(filter: FuseFilterWithValue<ComfyNodeDefImpl, string>) {\n nodeFilters.value = nodeFilters.value.filter(\n (f) => toRaw(f) !== toRaw(filter)\n )\n}\nfunction clearFilters() {\n nodeFilters.value = []\n}\nfunction closeDialog() {\n visible.value = false\n}\nconst canvasStore = useCanvasStore()\n\nfunction addNode(nodeDef: ComfyNodeDefImpl) {\n const node = litegraphService.addNodeOnGraph(nodeDef, {\n pos: getNewNodeLocation()\n })\n\n if (disconnectOnReset && triggerEvent) {\n canvasStore.getCanvas().linkConnector.connectToNode(node, triggerEvent)\n } else if (!triggerEvent) {\n console.warn('The trigger event was undefined when addNode was called.')\n }\n\n disconnectOnReset = false\n\n // Notify changeTracker - new step should be added\n useWorkflowStore().activeWorkflow?.changeTracker?.checkState()\n window.requestAnimationFrame(closeDialog)\n}\n\nfunction showSearchBox(e: CanvasPointerEvent | null) {\n if (newSearchBoxEnabled.value) {\n if (e?.pointerType === 'touch') {\n setTimeout(() => {\n showNewSearchBox(e)\n }, 128)\n } else {\n showNewSearchBox(e)\n }\n } else {\n canvasStore.getCanvas().showSearchBox(e)\n }\n}\n\nfunction getFirstLink() {\n return canvasStore.getCanvas().linkConnector.renderLinks.at(0)\n}\n\nconst nodeDefStore = useNodeDefStore()\nfunction showNewSearchBox(e: CanvasPointerEvent | null) {\n const firstLink = getFirstLink()\n if (firstLink) {\n const filter =\n firstLink.toType === 'input'\n ? nodeDefStore.nodeSearchService.inputTypeFilter\n : nodeDefStore.nodeSearchService.outputTypeFilter\n\n const dataType = firstLink.fromSlot.type?.toString() ?? ''\n addFilter({\n filterDef: filter,\n value: dataType\n })\n }\n\n visible.value = true\n triggerEvent = e\n\n // Prevent the dialog from being dismissed immediately\n dismissable.value = false\n setTimeout(() => {\n dismissable.value = true\n }, 300)\n}\n\nfunction showContextMenu(e: CanvasPointerEvent) {\n const firstLink = getFirstLink()\n if (!firstLink) return\n\n const { node, fromSlot, toType } = firstLink\n const commonOptions = {\n e,\n allow_searchbox: true,\n showSearchBox: () => {\n cancelResetOnContextClose()\n showSearchBox(e)\n }\n }\n const afterRerouteId = firstLink.fromReroute?.id\n const connectionOptions =\n toType === 'input'\n ? { nodeFrom: node, slotFrom: fromSlot, afterRerouteId }\n : { nodeTo: node, slotTo: fromSlot, afterRerouteId }\n\n const canvas = canvasStore.getCanvas()\n const menu = canvas.showConnectionMenu({\n ...connectionOptions,\n ...commonOptions\n })\n\n if (!menu) {\n console.warn('No menu was returned from showConnectionMenu')\n return\n }\n\n triggerEvent = e\n listenerController = new AbortController()\n const { signal } = listenerController\n const options = { once: true, signal }\n\n // Connect the node after it is created via context menu\n useEventListener(\n canvas.canvas,\n 'connect-new-default-node',\n (createEvent) => {\n if (!(createEvent instanceof CustomEvent))\n throw new Error('Invalid event')\n\n const node: unknown = createEvent.detail?.node\n if (!(node instanceof LGraphNode)) throw new Error('Invalid node')\n\n disconnectOnReset = false\n createEvent.preventDefault()\n canvas.linkConnector.connectToNode(node, e)\n },\n options\n )\n\n // Reset when the context menu is closed\n const cancelResetOnContextClose = useEventListener(\n menu.controller.signal,\n 'abort',\n reset,\n options\n )\n}\n\n// Disable litegraph's default behavior of release link and search box.\nwatchEffect(() => {\n const { canvas } = canvasStore\n if (!canvas) return\n\n LiteGraph.release_link_on_empty_shows_menu = false\n canvas.allow_searchbox = false\n\n useEventListener(\n canvas.linkConnector.events,\n 'dropped-on-canvas',\n handleDroppedOnCanvas\n )\n})\n\nfunction canvasEventHandler(e: LiteGraphCanvasEvent) {\n if (e.detail.subType === 'empty-double-click') {\n showSearchBox(e.detail.originalEvent)\n } else if (e.detail.subType === 'group-double-click') {\n const group = e.detail.group\n const [_, y] = group.pos\n const relativeY = e.detail.originalEvent.canvasY - y\n // Show search box if the click is NOT on the title bar\n if (relativeY > group.titleHeight) {\n showSearchBox(e.detail.originalEvent)\n }\n }\n}\n\nconst linkReleaseAction = computed(() =>\n settingStore.get('Comfy.LinkRelease.Action')\n)\n\nconst linkReleaseActionShift = computed(() =>\n settingStore.get('Comfy.LinkRelease.ActionShift')\n)\n\n// Prevent normal LinkConnector reset (called by CanvasPointer.finally)\nfunction preventDefault(e: Event) {\n return e.preventDefault()\n}\nfunction cancelNextReset(e: CustomEvent<CanvasPointerEvent>) {\n e.preventDefault()\n\n const canvas = canvasStore.getCanvas()\n canvas.linkConnector.state.snapLinksPos = [e.detail.canvasX, e.detail.canvasY]\n useEventListener(canvas.linkConnector.events, 'reset', preventDefault, {\n once: true\n })\n}\n\nfunction handleDroppedOnCanvas(e: CustomEvent<CanvasPointerEvent>) {\n disconnectOnReset = true\n const action = e.detail.shiftKey\n ? linkReleaseActionShift.value\n : linkReleaseAction.value\n switch (action) {\n case LinkReleaseTriggerAction.SEARCH_BOX:\n cancelNextReset(e)\n showSearchBox(e.detail)\n break\n case LinkReleaseTriggerAction.CONTEXT_MENU:\n cancelNextReset(e)\n showContextMenu(e.detail)\n break\n case LinkReleaseTriggerAction.NO_ACTION:\n default:\n break\n }\n}\n\n// Resets litegraph state\nfunction reset() {\n listenerController?.abort()\n listenerController = null\n triggerEvent = null\n\n const canvas = canvasStore.getCanvas()\n canvas.linkConnector.events.removeEventListener('reset', preventDefault)\n if (disconnectOnReset) canvas.linkConnector.disconnectLinks()\n\n canvas.linkConnector.reset()\n canvas.setDirty(true, true)\n}\n\n// Reset connecting links when the search box is closed\nwatch(visible, () => {\n if (!visible.value) reset()\n})\n\nuseEventListener(document, 'litegraph:canvas', canvasEventHandler)\ndefineExpose({ showSearchBox })\n</script>\n\n<style>\n.invisible-dialog-root {\n width: 60%;\n min-width: 24rem;\n max-width: 48rem;\n border: 0 !important;\n background-color: transparent !important;\n margin-top: 25vh;\n margin-left: 400px;\n}\n@media all and (max-width: 768px) {\n .invisible-dialog-root {\n margin-left: 0;\n }\n}\n\n.node-search-box-dialog-mask {\n align-items: flex-start !important;\n}\n</style>\n","import { defineStore } from 'pinia'\nimport { ref } from 'vue'\n\nexport type HelpCenterTriggerLocation = 'sidebar' | 'topbar'\n\nexport const useHelpCenterStore = defineStore('helpCenter', () => {\n const isVisible = ref(false)\n const triggerLocation = ref<HelpCenterTriggerLocation>('sidebar')\n\n const toggle = (location: HelpCenterTriggerLocation = 'sidebar') => {\n if (!isVisible.value) {\n triggerLocation.value = location\n }\n isVisible.value = !isVisible.value\n }\n\n const show = (location: HelpCenterTriggerLocation = 'sidebar') => {\n triggerLocation.value = location\n isVisible.value = true\n }\n\n const hide = () => {\n isVisible.value = false\n }\n\n return {\n isVisible,\n triggerLocation,\n toggle,\n show,\n hide\n }\n})\n","import { storeToRefs } from 'pinia'\nimport { computed, onMounted } from 'vue'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useReleaseStore } from '@/platform/updates/common/releaseStore'\nimport { useDialogService } from '@/services/dialogService'\nimport { useHelpCenterStore } from '@/stores/helpCenterStore'\nimport type { HelpCenterTriggerLocation } from '@/stores/helpCenterStore'\nimport { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'\nimport { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'\n\nexport function useHelpCenter(\n triggerFrom: HelpCenterTriggerLocation = 'sidebar'\n) {\n const settingStore = useSettingStore()\n const releaseStore = useReleaseStore()\n const helpCenterStore = useHelpCenterStore()\n const { isVisible: isHelpCenterVisible, triggerLocation } =\n storeToRefs(helpCenterStore)\n const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)\n\n const conflictDetection = useConflictDetection()\n const { showNodeConflictDialog } = useDialogService()\n\n // Use conflict acknowledgment state from composable - call only once\n const { shouldShowRedDot: shouldShowConflictRedDot, markConflictsAsSeen } =\n useConflictAcknowledgment()\n\n // Use either release red dot or conflict red dot\n const shouldShowRedDot = computed((): boolean => {\n const releaseRedDot = showReleaseRedDot.value\n return releaseRedDot || shouldShowConflictRedDot.value\n })\n\n const sidebarLocation = computed(() =>\n settingStore.get('Comfy.Sidebar.Location')\n )\n\n /**\n * Toggle Help Center and track UI button click.\n */\n const toggleHelpCenter = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: `${triggerFrom}_help_center_toggled`\n })\n helpCenterStore.toggle(triggerFrom)\n }\n\n const closeHelpCenter = () => {\n helpCenterStore.hide()\n }\n\n /**\n * Handle What's New popup dismissal\n * Check if conflict modal should be shown after ComfyUI update\n */\n const handleWhatsNewDismissed = async () => {\n try {\n // Check if conflict modal should be shown after update\n const shouldShow =\n await conflictDetection.shouldShowConflictModalAfterUpdate()\n if (shouldShow) {\n showConflictModal()\n }\n } catch (error) {\n console.error('[HelpCenter] Error checking conflict modal:', error)\n }\n }\n\n /**\n * Show the node conflict dialog with current conflict data\n */\n const showConflictModal = () => {\n showNodeConflictDialog({\n showAfterWhatsNew: true,\n dialogComponentProps: {\n onClose: () => {\n markConflictsAsSeen()\n }\n }\n })\n }\n\n // Initialize release store on mount\n onMounted(async () => {\n // Initialize release store to fetch releases for toast and popup\n await releaseStore.initialize()\n })\n\n return {\n isHelpCenterVisible,\n triggerLocation,\n shouldShowRedDot,\n sidebarLocation,\n toggleHelpCenter,\n closeHelpCenter,\n handleWhatsNewDismissed\n }\n}\n","<template>\n <div v-if=\"shouldShow\" class=\"release-toast-popup\">\n <div\n class=\"w-96 max-h-96 bg-base-background border border-border-default rounded-lg shadow-[1px_1px_8px_0_rgba(0,0,0,0.4)] flex flex-col\"\n >\n <!-- Main content -->\n <div class=\"p-4 flex flex-col gap-4 flex-1 min-h-0\">\n <!-- Header section with icon and text -->\n <div class=\"flex items-center gap-4\">\n <div\n class=\"p-3 bg-primary-background-hover rounded-lg flex items-center justify-center shrink-0\"\n >\n <i class=\"icon-[lucide--rocket] w-4 h-4 text-white\" />\n </div>\n <div class=\"flex flex-col gap-1\">\n <div\n class=\"text-sm font-normal text-base-foreground leading-[1.429]\"\n >\n {{ $t('releaseToast.newVersionAvailable') }}\n </div>\n <div\n class=\"text-sm font-normal text-muted-foreground leading-[1.21]\"\n >\n {{ latestRelease?.version }}\n </div>\n </div>\n </div>\n\n <!-- Description section -->\n <div\n class=\"pl-14 text-sm font-normal text-muted-foreground leading-[1.21] overflow-y-auto flex-1 min-h-0\"\n v-html=\"formattedContent\"\n ></div>\n </div>\n\n <!-- Footer section -->\n <div class=\"flex justify-between items-center px-4 pb-4\">\n <a\n class=\"flex items-center gap-2 text-sm font-normal py-1 text-muted-foreground hover:text-base-foreground\"\n :href=\"changelogUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click=\"handleLearnMore\"\n >\n <i class=\"icon-[lucide--external-link] w-4 h-4\"></i>\n {{ $t('releaseToast.whatsNew') }}\n </a>\n <div class=\"flex items-center gap-4\">\n <button\n class=\"h-6 px-0 bg-transparent border-none text-sm font-normal text-muted-foreground hover:text-base-foreground cursor-pointer\"\n @click=\"handleSkip\"\n >\n {{ $t('releaseToast.skip') }}\n </button>\n <button\n class=\"h-10 px-4 bg-secondary-background hover:bg-secondary-background-hover rounded-lg border-none text-sm font-normal text-base-foreground cursor-pointer\"\n @click=\"handleUpdate\"\n >\n {{ $t('releaseToast.update') }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { default as DOMPurify } from 'dompurify'\nimport { computed, onMounted, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { isElectron } from '@/utils/envUtil'\nimport { formatVersionAnchor } from '@/utils/formatUtil'\nimport { renderMarkdownToHtml } from '@/utils/markdownRendererUtil'\n\nimport type { ReleaseNote } from '../common/releaseService'\nimport { useReleaseStore } from '../common/releaseStore'\n\nconst { buildDocsUrl } = useExternalLink()\nconst { toastErrorHandler } = useErrorHandling()\nconst releaseStore = useReleaseStore()\nconst { t } = useI18n()\n\n// Local state for dismissed status\nconst isDismissed = ref(false)\n\n// Get latest release from store\nconst latestRelease = computed<ReleaseNote | null>(() => {\n return releaseStore.recentRelease\n})\n\n// Show toast when new version available and not dismissed\nconst shouldShow = computed(\n () => releaseStore.shouldShowToast && !isDismissed.value\n)\n\n// Generate changelog URL with version anchor (language-aware)\nconst changelogUrl = computed(() => {\n const changelogBaseUrl = buildDocsUrl('/changelog', { includeLocale: true })\n if (latestRelease.value?.version) {\n const versionAnchor = formatVersionAnchor(latestRelease.value.version)\n return `${changelogBaseUrl}#${versionAnchor}`\n }\n return changelogBaseUrl\n})\n\nconst formattedContent = computed(() => {\n if (!latestRelease.value?.content) {\n return DOMPurify.sanitize(`<p>${t('releaseToast.description')}</p>`)\n }\n\n try {\n const markdown = latestRelease.value.content\n // Remove the h1 title line and images for toast mode\n const contentWithoutTitle = markdown.replace(/^# .+$/m, '')\n const contentWithoutImages = contentWithoutTitle.replace(\n /!\\[.*?\\]\\(.*?\\)/g,\n ''\n )\n\n // Check if there's meaningful content left after cleanup\n const trimmedContent = contentWithoutImages.trim()\n if (!trimmedContent || trimmedContent.replace(/\\s+/g, '') === '') {\n return DOMPurify.sanitize(`<p>${t('releaseToast.description')}</p>`)\n }\n\n // renderMarkdownToHtml already sanitizes with DOMPurify, so this is safe\n return renderMarkdownToHtml(contentWithoutImages)\n } catch (error) {\n console.error('Error parsing markdown:', error)\n // Fallback to plain text with line breaks - sanitize the HTML we create\n const fallbackContent = latestRelease.value.content.replace(/\\n/g, '<br>')\n return fallbackContent.trim()\n ? DOMPurify.sanitize(fallbackContent)\n : DOMPurify.sanitize(`<p>${t('releaseToast.description')}</p>`)\n }\n})\n\n// Auto-hide timer\nlet hideTimer: ReturnType<typeof setTimeout> | null = null\n\nconst startAutoHide = () => {\n if (hideTimer) clearTimeout(hideTimer)\n hideTimer = setTimeout(() => {\n dismissToast()\n }, 8000) // 8 second auto-hide\n}\n\nconst clearAutoHide = () => {\n if (hideTimer) {\n clearTimeout(hideTimer)\n hideTimer = null\n }\n}\n\nconst dismissToast = () => {\n isDismissed.value = true\n clearAutoHide()\n}\n\nconst handleSkip = () => {\n if (latestRelease.value) {\n void releaseStore.handleSkipRelease(latestRelease.value.version)\n }\n dismissToast()\n}\n\nconst handleLearnMore = () => {\n if (latestRelease.value) {\n void releaseStore.handleShowChangelog(latestRelease.value.version)\n }\n // Do not dismiss; anchor will navigate in new tab but keep toast? spec maybe wants dismiss? We'll dismiss.\n dismissToast()\n}\n\nconst handleUpdate = async () => {\n if (isElectron()) {\n try {\n await useCommandStore().execute('Comfy-Desktop.CheckForUpdates')\n dismissToast()\n } catch (error) {\n toastErrorHandler(error)\n }\n return\n }\n\n window.open(\n buildDocsUrl('/installation/update_comfyui', { includeLocale: true }),\n '_blank'\n )\n dismissToast()\n}\n\n// Start auto-hide when toast becomes visible\nwatch(shouldShow, (isVisible) => {\n if (isVisible) {\n startAutoHide()\n } else {\n clearAutoHide()\n }\n})\n\n// Initialize on mount\nonMounted(async () => {\n // Fetch releases if not already loaded\n if (!releaseStore.releases.length) {\n await releaseStore.fetchReleases()\n }\n})\n\n// Expose methods for testing\ndefineExpose({\n handleSkip,\n handleLearnMore,\n handleUpdate\n})\n</script>\n\n<style scoped>\n/* Toast popup - positioning handled by parent */\n.release-toast-popup {\n position: absolute;\n bottom: 1rem;\n z-index: 1000;\n pointer-events: auto;\n}\n\n/* Sidebar positioning classes applied by parent - matching help center */\n.release-toast-popup.sidebar-left,\n.release-toast-popup.sidebar-left.small-sidebar {\n left: 1rem;\n}\n\n.release-toast-popup.sidebar-right {\n right: 1rem;\n}\n</style>\n","<template>\n <div v-if=\"shouldShow\" class=\"whats-new-popup-container left-4\">\n <div class=\"whats-new-popup\" @click.stop>\n <!-- Close Button -->\n <Button\n class=\"close-button absolute top-2 right-2 z-10 w-8 h-8 p-2 rounded-lg opacity-50\"\n :aria-label=\"$t('g.close')\"\n size=\"icon-sm\"\n variant=\"muted-textonly\"\n @click=\"closePopup\"\n >\n <i class=\"icon-[lucide--x]\" />\n </Button>\n\n <!-- Modal Body -->\n <div class=\"modal-body flex flex-col gap-4 px-0 pt-0 pb-2 flex-1\">\n <!-- Release Content -->\n <div\n class=\"content-text max-h-96 overflow-y-auto\"\n v-html=\"formattedContent\"\n ></div>\n </div>\n\n <!-- Modal Footer -->\n <div\n class=\"modal-footer flex justify-between items-center gap-4 px-4 pb-4\"\n >\n <a\n class=\"learn-more-link flex items-center gap-2 text-sm font-normal py-1\"\n :href=\"changelogUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n @click=\"closePopup\"\n >\n <i class=\"icon-[lucide--external-link]\"></i>\n {{ $t('whatsNewPopup.learnMore') }}\n </a>\n <div class=\"footer-actions flex items-center gap-4\">\n <Button\n class=\"h-8\"\n size=\"sm\"\n variant=\"muted-textonly\"\n @click=\"closePopup\"\n >\n {{ $t('whatsNewPopup.later') }}\n </Button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { default as DOMPurify } from 'dompurify'\nimport { computed, onMounted, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport { formatVersionAnchor } from '@/utils/formatUtil'\nimport { renderMarkdownToHtml } from '@/utils/markdownRendererUtil'\n\nimport type { ReleaseNote } from '../common/releaseService'\nimport { useReleaseStore } from '../common/releaseStore'\n\nconst { buildDocsUrl } = useExternalLink()\nconst releaseStore = useReleaseStore()\nconst { t } = useI18n()\n\n// Define emits\nconst emit = defineEmits<{\n 'whats-new-dismissed': []\n}>()\n\n// Local state for dismissed status\nconst isDismissed = ref(false)\n\n// Get latest release from store\nconst latestRelease = computed<ReleaseNote | null>(() => {\n return releaseStore.recentRelease\n})\n\n// Show popup when on latest version and not dismissed\nconst shouldShow = computed(\n () => releaseStore.shouldShowPopup && !isDismissed.value\n)\n\n// Generate changelog URL with version anchor (language-aware)\nconst changelogUrl = computed(() => {\n const changelogBaseUrl = buildDocsUrl('/changelog', { includeLocale: true })\n if (latestRelease.value?.version) {\n const versionAnchor = formatVersionAnchor(latestRelease.value.version)\n return `${changelogBaseUrl}#${versionAnchor}`\n }\n return changelogBaseUrl\n})\n\nconst formattedContent = computed(() => {\n if (!latestRelease.value?.content) {\n return DOMPurify.sanitize(`<p>${t('whatsNewPopup.noReleaseNotes')}</p>`)\n }\n\n try {\n const markdown = latestRelease.value.content\n\n // Check if content is meaningful (not just whitespace)\n const trimmedContent = markdown.trim()\n if (!trimmedContent || trimmedContent.replace(/\\s+/g, '') === '') {\n return DOMPurify.sanitize(`<p>${t('whatsNewPopup.noReleaseNotes')}</p>`)\n }\n\n // Extract image and remaining content separately\n const imageMatch = markdown.match(/!\\[.*?\\]\\(.*?\\)/)\n const image = imageMatch ? imageMatch[0] : ''\n\n // Remove image from content but keep original title\n const contentWithoutImage = markdown.replace(/!\\[.*?\\]\\(.*?\\)/, '').trim()\n\n // Reorder: image first, then original content\n const reorderedContent = [image, contentWithoutImage]\n .filter(Boolean)\n .join('\\n\\n')\n\n // renderMarkdownToHtml already sanitizes with DOMPurify, so this is safe\n return renderMarkdownToHtml(reorderedContent)\n } catch (error) {\n console.error('Error parsing markdown:', error)\n // Fallback to plain text with line breaks - sanitize the HTML we create\n const fallbackContent = latestRelease.value.content.replace(/\\n/g, '<br>')\n return fallbackContent.trim()\n ? DOMPurify.sanitize(fallbackContent)\n : DOMPurify.sanitize(`<p>${t('whatsNewPopup.noReleaseNotes')}</p>`)\n }\n})\n\nconst show = () => {\n isDismissed.value = false\n}\n\nconst hide = () => {\n isDismissed.value = true\n emit('whats-new-dismissed')\n}\n\nconst closePopup = async () => {\n // Mark \"what's new\" seen when popup is closed\n if (latestRelease.value) {\n await releaseStore.handleWhatsNewSeen(latestRelease.value.version)\n }\n hide()\n}\n\n// Initialize on mount\nonMounted(async () => {\n // Fetch releases if not already loaded\n if (!releaseStore.releases.length) {\n await releaseStore.fetchReleases()\n }\n})\n\n// Expose methods for parent component and tests\ndefineExpose({\n show,\n hide,\n closePopup\n})\n</script>\n\n<style scoped>\n/* Popup container - positioning handled by parent */\n.whats-new-popup-container {\n --whats-new-popup-bottom: 1rem;\n\n position: absolute;\n bottom: var(--whats-new-popup-bottom);\n z-index: 1000;\n pointer-events: auto;\n}\n\n.whats-new-popup {\n background: var(--interface-menu-surface);\n border-radius: 8px;\n max-width: 400px;\n width: 400px;\n border: 1px solid var(--interface-menu-stroke);\n box-shadow: 1px 1px 8px 0 rgb(0 0 0 / 0.2);\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n/* Modal Body */\n.modal-body {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n padding: 0;\n flex: 1;\n}\n\n.modal-header {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.content-text {\n color: var(--text-primary);\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n padding: 0 1rem;\n}\n\n/* Style the markdown content */\n/* Title */\n.content-text :deep(*) {\n box-sizing: border-box;\n}\n\n.content-text :deep(h1) {\n color: var(--text-secondary);\n font-family: Inter, sans-serif;\n font-size: 14px;\n font-weight: 400;\n margin-top: 1rem;\n margin-bottom: 8px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n/* What's new title - targets h2 or strong text after h1 */\n.content-text :deep(h2),\n.content-text :deep(h1 + p strong) {\n color: var(--text-primary);\n font-family: Inter, sans-serif;\n font-size: 14px;\n font-weight: 600;\n margin: 0 0 8px;\n line-height: 1.429;\n}\n\n/* Regular paragraphs - short description */\n.content-text :deep(p) {\n color: var(--text-secondary);\n font-family: Inter, sans-serif;\n margin: 1rem 0;\n}\n\n/* List */\n.content-text :deep(ul),\n.content-text :deep(ol) {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n\n.content-text :deep(ul:first-child),\n.content-text :deep(ol:first-child) {\n margin-top: 0;\n}\n\n.content-text :deep(ul:last-child),\n.content-text :deep(ol:last-child) {\n margin-bottom: 0;\n}\n\n.content-text :deep(li) {\n margin-bottom: 6px;\n position: relative;\n padding-left: 18px;\n color: var(--text-secondary);\n font-family: Inter, sans-serif;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2102;\n}\n\n.content-text :deep(li:last-child) {\n margin-bottom: 0;\n}\n\n.content-text :deep(li::before) {\n content: '';\n position: absolute;\n left: 4px;\n top: 7px;\n width: 6px;\n height: 6px;\n border: 2px solid var(--text-secondary);\n border-radius: 50%;\n background: transparent;\n}\n\n.content-text :deep(li strong) {\n color: var(--text-secondary);\n font-family: Inter, sans-serif;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2102;\n margin-right: 4px;\n}\n\n.content-text :deep(li p) {\n margin: 2px 0 0;\n display: inline;\n}\n\n/* Code styling */\n.content-text :deep(code) {\n background-color: var(--input-surface);\n border: 1px solid var(--interface-menu-stroke);\n border-radius: 4px;\n padding: 2px 6px;\n color: var(--text-primary);\n white-space: nowrap;\n}\n\n.content-text :deep(img) {\n width: 100%;\n height: 200px;\n margin: 0 0 16px;\n object-fit: cover;\n display: block;\n border-radius: 8px;\n}\n\n.content-text :deep(img:first-child) {\n margin: -1rem -1rem 16px;\n width: calc(100% + 2rem);\n border-top-left-radius: 8px;\n border-top-right-radius: 8px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n/* Add border to content when image is present */\n.content-text:has(img:first-child) {\n border-left: 1px solid var(--interface-menu-stroke);\n border-right: 1px solid var(--interface-menu-stroke);\n border-top: 1px solid var(--interface-menu-stroke);\n border-bottom-left-radius: 8px;\n border-bottom-right-radius: 8px;\n margin: -1px;\n margin-bottom: 0;\n}\n\n.content-text :deep(img + h1) {\n margin-top: 0;\n}\n\n/* Secondary headings */\n.content-text :deep(h3) {\n color: var(--text-primary);\n font-family: Inter, sans-serif;\n font-size: 16px;\n font-weight: 600;\n margin: 16px 0 8px;\n line-height: 1.4;\n}\n\n/* Modal Footer */\n.modal-footer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 16px;\n padding: 16px;\n border-top: none;\n}\n\n.footer-actions {\n display: flex;\n align-items: center;\n gap: 16px;\n}\n\n.learn-more-link {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--text-secondary);\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2102;\n text-decoration: none;\n padding: 4px 0;\n}\n\n.learn-more-link:hover {\n color: var(--text-primary);\n}\n\n.learn-more-link i {\n width: 16px;\n height: 16px;\n}\n\n.action-secondary {\n height: 32px;\n padding: 4px 0;\n background: transparent;\n border: none;\n color: var(--text-secondary);\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2102;\n cursor: pointer;\n border-radius: 4px;\n}\n\n.action-secondary:hover {\n color: var(--text-primary);\n}\n\n.action-primary {\n height: 40px;\n padding: 8px 16px;\n background: var(--interface-menu-component-surface-hovered);\n border-radius: 8px;\n border: none;\n color: var(--text-primary);\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2102;\n cursor: pointer;\n}\n\n.action-primary:hover {\n background: var(--button-hover-surface);\n}\n</style>\n","<template>\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n :width=\"size\"\n :height=\"size\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n :class=\"iconClass\"\n >\n <g clip-path=\"url(#clip0_1099_16244)\">\n <path\n d=\"M4.99992 3.00016C4.99992 2.07969 5.74611 1.3335 6.66658 1.3335C7.58706 1.3335 8.33325 2.07969 8.33325 3.00016V4.00016H8.99992C9.9318 4.00016 10.3977 4.00016 10.7653 4.1524C11.2553 4.35539 11.6447 4.74474 11.8477 5.2348C11.9999 5.60234 11.9999 6.06828 11.9999 7.00016H12.9999C13.9204 7.00016 14.6666 7.74635 14.6666 8.66683C14.6666 9.5873 13.9204 10.3335 12.9999 10.3335H11.9999V11.4668C11.9999 12.5869 11.9999 13.147 11.7819 13.5748C11.5902 13.9511 11.2842 14.2571 10.9079 14.4488C10.4801 14.6668 9.92002 14.6668 8.79992 14.6668H8.33325V13.5002C8.33325 12.6717 7.66168 12.0002 6.83325 12.0002C6.00482 12.0002 5.33325 12.6717 5.33325 13.5002V14.6668H4.53325C3.41315 14.6668 2.85309 14.6668 2.42527 14.4488C2.04895 14.2571 1.74299 13.9511 1.55124 13.5748C1.33325 13.147 1.33325 12.5869 1.33325 11.4668V10.3335H2.33325C3.25373 10.3335 3.99992 9.5873 3.99992 8.66683C3.99992 7.74635 3.25373 7.00016 2.33325 7.00016H1.33325C1.33325 6.06828 1.33325 5.60234 1.48549 5.2348C1.68848 4.74474 2.07783 4.35539 2.56789 4.1524C2.93543 4.00016 3.40137 4.00016 4.33325 4.00016H4.99992V3.00016Z\"\n :stroke=\"color\"\n stroke-width=\"1.2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </g>\n <defs>\n <clipPath id=\"clip0_1099_16244\">\n <rect width=\"16\" height=\"16\" fill=\"white\" />\n </clipPath>\n </defs>\n </svg>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n size?: number | string\n color?: string\n class?: string\n}\nconst {\n size = 16,\n color = 'currentColor',\n class: className\n} = defineProps<Props>()\nconst iconClass = computed(() => className || '')\n</script>\n","<template>\n <div\n class=\"help-center-menu flex flex-col items-start gap-1\"\n role=\"menu\"\n :aria-label=\"$t('help.helpCenterMenu')\"\n >\n <!-- Main Menu Items -->\n <div class=\"w-full\">\n <nav class=\"flex w-full flex-col gap-2\" role=\"menubar\">\n <button\n v-for=\"menuItem in menuItems\"\n v-show=\"menuItem.visible !== false\"\n :key=\"menuItem.key\"\n type=\"button\"\n class=\"help-menu-item\"\n :class=\"{ 'more-item': menuItem.key === 'more' }\"\n role=\"menuitem\"\n @click=\"menuItem.action\"\n @mouseenter=\"onMenuItemHover(menuItem.key, $event)\"\n @mouseleave=\"onMenuItemLeave(menuItem.key)\"\n >\n <div class=\"help-menu-icon-container\">\n <div class=\"help-menu-icon\">\n <component\n :is=\"menuItem.icon\"\n v-if=\"typeof menuItem.icon === 'object'\"\n :size=\"16\"\n />\n <i v-else :class=\"menuItem.icon\" />\n </div>\n <div v-if=\"menuItem.showRedDot\" class=\"menu-red-dot\" />\n </div>\n <span class=\"menu-label\">{{ menuItem.label }}</span>\n <i\n v-if=\"menuItem.showExternalIcon\"\n class=\"icon-[lucide--external-link] text-primary w-4 h-4 ml-auto\"\n />\n <i\n v-if=\"menuItem.key === 'more'\"\n class=\"pi pi-chevron-right ml-auto\"\n />\n </button>\n </nav>\n <div\n class=\"flex h-4 flex-col items-center justify-between self-stretch p-2\"\n >\n <div class=\"w-full border-b border-interface-menu-stroke\" />\n </div>\n </div>\n\n <!-- More Submenu -->\n <Teleport to=\"body\">\n <div\n v-if=\"isSubmenuVisible\"\n ref=\"submenuRef\"\n class=\"more-submenu\"\n :style=\"submenuStyle\"\n @mouseenter=\"onSubmenuHover\"\n @mouseleave=\"onSubmenuLeave\"\n >\n <template\n v-for=\"submenuItem in moreMenuItem?.items\"\n :key=\"submenuItem.key\"\n >\n <div\n v-if=\"submenuItem.type === 'divider'\"\n v-show=\"submenuItem.visible !== false\"\n class=\"submenu-divider\"\n />\n <button\n v-else\n v-show=\"submenuItem.visible !== false\"\n type=\"button\"\n class=\"help-menu-item submenu-item\"\n role=\"menuitem\"\n @click=\"submenuItem.action\"\n >\n <span class=\"menu-label\">{{ submenuItem.label }}</span>\n </button>\n </template>\n </div>\n </Teleport>\n\n <!-- What's New Section -->\n <section\n v-if=\"showVersionUpdates\"\n class=\"w-full\"\n data-testid=\"whats-new-section\"\n >\n <h3\n class=\"section-description flex items-center gap-2.5 self-stretch px-8 pt-2 pb-2\"\n >\n {{ $t('helpCenter.whatsNew') }}\n </h3>\n\n <!-- Release Items -->\n <div\n v-if=\"hasReleases\"\n role=\"group\"\n :aria-label=\"$t('help.recentReleases')\"\n >\n <article\n v-for=\"release in releaseStore.recentReleases\"\n :key=\"release.id || release.version\"\n class=\"release-menu-item flex h-12 min-h-6 cursor-pointer items-center gap-2 self-stretch rounded p-2 transition-colors hover:bg-interface-menu-component-surface-hovered\"\n role=\"button\"\n tabindex=\"0\"\n @click=\"onReleaseClick(release)\"\n @keydown.enter=\"onReleaseClick(release)\"\n @keydown.space.prevent=\"onReleaseClick(release)\"\n >\n <i class=\"help-menu-icon icon-[lucide--package]\" aria-hidden=\"true\" />\n <div class=\"release-content\">\n <span class=\"release-title\">\n {{\n $t('g.releaseTitle', {\n package: 'Comfy',\n version: release.version\n })\n }}\n </span>\n <time class=\"release-date\" :datetime=\"release.published_at\">\n <span class=\"normal-state\">\n {{ formatReleaseDate(release.published_at) }}\n </span>\n <span class=\"hover-state\">\n {{ $t('helpCenter.clickToLearnMore') }}\n </span>\n </time>\n </div>\n </article>\n </div>\n\n <!-- Loading State -->\n <div\n v-else-if=\"releaseStore.isLoading\"\n class=\"help-menu-item\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <i class=\"pi pi-spin pi-spinner help-menu-icon\" aria-hidden=\"true\" />\n <span>{{ $t('helpCenter.loadingReleases') }}</span>\n </div>\n\n <!-- No Releases State -->\n <div v-else class=\"help-menu-item\" role=\"status\">\n <i class=\"pi pi-info-circle help-menu-icon\" aria-hidden=\"true\" />\n <span>{{ $t('helpCenter.noRecentReleases') }}</span>\n </div>\n </section>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useToast } from 'primevue/usetoast'\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'\nimport type { CSSProperties, Component } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport PuzzleIcon from '@/components/icons/PuzzleIcon.vue'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport { isCloud } from '@/platform/distribution/types'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport type { ReleaseNote } from '@/platform/updates/common/releaseService'\nimport { useReleaseStore } from '@/platform/updates/common/releaseStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { electronAPI, isElectron } from '@/utils/envUtil'\nimport { formatVersionAnchor } from '@/utils/formatUtil'\nimport { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'\nimport { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'\nimport { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'\nimport { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'\n\n// Types\ninterface MenuItem {\n key: string\n icon?: string | Component\n label?: string\n action?: () => void\n visible?: boolean\n type?: 'item' | 'divider'\n items?: MenuItem[]\n showRedDot?: boolean\n showExternalIcon?: boolean\n}\n\n// Constants\nconst TIME_UNITS = {\n MINUTE: 60 * 1000,\n HOUR: 60 * 60 * 1000,\n DAY: 24 * 60 * 60 * 1000,\n WEEK: 7 * 24 * 60 * 60 * 1000,\n MONTH: 30 * 24 * 60 * 60 * 1000,\n YEAR: 365 * 24 * 60 * 60 * 1000\n} as const\n\nconst SUBMENU_CONFIG = {\n DELAY_MS: 100,\n OFFSET_PX: 8,\n Z_INDEX: 10001\n} as const\n\n// Composables\nconst { t } = useI18n()\nconst toast = useToast()\nconst { staticUrls, buildDocsUrl } = useExternalLink()\nconst releaseStore = useReleaseStore()\nconst commandStore = useCommandStore()\nconst settingStore = useSettingStore()\nconst telemetry = useTelemetry()\n\n// Track when help center was opened\nconst openedAt = ref(Date.now())\n\n// Emits\nconst emit = defineEmits<{\n close: []\n}>()\n\n// State\nconst isSubmenuVisible = ref(false)\nconst submenuRef = ref<HTMLElement | null>(null)\nconst submenuStyle = ref<CSSProperties>({})\nlet hoverTimeout: number | null = null\n\n// Computed\nconst hasReleases = computed(() => releaseStore.releases.length > 0)\nconst showVersionUpdates = computed(() =>\n settingStore.get('Comfy.Notification.ShowVersionUpdates')\n)\n\n// Use conflict acknowledgment state from composable\nconst { shouldShowRedDot: shouldShowManagerRedDot } =\n useConflictAcknowledgment()\nconst { isNewManagerUI } = useManagerState()\n\nconst moreItems = computed<MenuItem[]>(() => {\n const allMoreItems: MenuItem[] = [\n {\n key: 'desktop-guide',\n type: 'item',\n label: t('helpCenter.desktopUserGuide'),\n visible: isElectron(),\n action: () => {\n trackResourceClick('docs', true)\n openExternalLink(\n buildDocsUrl('/installation/desktop', {\n includeLocale: true,\n platform: true\n })\n )\n emit('close')\n }\n },\n {\n key: 'dev-tools',\n type: 'item',\n label: t('helpCenter.openDevTools'),\n visible: isElectron(),\n action: () => {\n openDevTools()\n emit('close')\n }\n },\n {\n key: 'divider-1',\n type: 'divider',\n visible: isElectron()\n },\n {\n key: 'reinstall',\n type: 'item',\n label: t('helpCenter.reinstall'),\n visible: isElectron(),\n action: () => {\n onReinstall()\n emit('close')\n }\n }\n ]\n\n // Filter for visible items only\n return allMoreItems.filter((item) => item.visible !== false)\n})\n\nconst hasVisibleMoreItems = computed(() => {\n return !!moreItems.value.length\n})\n\nconst moreMenuItem = computed(() =>\n menuItems.value.find((item) => item.key === 'more')\n)\n\nconst menuItems = computed<MenuItem[]>(() => {\n const items: MenuItem[] = [\n {\n key: 'feedback',\n type: 'item',\n icon: 'icon-[lucide--clipboard-pen]',\n label: t('helpCenter.feedback'),\n action: () => {\n trackResourceClick('help_feedback', false)\n void commandStore.execute('Comfy.ContactSupport')\n emit('close')\n }\n },\n {\n key: 'help',\n type: 'item',\n icon: 'icon-[lucide--message-circle-question]',\n label: t('helpCenter.help'),\n action: () => {\n trackResourceClick('help_feedback', false)\n void commandStore.execute('Comfy.ContactSupport')\n emit('close')\n }\n },\n {\n key: 'docs',\n type: 'item',\n icon: 'icon-[lucide--book-open]',\n label: t('helpCenter.docs'),\n showExternalIcon: true,\n action: () => {\n trackResourceClick('docs', true)\n const path = isCloud ? '/get_started/cloud' : '/'\n openExternalLink(buildDocsUrl(path, { includeLocale: true }))\n emit('close')\n }\n },\n {\n key: 'discord',\n type: 'item',\n icon: 'pi pi-discord',\n label: 'Discord',\n showExternalIcon: true,\n action: () => {\n trackResourceClick('discord', true)\n openExternalLink(staticUrls.discord)\n emit('close')\n }\n },\n {\n key: 'github',\n type: 'item',\n icon: 'icon-[lucide--github]',\n label: t('helpCenter.github'),\n showExternalIcon: true,\n action: () => {\n trackResourceClick('github', true)\n openExternalLink(staticUrls.github)\n emit('close')\n }\n }\n ]\n\n // Extension manager - only in non-cloud distributions\n if (!isCloud) {\n items.push({\n key: 'manager',\n type: 'item',\n icon: PuzzleIcon,\n label: t('helpCenter.managerExtension'),\n showRedDot: shouldShowManagerRedDot.value,\n action: async () => {\n trackResourceClick('manager', false)\n await useManagerState().openManager({\n initialTab: ManagerTab.All,\n showToastOnLegacyError: false\n })\n emit('close')\n }\n })\n }\n // Update ComfyUI - only for non-desktop, non-cloud with new manager UI\n if (!isElectron() && !isCloud && isNewManagerUI.value) {\n items.push({\n key: 'update-comfyui',\n type: 'item',\n icon: 'icon-[lucide--download]',\n label: t('helpCenter.updateComfyUI'),\n action: () => {\n onUpdateComfyUI()\n emit('close')\n }\n })\n }\n\n items.push({\n key: 'more',\n type: 'item',\n icon: '',\n label: t('helpCenter.more'),\n visible: hasVisibleMoreItems.value,\n action: () => {}, // No action for more item\n items: moreItems.value\n })\n\n return items\n})\n\n// Utility Functions\nconst trackResourceClick = (\n resourceType:\n | 'docs'\n | 'discord'\n | 'github'\n | 'help_feedback'\n | 'manager'\n | 'release_notes',\n isExternal: boolean\n): void => {\n telemetry?.trackHelpResourceClicked({\n resource_type: resourceType,\n is_external: isExternal,\n source: 'help_center'\n })\n}\n\nconst openExternalLink = (url: string): void => {\n window.open(url, '_blank', 'noopener,noreferrer')\n}\n\nconst clearHoverTimeout = (): void => {\n if (hoverTimeout) {\n clearTimeout(hoverTimeout)\n hoverTimeout = null\n }\n}\n\nconst calculateSubmenuPosition = (button: HTMLElement): CSSProperties => {\n const rect = button.getBoundingClientRect()\n const submenuWidth = 210 // Width defined in CSS\n\n // Get actual submenu height if available, otherwise estimate based on visible item count\n const visibleItemCount =\n moreMenuItem.value?.items?.filter((item) => item.visible !== false)\n .length || 0\n const estimatedHeight = visibleItemCount * 48 + 16 // ~48px per item + padding\n const submenuHeight = submenuRef.value?.offsetHeight || estimatedHeight\n\n // Get viewport dimensions\n const viewportWidth = window.innerWidth\n const viewportHeight = window.innerHeight\n\n // Calculate basic position (aligned with button)\n let top = rect.top\n let left = rect.right + SUBMENU_CONFIG.OFFSET_PX\n\n // Check if submenu would overflow viewport on the right\n if (left + submenuWidth > viewportWidth) {\n // Position submenu to the left of the button instead\n left = rect.left - submenuWidth - SUBMENU_CONFIG.OFFSET_PX\n }\n\n // Check if submenu would overflow viewport at the bottom\n if (top + submenuHeight > viewportHeight) {\n // Position submenu above the button, aligned to bottom\n top = Math.max(\n SUBMENU_CONFIG.OFFSET_PX, // Minimum distance from top of viewport\n rect.bottom - submenuHeight\n )\n }\n\n // Ensure submenu doesn't go above viewport\n if (top < SUBMENU_CONFIG.OFFSET_PX) {\n top = SUBMENU_CONFIG.OFFSET_PX\n }\n\n top -= 8\n\n return {\n position: 'fixed',\n top: `${top}px`,\n left: `${left}px`,\n zIndex: SUBMENU_CONFIG.Z_INDEX\n }\n}\n\nconst formatReleaseDate = (dateString?: string): string => {\n if (!dateString) return 'date'\n\n const date = new Date(dateString)\n const now = new Date()\n const diffTime = Math.abs(now.getTime() - date.getTime())\n\n const timeUnits = [\n { unit: TIME_UNITS.YEAR, key: 'yearsAgo' },\n { unit: TIME_UNITS.MONTH, key: 'monthsAgo' },\n { unit: TIME_UNITS.WEEK, key: 'weeksAgo' },\n { unit: TIME_UNITS.DAY, key: 'daysAgo' },\n { unit: TIME_UNITS.HOUR, key: 'hoursAgo' },\n { unit: TIME_UNITS.MINUTE, key: 'minutesAgo' }\n ]\n\n for (const { unit, key } of timeUnits) {\n const value = Math.floor(diffTime / unit)\n if (value > 0) {\n return t(`g.relativeTime.${key}`, { count: value })\n }\n }\n\n return t('g.relativeTime.now')\n}\n\n// Event Handlers\nconst onMenuItemHover = async (\n key: string,\n event: MouseEvent\n): Promise<void> => {\n if (key !== 'more' || !moreMenuItem.value?.items) return\n\n // Don't show submenu if all items are hidden\n const hasVisibleItems = moreMenuItem.value.items.some(\n (item) => item.visible !== false\n )\n if (!hasVisibleItems) return\n\n clearHoverTimeout()\n\n const moreButton = event.currentTarget as HTMLElement\n\n // Calculate initial position before showing submenu\n submenuStyle.value = calculateSubmenuPosition(moreButton)\n\n // Show submenu with correct position\n isSubmenuVisible.value = true\n\n // After submenu is rendered, refine position if needed\n await nextTick()\n if (submenuRef.value) {\n submenuStyle.value = calculateSubmenuPosition(moreButton)\n }\n}\n\nconst onMenuItemLeave = (key: string): void => {\n if (key !== 'more') return\n\n hoverTimeout = window.setTimeout(() => {\n isSubmenuVisible.value = false\n }, SUBMENU_CONFIG.DELAY_MS)\n}\n\nconst onSubmenuHover = (): void => {\n clearHoverTimeout()\n}\n\nconst onSubmenuLeave = (): void => {\n isSubmenuVisible.value = false\n}\n\nconst openDevTools = (): void => {\n if (isElectron()) {\n electronAPI().openDevTools()\n }\n}\n\nconst onReinstall = (): void => {\n if (isElectron()) {\n void electronAPI().reinstall()\n }\n}\n\nconst onUpdateComfyUI = async (): Promise<void> => {\n const { updateComfyUI, rebootComfyUI, error } = useComfyManagerService()\n\n toast.add({\n severity: 'info',\n summary: t('helpCenter.updateComfyUIStarted'),\n detail: t('helpCenter.updateComfyUIStartedDetail'),\n life: 3000\n })\n\n try {\n const result = await updateComfyUI({ is_stable: true })\n\n if (result === null || error.value) {\n toast.add({\n severity: 'error',\n summary: t('g.error'),\n detail: error.value || t('helpCenter.updateComfyUIFailed'),\n life: 5000\n })\n return\n }\n\n toast.add({\n severity: 'success',\n summary: t('helpCenter.updateComfyUISuccess'),\n detail: t('helpCenter.updateComfyUISuccessDetail'),\n life: 3000\n })\n\n await rebootComfyUI()\n } catch (err) {\n toast.add({\n severity: 'error',\n summary: t('g.error'),\n detail: err instanceof Error ? err.message : t('g.unknownError'),\n life: 5000\n })\n }\n}\n\nconst onReleaseClick = (release: ReleaseNote): void => {\n trackResourceClick('release_notes', true)\n void releaseStore.handleShowChangelog(release.version)\n const versionAnchor = formatVersionAnchor(release.version)\n const changelogUrl = `${buildDocsUrl('/changelog', { includeLocale: true })}#${versionAnchor}`\n openExternalLink(changelogUrl)\n emit('close')\n}\n\n// Lifecycle\nonMounted(async () => {\n telemetry?.trackHelpCenterOpened({ source: 'sidebar' })\n if (!hasReleases.value) {\n await releaseStore.fetchReleases()\n }\n})\n\nonBeforeUnmount(() => {\n const timeSpentSeconds = Math.round((Date.now() - openedAt.value) / 1000)\n telemetry?.trackHelpCenterClosed({ time_spent_seconds: timeSpentSeconds })\n})\n</script>\n\n<style scoped>\n.help-center-menu {\n width: 256px;\n max-height: 500px;\n overflow-y: auto;\n background: var(--interface-menu-surface);\n border-radius: 8px;\n box-shadow: 0 2px 12px 0 rgb(0 0 0 / 0.1);\n border: 1px solid var(--interface-menu-stroke);\n padding: 12px 8px;\n position: relative;\n}\n\n.help-menu-item {\n display: flex;\n align-items: center;\n width: 100%;\n height: 32px;\n min-height: 24px;\n padding: 8px;\n gap: 8px;\n background: transparent;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.2s;\n font-size: 0.9rem;\n color: var(--text-primary);\n text-align: left;\n}\n\n.help-menu-item:hover {\n background-color: var(--interface-menu-component-surface-hovered);\n}\n\n.help-menu-item:focus,\n.help-menu-item:focus-visible {\n outline: none;\n box-shadow: none;\n}\n\n.help-menu-icon-container {\n position: relative;\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n}\n\n.help-menu-icon {\n width: 16px;\n height: 16px;\n font-size: 16px;\n color: var(--text-primary);\n display: flex;\n justify-content: center;\n align-items: center;\n flex-shrink: 0;\n}\n\n.help-menu-icon svg {\n width: 16px;\n height: 16px;\n color: var(--text-primary);\n}\n\n.menu-red-dot {\n position: absolute;\n top: -2px;\n right: -2px;\n width: 8px;\n height: 8px;\n background: #ff3b30;\n border-radius: 50%;\n border: 1.5px solid var(--p-content-background);\n z-index: 1;\n}\n\n.menu-label {\n flex: 1;\n}\n\n.more-item {\n justify-content: space-between;\n}\n\n.section-description {\n color: var(--text-secondary);\n font-family: var(--font-inter);\n font-size: 12px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n margin: 0;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.release-menu-item {\n position: relative;\n}\n\n.release-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n}\n\n.release-title {\n font-size: 0.9rem;\n line-height: 1.2;\n font-weight: 500;\n color: var(--text-primary);\n}\n\n.release-date {\n height: 16px;\n color: var(--text-secondary);\n font-family: var(--font-inter);\n font-size: 12px;\n font-style: normal;\n font-weight: 400;\n line-height: normal;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n}\n\n.release-date .hover-state {\n display: none;\n}\n\n.release-menu-item:hover .release-date .normal-state,\n.release-menu-item:focus-within .release-date .normal-state {\n display: none;\n}\n\n.release-menu-item:hover .release-date .hover-state,\n.release-menu-item:focus-within .release-date .hover-state {\n display: inline;\n}\n\n/* Submenu Styles */\n.more-submenu {\n width: 210px;\n padding: 12px 8px;\n background: var(--interface-menu-surface);\n border-radius: 8px;\n border: 1px solid var(--interface-menu-stroke);\n box-shadow: 0 2px 12px 0 rgb(0 0 0 / 0.1);\n overflow: hidden;\n transition: opacity 0.15s ease-out;\n}\n\n.submenu-item {\n padding: 8px;\n height: 32px;\n min-height: 24px;\n border-radius: 4px;\n color: var(--text-primary);\n font-size: 0.9rem;\n font-weight: inherit;\n line-height: inherit;\n}\n\n.submenu-item:hover {\n background-color: var(--interface-menu-component-surface-hovered);\n}\n\n.submenu-item:focus,\n.submenu-item:focus-visible {\n outline: none;\n box-shadow: none;\n}\n\n.submenu-divider {\n height: 1px;\n background: var(--interface-menu-stroke);\n margin: 4px 0;\n}\n\n/* Scrollbar Styling */\n.help-center-menu::-webkit-scrollbar {\n width: 6px;\n}\n\n.help-center-menu::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.help-center-menu::-webkit-scrollbar-thumb {\n background: var(--interface-menu-stroke);\n border-radius: 3px;\n}\n\n.help-center-menu::-webkit-scrollbar-thumb:hover {\n background: var(--text-secondary);\n}\n\n/* Reduced Motion */\n@media (prefers-reduced-motion: reduce) {\n .help-menu-item {\n transition: none;\n }\n}\n</style>\n","<template>\n <!-- Help Center Popup positioned within canvas area -->\n <Teleport to=\"#graph-canvas-container\">\n <div\n v-if=\"isHelpCenterVisible\"\n class=\"help-center-popup\"\n :class=\"{\n 'sidebar-left':\n triggerLocation === 'sidebar' && sidebarLocation === 'left',\n 'sidebar-right':\n triggerLocation === 'sidebar' && sidebarLocation === 'right',\n 'topbar-right': triggerLocation === 'topbar',\n 'small-sidebar': isSmall\n }\"\n >\n <HelpCenterMenuContent @close=\"closeHelpCenter\" />\n </div>\n </Teleport>\n\n <!-- Release Notification Toast positioned within canvas area -->\n <Teleport to=\"#graph-canvas-container\">\n <ReleaseNotificationToast\n :class=\"{\n 'sidebar-left': sidebarLocation === 'left',\n 'sidebar-right': sidebarLocation === 'right',\n 'small-sidebar': isSmall\n }\"\n />\n </Teleport>\n\n <!-- WhatsNew Popup positioned within canvas area -->\n <Teleport to=\"#graph-canvas-container\">\n <WhatsNewPopup\n :class=\"{\n 'sidebar-left': sidebarLocation === 'left',\n 'sidebar-right': sidebarLocation === 'right',\n 'small-sidebar': isSmall\n }\"\n @whats-new-dismissed=\"handleWhatsNewDismissed\"\n />\n </Teleport>\n\n <!-- Backdrop to close popup when clicking outside -->\n <Teleport to=\"body\">\n <div\n v-if=\"isHelpCenterVisible\"\n class=\"help-center-backdrop\"\n @click=\"closeHelpCenter\"\n />\n </Teleport>\n</template>\n\n<script setup lang=\"ts\">\nimport { useHelpCenter } from '@/composables/useHelpCenter'\nimport ReleaseNotificationToast from '@/platform/updates/components/ReleaseNotificationToast.vue'\nimport WhatsNewPopup from '@/platform/updates/components/WhatsNewPopup.vue'\n\nimport HelpCenterMenuContent from './HelpCenterMenuContent.vue'\n\nconst { isSmall = false } = defineProps<{\n isSmall?: boolean\n}>()\n\nconst {\n isHelpCenterVisible,\n triggerLocation,\n sidebarLocation,\n closeHelpCenter,\n handleWhatsNewDismissed\n} = useHelpCenter()\n</script>\n\n<style scoped>\n.help-center-backdrop {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 9999;\n background: transparent;\n}\n\n.help-center-popup {\n position: absolute;\n bottom: 1rem;\n z-index: 10000;\n animation: slideInUp 0.2s ease-out;\n pointer-events: auto;\n}\n\n.help-center-popup.sidebar-left {\n left: 1rem;\n}\n\n.help-center-popup.sidebar-left.small-sidebar {\n left: 1rem;\n}\n\n.help-center-popup.sidebar-right {\n right: 1rem;\n}\n\n.help-center-popup.topbar-right {\n top: 2rem;\n right: 1rem;\n bottom: auto;\n animation: slideInDown 0.2s ease-out;\n}\n\n@keyframes slideInDown {\n from {\n opacity: 0;\n transform: translateY(-20px);\n }\n\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes slideInUp {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n</style>\n","<template>\n <svg\n :class=\"iconClass\"\n :width=\"size\"\n :height=\"size\"\n viewBox=\"0 0 18 18\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M14.8193 0.600586C15.1248 0.600586 15.3296 0.70893 15.459 0.881836C15.5914 1.05888 15.6471 1.33774 15.5527 1.66895L14.8037 4.30176C14.7063 4.64386 14.4729 4.97024 14.1641 5.21191C13.8544 5.45415 13.496 5.58984 13.1699 5.58984H13.1689L9.5791 5.59668H7.90625C7.52654 5.59668 7.19496 5.84986 7.09082 6.21289L5.69434 11.0889C5.63007 11.3133 5.66134 11.5534 5.77734 11.7529L5.83203 11.8359C5.99177 12.0491 6.24252 12.1758 6.50977 12.1758H6.51074L8.88281 12.1709H11.4971C11.7643 12.171 11.9541 12.254 12.084 12.3906L12.1357 12.4521C12.2685 12.6295 12.3249 12.9089 12.2305 13.2402L11.4805 15.8721C11.383 16.2144 11.1498 16.5415 10.8408 16.7832C10.5314 17.0252 10.1736 17.161 9.84766 17.1611H9.84668L6.25684 17.168H3.64258C3.33762 17.1679 3.13349 17.0588 3.00391 16.8857C2.87135 16.7087 2.81482 16.43 2.90918 16.0986L3.39551 14.3887C3.46841 14.1327 3.41794 13.8576 3.25879 13.6445V13.6436C3.09901 13.4303 2.84745 13.3037 2.58008 13.3037H1.18066C0.875088 13.3037 0.670398 13.1953 0.541016 13.0225C0.408483 12.8451 0.351891 12.5655 0.446289 12.2344L2.11914 6.38965L2.30371 5.74707V5.74609C2.40139 5.40341 2.63456 5.07671 2.94336 4.83496C3.25302 4.59258 3.61143 4.45705 3.9375 4.45703H5.6123C5.94484 4.45703 6.24083 4.26316 6.37891 3.9707L6.42773 3.83984L6.98145 1.89551C7.07894 1.55317 7.31212 1.22614 7.62109 0.984375C7.93074 0.742127 8.2892 0.606445 8.61523 0.606445H8.61621L12.1982 0.600586H14.8193Z\"\n v-bind=\"attributes\"\n />\n </svg>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n size?: number | string\n color?: string\n class?: string\n mode?: 'outline' | 'fill'\n}\nconst {\n size = 16,\n color = 'currentColor',\n mode = 'outline',\n class: className\n} = defineProps<Props>()\nconst iconClass = computed(() => className || '')\nconst attributes = computed(() => ({\n stroke: mode === 'outline' ? color : undefined,\n strokeWidth: mode === 'outline' ? 1 : undefined,\n fill: mode === 'fill' ? color : 'none'\n}))\n</script>\n","<template>\n <div :class=\"containerClasses\">\n <slot></slot>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { fullHeight = true } = defineProps<{\n fullHeight?: boolean\n}>()\n\nconst containerClasses = computed(() =>\n cn('flex-1 w-full', fullHeight && 'h-full')\n)\n</script>\n","<template>\n <div :class=\"containerClasses\">\n <slot name=\"top\"></slot>\n <slot name=\"bottom\"></slot>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nconst {\n size = 'regular',\n variant = 'default',\n rounded = 'md',\n customAspectRatio,\n hasBorder = true,\n hasBackground = true,\n hasShadow = true,\n hasCursor = true,\n class: customClass = ''\n} = defineProps<{\n size?: 'mini' | 'compact' | 'regular' | 'portrait' | 'tall'\n variant?: 'default' | 'ghost' | 'outline'\n rounded?: 'none' | 'md' | 'lg' | 'xl'\n customAspectRatio?: string\n hasBorder?: boolean\n hasBackground?: boolean\n hasShadow?: boolean\n hasCursor?: boolean\n class?: string\n}>()\n\n// Base structure classes\nconst structureClasses = 'flex flex-col overflow-hidden'\n\n// Rounded corners\nconst roundedClasses = {\n none: 'rounded-none',\n md: 'rounded',\n lg: 'rounded-lg',\n xl: 'rounded-xl'\n} as const\n\nconst containerClasses = computed(() => {\n // Variant styles\n const variantClasses = {\n default: cn(\n hasBackground && 'bg-modal-card-background',\n hasBorder && 'border border-border-default',\n hasShadow && 'shadow-sm',\n hasCursor && 'cursor-pointer'\n ),\n ghost: cn(\n hasCursor && 'cursor-pointer',\n 'p-2 transition-colors duration-200'\n ),\n outline: cn(\n hasBorder && 'border-2 border-border-subtle',\n hasCursor && 'cursor-pointer',\n 'hover:border-border-subtle/50 transition-colors'\n )\n }\n\n // Size/aspect ratio\n const aspectRatio = customAspectRatio\n ? `aspect-[${customAspectRatio}]`\n : {\n mini: 'aspect-100/120',\n compact: 'aspect-240/311',\n regular: 'aspect-256/308',\n portrait: 'aspect-256/325',\n tall: 'aspect-256/353'\n }[size]\n\n return cn(\n structureClasses,\n roundedClasses[rounded],\n variantClasses[variant],\n aspectRatio,\n customClass\n )\n})\n</script>\n","<template>\n <div :class=\"topStyle\">\n <slot class=\"absolute top-0 left-0 h-full w-full\"></slot>\n\n <div v-if=\"slots['top-left']\" :class=\"slotClasses['top-left']\">\n <slot name=\"top-left\"></slot>\n </div>\n\n <div v-if=\"slots['top-right']\" :class=\"slotClasses['top-right']\">\n <slot name=\"top-right\"></slot>\n </div>\n\n <div v-if=\"slots['center-left']\" :class=\"slotClasses['center-left']\">\n <slot name=\"center-left\"></slot>\n </div>\n\n <div v-if=\"slots['center-right']\" :class=\"slotClasses['center-right']\">\n <slot name=\"center-right\"></slot>\n </div>\n\n <div v-if=\"slots['bottom-left']\" :class=\"slotClasses['bottom-left']\">\n <slot name=\"bottom-left\"></slot>\n </div>\n\n <div v-if=\"slots['bottom-right']\" :class=\"slotClasses['bottom-right']\">\n <slot name=\"bottom-right\"></slot>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nconst slots = useSlots()\n\nconst {\n ratio = 'square',\n topLeftClass,\n topRightClass,\n centerLeftClass,\n centerRightClass,\n bottomLeftClass,\n bottomRightClass\n} = defineProps<{\n ratio?: 'square' | 'landscape'\n topLeftClass?: string\n topRightClass?: string\n centerLeftClass?: string\n centerRightClass?: string\n bottomLeftClass?: string\n bottomRightClass?: string\n}>()\n\nconst topStyle = computed(() => {\n const baseClasses = 'relative p-0 overflow-hidden'\n\n const ratioClasses = {\n square: 'aspect-square',\n landscape: 'aspect-48/27'\n }\n\n return `${baseClasses} ${ratioClasses[ratio]}`\n})\n\n// Get default classes for each slot position\nconst defaultSlotClasses = {\n 'top-left': 'absolute top-2 left-2 flex flex-wrap justify-start gap-2',\n 'top-right': 'absolute top-2 right-2 flex flex-wrap justify-end gap-2',\n 'center-left':\n 'absolute top-1/2 left-2 flex -translate-y-1/2 flex-wrap justify-start gap-2',\n 'center-right':\n 'absolute top-1/2 right-2 flex -translate-y-1/2 flex-wrap justify-end gap-2',\n 'bottom-left': 'absolute bottom-2 left-2 flex flex-wrap justify-start gap-2',\n 'bottom-right': 'absolute right-2 bottom-2 flex flex-wrap justify-end gap-2'\n}\n\n// Compute all slot classes once and cache them\nconst slotClasses = computed(() => ({\n 'top-left': cn(defaultSlotClasses['top-left'], topLeftClass),\n 'top-right': cn(defaultSlotClasses['top-right'], topRightClass),\n 'center-left': cn(defaultSlotClasses['center-left'], centerLeftClass),\n 'center-right': cn(defaultSlotClasses['center-right'], centerRightClass),\n 'bottom-left': cn(defaultSlotClasses['bottom-left'], bottomLeftClass),\n 'bottom-right': cn(defaultSlotClasses['bottom-right'], bottomRightClass)\n}))\n</script>\n","<template>\n <div :class=\"chipClasses\">\n <slot name=\"icon\"></slot>\n <span>{{ label }}</span>\n </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { label, variant = 'dark' } = defineProps<{\n label: string\n variant?: 'dark' | 'light' | 'gray'\n}>()\n\nconst baseClasses =\n 'inline-flex shrink-0 items-center justify-center gap-1 rounded px-2 py-1 text-xs font-bold'\n\nconst variantStyles = {\n dark: 'bg-zinc-500/40 text-white/90',\n light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground'),\n gray: cn(\n 'backdrop-blur-[2px] bg-modal-card-tag-background text-base-foreground'\n )\n}\n\nconst chipClasses = computed(() => {\n return cn(baseClasses, variantStyles[variant])\n})\n</script>\n","<template>\n <div\n class=\"relative aspect-square w-full overflow-hidden rounded-t-lg select-none\"\n >\n <div\n v-if=\"!error\"\n ref=\"contentRef\"\n class=\"h-full w-full transform-gpu transition-transform duration-1000 ease-out\"\n :style=\"\n isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined\n \"\n >\n <slot />\n </div>\n <div v-else class=\"flex h-full w-full items-center justify-center\">\n <img\n src=\"/assets/images/default-template.png\"\n draggable=\"false\"\n class=\"h-full w-full transform-gpu object-cover transition-transform duration-300 ease-out\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport { onMounted, ref } from 'vue'\n\nconst error = ref(false)\nconst contentRef = ref<HTMLElement | null>(null)\n\nconst { hoverZoom = 4 } = defineProps<{\n hoverZoom?: number\n isHovered?: boolean\n}>()\n\nonMounted(() => {\n const images = Array.from(contentRef.value?.getElementsByTagName('img') ?? [])\n images.forEach((img) => {\n useEventListener(img, 'error', () => {\n error.value = true\n })\n })\n})\n</script>\n<style scoped>\nimg {\n transition: transform 1s cubic-bezier(0.2, 0, 0.4, 1);\n}\n</style>\n","<template>\n <BaseThumbnail :is-hovered=\"isHovered\">\n <LazyImage\n :src=\"baseImageSrc\"\n :alt=\"alt\"\n :image-class=\"\n isVideoType\n ? 'w-full h-full object-cover'\n : 'max-w-full max-h-64 object-contain'\n \"\n />\n <div ref=\"containerRef\" class=\"absolute inset-0\">\n <LazyImage\n :src=\"overlayImageSrc\"\n :alt=\"alt\"\n :image-class=\"\n isVideoType\n ? 'w-full h-full object-cover'\n : 'max-w-full max-h-64 object-contain'\n \"\n :image-style=\"{\n clipPath: `inset(0 ${100 - sliderPosition}% 0 0)`\n }\"\n />\n <div\n class=\"pointer-events-none absolute inset-y-0 z-10 w-0.5 bg-white/30 backdrop-blur-sm\"\n :style=\"{\n left: `${sliderPosition}%`\n }\"\n />\n </div>\n </BaseThumbnail>\n</template>\n\n<script setup lang=\"ts\">\nimport { useMouseInElement } from '@vueuse/core'\nimport { ref, watch } from 'vue'\n\nimport LazyImage from '@/components/common/LazyImage.vue'\nimport BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'\n\nconst SLIDER_START_POSITION = 50\n\nconst { baseImageSrc, overlayImageSrc, isHovered, isVideo } = defineProps<{\n baseImageSrc: string\n overlayImageSrc: string\n alt: string\n isHovered?: boolean\n isVideo?: boolean\n}>()\n\nconst isVideoType =\n isVideo ||\n baseImageSrc?.toLowerCase().endsWith('.webp') ||\n overlayImageSrc?.toLowerCase().endsWith('.webp') ||\n false\n\nconst sliderPosition = ref(SLIDER_START_POSITION)\nconst containerRef = ref<HTMLElement | null>(null)\n\nconst { elementX, elementWidth, isOutside } = useMouseInElement(containerRef)\n\n// Update slider position based on mouse position when hovered\nwatch(\n [() => isHovered, elementX, elementWidth, isOutside],\n ([isHovered, x, width, outside]) => {\n if (!isHovered) return\n if (!outside) {\n sliderPosition.value = (x / width) * 100\n }\n }\n)\n</script>\n","<template>\n <BaseThumbnail :hover-zoom=\"hoverZoom\" :is-hovered=\"isHovered\">\n <LazyImage\n :src=\"src\"\n :alt=\"alt\"\n :image-class=\"[\n 'transform-gpu transition-transform duration-300 ease-out',\n isVideoType\n ? 'w-full h-full object-cover'\n : 'max-w-full max-h-64 object-contain'\n ]\"\n :image-style=\"\n isHovered ? { transform: `scale(${1 + hoverZoom / 100})` } : undefined\n \"\n />\n </BaseThumbnail>\n</template>\n\n<script setup lang=\"ts\">\nimport LazyImage from '@/components/common/LazyImage.vue'\nimport BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'\n\nconst { src, isVideo } = defineProps<{\n src: string\n alt: string\n hoverZoom: number\n isHovered?: boolean\n isVideo?: boolean\n}>()\n\nconst isVideoType = isVideo ?? (src?.toLowerCase().endsWith('.webp') || false)\n</script>\n","<template>\n <BaseThumbnail :is-hovered=\"isHovered\">\n <div class=\"relative h-full w-full\">\n <div class=\"absolute inset-0\">\n <LazyImage\n :src=\"baseImageSrc\"\n :alt=\"alt\"\n :image-class=\"baseImageClass\"\n />\n </div>\n <div class=\"absolute inset-0 z-10\">\n <LazyImage\n :src=\"overlayImageSrc\"\n :alt=\"alt\"\n :image-class=\"overlayImageClass\"\n />\n </div>\n </div>\n </BaseThumbnail>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport LazyImage from '@/components/common/LazyImage.vue'\nimport BaseThumbnail from '@/components/templates/thumbnails/BaseThumbnail.vue'\n\nconst { baseImageSrc, overlayImageSrc, isVideo, isHovered } = defineProps<{\n baseImageSrc: string\n overlayImageSrc: string\n alt: string\n isHovered: boolean\n isVideo?: boolean\n}>()\n\nconst isVideoType =\n isVideo ||\n baseImageSrc?.toLowerCase().endsWith('.webp') ||\n overlayImageSrc?.toLowerCase().endsWith('.webp') ||\n false\n\nconst baseImageClass = computed(() => {\n const sizeClasses = isVideoType\n ? 'size-full object-cover'\n : 'size-full object-contain'\n return sizeClasses\n})\n\nconst overlayImageClass = computed(() => {\n const baseClasses = 'size-full transition-opacity duration-300'\n const sizeClasses = isVideoType ? 'object-cover' : 'object-contain'\n const opacityClasses = isHovered ? 'opacity-100' : 'opacity-0'\n return `${baseClasses} ${sizeClasses} ${opacityClasses}`\n})\n</script>\n","import { computed, ref, shallowRef, watch } from 'vue'\nimport type { Ref } from 'vue'\n\ninterface LazyPaginationOptions {\n itemsPerPage?: number\n initialPage?: number\n}\n\nexport function useLazyPagination<T>(\n items: Ref<T[]> | T[],\n options: LazyPaginationOptions = {}\n) {\n const { itemsPerPage = 12, initialPage = 1 } = options\n\n const currentPage = ref(initialPage)\n const isLoading = ref(false)\n const loadedPages = shallowRef(new Set<number>([]))\n\n // Get reactive items array\n const itemsArray = computed(() => {\n const itemData = 'value' in items ? items.value : items\n return Array.isArray(itemData) ? itemData : []\n })\n\n // Simulate pagination by slicing the items\n const paginatedItems = computed(() => {\n const itemData = itemsArray.value\n if (itemData.length === 0) {\n return []\n }\n\n const loadedPageNumbers = Array.from(loadedPages.value).sort(\n (a, b) => a - b\n )\n const maxLoadedPage = Math.max(...loadedPageNumbers, 0)\n const endIndex = maxLoadedPage * itemsPerPage\n return itemData.slice(0, endIndex)\n })\n\n const hasMoreItems = computed(() => {\n const itemData = itemsArray.value\n if (itemData.length === 0) {\n return false\n }\n\n const loadedPagesArray = Array.from(loadedPages.value)\n const maxLoadedPage = Math.max(...loadedPagesArray, 0)\n return maxLoadedPage * itemsPerPage < itemData.length\n })\n\n const totalPages = computed(() => {\n const itemData = itemsArray.value\n if (itemData.length === 0) {\n return 0\n }\n return Math.ceil(itemData.length / itemsPerPage)\n })\n\n const loadNextPage = async () => {\n if (isLoading.value || !hasMoreItems.value) return\n\n isLoading.value = true\n const loadedPagesArray = Array.from(loadedPages.value)\n const nextPage = Math.max(...loadedPagesArray, 0) + 1\n\n // Simulate network delay\n // await new Promise((resolve) => setTimeout(resolve, 5000))\n\n const newLoadedPages = new Set(loadedPages.value)\n newLoadedPages.add(nextPage)\n loadedPages.value = newLoadedPages\n currentPage.value = nextPage\n isLoading.value = false\n }\n\n // Initialize with first page\n watch(\n () => itemsArray.value.length,\n (length) => {\n if (length > 0 && loadedPages.value.size === 0) {\n loadedPages.value = new Set([1])\n }\n },\n { immediate: true }\n )\n\n const reset = () => {\n currentPage.value = initialPage\n loadedPages.value = new Set([])\n isLoading.value = false\n\n // Immediately load first page if we have items\n const itemData = itemsArray.value\n if (itemData.length > 0) {\n loadedPages.value = new Set([1])\n }\n }\n\n return {\n paginatedItems,\n isLoading,\n hasMoreItems,\n currentPage,\n totalPages,\n loadNextPage,\n reset\n }\n}\n","/**\n * Store for template ranking scores.\n * Loads pre-computed usage scores from static JSON.\n * Internal ranks come from template.searchRank in index.json.\n * See docs/TEMPLATE_RANKING.md for details.\n */\n\nimport { defineStore } from 'pinia'\nimport { ref } from 'vue'\n\nexport const useTemplateRankingStore = defineStore('templateRanking', () => {\n const largestUsageScore = ref<number>()\n\n const normalizeUsageScore = (usage: number): number => {\n return usage / (largestUsageScore.value ?? usage)\n }\n\n /**\n * Compute freshness score based on template date.\n * Returns 1.0 for brand new, decays to 0.1 over ~6 months.\n */\n const computeFreshness = (dateStr: string | undefined): number => {\n if (!dateStr) return 0.5 // Default for templates without dates\n\n const date = new Date(dateStr)\n if (isNaN(date.getTime())) return 0.5\n\n const daysSinceAdded = (Date.now() - date.getTime()) / (1000 * 60 * 60 * 24)\n return Math.max(0.1, 1.0 / (1 + daysSinceAdded / 90))\n }\n\n /**\n * Compute composite score for \"default\" sort.\n * Formula: usage × 0.5 + internal × 0.3 + freshness × 0.2\n */\n const computeDefaultScore = (\n dateStr: string | undefined,\n searchRank: number | undefined,\n usage: number = 0\n ): number => {\n const internal = (searchRank ?? 5) / 10 // Normalize 1-10 to 0-1\n const freshness = computeFreshness(dateStr)\n\n return normalizeUsageScore(usage) * 0.5 + internal * 0.3 + freshness * 0.2\n }\n\n /**\n * Compute composite score for \"popular\" sort.\n * Formula: usage × 0.9 + freshness × 0.1\n */\n const computePopularScore = (\n dateStr: string | undefined,\n usage: number = 0\n ): number => {\n const freshness = computeFreshness(dateStr)\n\n return normalizeUsageScore(usage) * 0.9 + freshness * 0.1\n }\n\n return {\n largestUsageScore,\n computeFreshness,\n computeDefaultScore,\n computePopularScore\n }\n})\n","import { refDebounced, watchDebounced } from '@vueuse/core'\nimport Fuse from 'fuse.js'\nimport type { IFuseOptions } from 'fuse.js'\nimport { computed, ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport type { TemplateInfo } from '@/platform/workflow/templates/types/template'\nimport { useTemplateRankingStore } from '@/stores/templateRankingStore'\nimport { debounce } from 'es-toolkit/compat'\nimport { api } from '@/scripts/api'\n\n// Fuse.js configuration for fuzzy search\nconst defaultFuseOptions: IFuseOptions<TemplateInfo> = {\n keys: [\n { name: 'name', weight: 0.3 },\n { name: 'title', weight: 0.3 },\n { name: 'description', weight: 0.1 },\n { name: 'tags', weight: 0.2 },\n { name: 'models', weight: 0.3 }\n ],\n threshold: 0.33,\n includeScore: true,\n includeMatches: true\n}\n\nexport function useTemplateFiltering(\n templates: Ref<TemplateInfo[]> | TemplateInfo[]\n) {\n const settingStore = useSettingStore()\n const rankingStore = useTemplateRankingStore()\n\n const searchQuery = ref('')\n const selectedModels = ref<string[]>(\n settingStore.get('Comfy.Templates.SelectedModels')\n )\n const selectedUseCases = ref<string[]>(\n settingStore.get('Comfy.Templates.SelectedUseCases')\n )\n const selectedRunsOn = ref<string[]>(\n settingStore.get('Comfy.Templates.SelectedRunsOn')\n )\n const sortBy = ref<\n | 'default'\n | 'recommended'\n | 'popular'\n | 'alphabetical'\n | 'newest'\n | 'vram-low-to-high'\n | 'model-size-low-to-high'\n >(settingStore.get('Comfy.Templates.SortBy'))\n\n const fuseOptions = ref<IFuseOptions<TemplateInfo>>(defaultFuseOptions)\n\n const templatesArray = computed(() => {\n const templateData = 'value' in templates ? templates.value : templates\n return Array.isArray(templateData) ? templateData : []\n })\n\n const fuse = computed(() => new Fuse(templatesArray.value, fuseOptions.value))\n\n const availableModels = computed(() => {\n const modelSet = new Set<string>()\n templatesArray.value.forEach((template) => {\n if (Array.isArray(template.models)) {\n template.models.forEach((model) => modelSet.add(model))\n }\n })\n return Array.from(modelSet).sort()\n })\n\n const availableUseCases = computed(() => {\n const tagSet = new Set<string>()\n templatesArray.value.forEach((template) => {\n if (template.tags && Array.isArray(template.tags)) {\n template.tags.forEach((tag) => tagSet.add(tag))\n }\n })\n return Array.from(tagSet).sort()\n })\n\n const availableRunsOn = computed(() => {\n return ['ComfyUI', 'External or Remote API']\n })\n\n const debouncedSearchQuery = refDebounced(searchQuery, 50)\n\n const filteredBySearch = computed(() => {\n if (!debouncedSearchQuery.value.trim()) {\n return templatesArray.value\n }\n\n const results = fuse.value.search(debouncedSearchQuery.value)\n return results.map((result) => result.item)\n })\n\n const filteredByModels = computed(() => {\n if (selectedModels.value.length === 0) {\n return filteredBySearch.value\n }\n\n return filteredBySearch.value.filter((template) => {\n if (!template.models || !Array.isArray(template.models)) {\n return false\n }\n return selectedModels.value.some((selectedModel) =>\n template.models?.includes(selectedModel)\n )\n })\n })\n\n const filteredByUseCases = computed(() => {\n if (selectedUseCases.value.length === 0) {\n return filteredByModels.value\n }\n\n return filteredByModels.value.filter((template) => {\n if (!template.tags || !Array.isArray(template.tags)) {\n return false\n }\n return selectedUseCases.value.some((selectedTag) =>\n template.tags?.includes(selectedTag)\n )\n })\n })\n\n const filteredByRunsOn = computed(() => {\n if (selectedRunsOn.value.length === 0) {\n return filteredByUseCases.value\n }\n\n return filteredByUseCases.value.filter((template) => {\n // Use openSource field to determine where template runs\n // openSource === false -> External/Remote API\n // openSource !== false -> ComfyUI (includes true and undefined)\n const isExternalAPI = template.openSource === false\n const isComfyUI = template.openSource !== false\n\n return selectedRunsOn.value.some((selectedRunsOn) => {\n if (selectedRunsOn === 'External or Remote API') {\n return isExternalAPI\n } else if (selectedRunsOn === 'ComfyUI') {\n return isComfyUI\n }\n return false\n })\n })\n })\n\n const getVramMetric = (template: TemplateInfo) => {\n if (\n typeof template.vram === 'number' &&\n Number.isFinite(template.vram) &&\n template.vram > 0\n ) {\n return template.vram\n }\n return Number.POSITIVE_INFINITY\n }\n\n watch(\n filteredByRunsOn,\n (templates) => {\n rankingStore.largestUsageScore = Math.max(\n ...templates.map((t) => t.usage || 0)\n )\n },\n { immediate: true }\n )\n\n const sortedTemplates = computed(() => {\n const templates = [...filteredByRunsOn.value]\n\n switch (sortBy.value) {\n case 'recommended':\n // Curated: usage × 0.5 + internal × 0.3 + freshness × 0.2\n return templates.sort((a, b) => {\n const scoreA = rankingStore.computeDefaultScore(\n a.date,\n a.searchRank,\n a.usage\n )\n const scoreB = rankingStore.computeDefaultScore(\n b.date,\n b.searchRank,\n b.usage\n )\n return scoreB - scoreA\n })\n case 'popular':\n // User-driven: usage × 0.9 + freshness × 0.1\n return templates.sort((a, b) => {\n const scoreA = rankingStore.computePopularScore(a.date, a.usage)\n const scoreB = rankingStore.computePopularScore(b.date, b.usage)\n return scoreB - scoreA\n })\n case 'alphabetical':\n return templates.sort((a, b) => {\n const nameA = a.title || a.name || ''\n const nameB = b.title || b.name || ''\n return nameA.localeCompare(nameB)\n })\n case 'newest':\n return templates.sort((a, b) => {\n const dateA = new Date(a.date || '1970-01-01')\n const dateB = new Date(b.date || '1970-01-01')\n return dateB.getTime() - dateA.getTime()\n })\n case 'vram-low-to-high':\n return templates.sort((a, b) => {\n const vramA = getVramMetric(a)\n const vramB = getVramMetric(b)\n\n if (vramA === vramB) {\n const nameA = a.title || a.name || ''\n const nameB = b.title || b.name || ''\n return nameA.localeCompare(nameB)\n }\n\n if (vramA === Number.POSITIVE_INFINITY) return 1\n if (vramB === Number.POSITIVE_INFINITY) return -1\n\n return vramA - vramB\n })\n case 'model-size-low-to-high':\n return templates.sort((a, b) => {\n const sizeA =\n typeof a.size === 'number' ? a.size : Number.POSITIVE_INFINITY\n const sizeB =\n typeof b.size === 'number' ? b.size : Number.POSITIVE_INFINITY\n if (sizeA === sizeB) return 0\n return sizeA - sizeB\n })\n case 'default':\n default:\n return templates\n }\n })\n\n const filteredTemplates = computed(() => sortedTemplates.value)\n\n const resetFilters = () => {\n searchQuery.value = ''\n selectedModels.value = []\n selectedUseCases.value = []\n selectedRunsOn.value = []\n sortBy.value = 'default'\n }\n\n const removeModelFilter = (model: string) => {\n selectedModels.value = selectedModels.value.filter((m) => m !== model)\n }\n\n const removeUseCaseFilter = (tag: string) => {\n selectedUseCases.value = selectedUseCases.value.filter((t) => t !== tag)\n }\n\n const removeRunsOnFilter = (runsOn: string) => {\n selectedRunsOn.value = selectedRunsOn.value.filter((r) => r !== runsOn)\n }\n\n const filteredCount = computed(() => filteredTemplates.value.length)\n const totalCount = computed(() => templatesArray.value.length)\n\n // Template filter tracking (debounced to avoid excessive events)\n const debouncedTrackFilterChange = debounce(() => {\n useTelemetry()?.trackTemplateFilterChanged({\n search_query: searchQuery.value || undefined,\n selected_models: selectedModels.value,\n selected_use_cases: selectedUseCases.value,\n selected_runs_on: selectedRunsOn.value,\n sort_by: sortBy.value,\n filtered_count: filteredCount.value,\n total_count: totalCount.value\n })\n }, 500)\n\n const loadFuseOptions = async () => {\n const fetchedOptions = await api.getFuseOptions()\n if (fetchedOptions) {\n fuseOptions.value = fetchedOptions\n }\n }\n\n // Watch for filter changes and track them\n watch(\n [searchQuery, selectedModels, selectedUseCases, selectedRunsOn, sortBy],\n () => {\n // Only track if at least one filter is active (to avoid tracking initial state)\n const hasActiveFilters =\n searchQuery.value.trim() !== '' ||\n selectedModels.value.length > 0 ||\n selectedUseCases.value.length > 0 ||\n selectedRunsOn.value.length > 0 ||\n sortBy.value !== 'default'\n\n if (hasActiveFilters) {\n debouncedTrackFilterChange()\n }\n },\n { deep: true }\n )\n\n // Persist filter changes to settings (debounced to avoid excessive saves)\n watchDebounced(\n selectedModels,\n (newValue) => {\n void settingStore.set('Comfy.Templates.SelectedModels', newValue)\n },\n { debounce: 500, deep: true }\n )\n\n watchDebounced(\n selectedUseCases,\n (newValue) => {\n void settingStore.set('Comfy.Templates.SelectedUseCases', newValue)\n },\n { debounce: 500, deep: true }\n )\n\n watchDebounced(\n selectedRunsOn,\n (newValue) => {\n void settingStore.set('Comfy.Templates.SelectedRunsOn', newValue)\n },\n { debounce: 500, deep: true }\n )\n\n watchDebounced(\n sortBy,\n (newValue) => {\n void settingStore.set('Comfy.Templates.SortBy', newValue)\n },\n { debounce: 500 }\n )\n\n return {\n // State\n searchQuery,\n selectedModels,\n selectedUseCases,\n selectedRunsOn,\n sortBy,\n\n // Computed\n filteredTemplates,\n availableModels,\n availableUseCases,\n availableRunsOn,\n filteredCount,\n totalCount,\n\n // Methods\n resetFilters,\n removeModelFilter,\n removeUseCaseFilter,\n removeRunsOnFilter,\n loadFuseOptions\n }\n}\n","import { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { isCloud } from '@/platform/distribution/types'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'\nimport type {\n TemplateGroup,\n TemplateInfo,\n WorkflowTemplates\n} from '@/platform/workflow/templates/types/template'\nimport { api } from '@/scripts/api'\nimport { app } from '@/scripts/app'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nexport function useTemplateWorkflows() {\n const { t } = useI18n()\n const workflowTemplatesStore = useWorkflowTemplatesStore()\n const dialogStore = useDialogStore()\n\n // State\n const selectedTemplate = ref<WorkflowTemplates | null>(null)\n const loadingTemplateId = ref<string | null>(null)\n\n // Computed\n const isTemplatesLoaded = computed(() => workflowTemplatesStore.isLoaded)\n const allTemplateGroups = computed<TemplateGroup[]>(\n () => workflowTemplatesStore.groupedTemplates\n )\n\n /**\n * Loads all template workflows from the API\n */\n const loadTemplates = async () => {\n if (!workflowTemplatesStore.isLoaded) {\n await workflowTemplatesStore.loadWorkflowTemplates()\n }\n return workflowTemplatesStore.isLoaded\n }\n\n /**\n * Selects the first template category as default\n */\n const selectFirstTemplateCategory = () => {\n if (allTemplateGroups.value.length > 0) {\n const firstCategory = allTemplateGroups.value[0].modules[0]\n selectTemplateCategory(firstCategory)\n }\n }\n\n /**\n * Selects a template category\n */\n const selectTemplateCategory = (category: WorkflowTemplates | null) => {\n selectedTemplate.value = category\n return category !== null\n }\n\n /**\n * Gets template thumbnail URL\n */\n const getTemplateThumbnailUrl = (\n template: TemplateInfo,\n sourceModule: string,\n index = '1'\n ) => {\n const basePath =\n sourceModule === 'default'\n ? api.fileURL(`/templates/${template.name}`)\n : api.apiURL(`/workflow_templates/${sourceModule}/${template.name}`)\n\n const indexSuffix = sourceModule === 'default' && index ? `-${index}` : ''\n return `${basePath}${indexSuffix}.${template.mediaSubtype}`\n }\n\n /**\n * Gets formatted template title\n */\n const getTemplateTitle = (template: TemplateInfo, sourceModule: string) => {\n const fallback =\n template.title ?? template.name ?? `${sourceModule} Template`\n return sourceModule === 'default'\n ? (template.localizedTitle ?? fallback)\n : fallback\n }\n\n /**\n * Gets formatted template description\n */\n const getTemplateDescription = (template: TemplateInfo) => {\n return (\n (template.localizedDescription || template.description)\n ?.replace(/[-_]/g, ' ')\n .trim() ?? ''\n )\n }\n\n /**\n * Loads a workflow template\n */\n const loadWorkflowTemplate = async (id: string, sourceModule: string) => {\n if (!isTemplatesLoaded.value) return false\n\n loadingTemplateId.value = id\n let json\n\n try {\n // Handle \"All\" category as a special case\n if (sourceModule === 'all') {\n // Find \"All\" category in the ComfyUI Examples group\n const comfyExamplesGroup = allTemplateGroups.value.find(\n (g) =>\n g.label ===\n t('templateWorkflows.category.ComfyUI Examples', 'ComfyUI Examples')\n )\n const allCategory = comfyExamplesGroup?.modules.find(\n (m) => m.moduleName === 'all'\n )\n const template = allCategory?.templates.find((t) => t.name === id)\n\n if (!template || !template.sourceModule) return false\n\n // Use the stored source module for loading\n const actualSourceModule = template.sourceModule\n json = await fetchTemplateJson(id, actualSourceModule)\n\n // Use source module for name\n const workflowName =\n actualSourceModule === 'default'\n ? t(`templateWorkflows.template.${id}`, id)\n : id\n\n if (isCloud) {\n useTelemetry()?.trackTemplate({\n workflow_name: id,\n template_source: actualSourceModule\n })\n }\n\n dialogStore.closeDialog()\n await app.loadGraphData(json, true, true, workflowName, {\n openSource: 'template'\n })\n\n return true\n }\n\n // Regular case for normal categories\n json = await fetchTemplateJson(id, sourceModule)\n\n const workflowName =\n sourceModule === 'default'\n ? t(`templateWorkflows.template.${id}`, id)\n : id\n\n if (isCloud) {\n useTelemetry()?.trackTemplate({\n workflow_name: id,\n template_source: sourceModule\n })\n }\n\n dialogStore.closeDialog()\n await app.loadGraphData(json, true, true, workflowName, {\n openSource: 'template'\n })\n\n return true\n } catch (error) {\n console.error('Error loading workflow template:', error)\n return false\n } finally {\n loadingTemplateId.value = null\n }\n }\n\n /**\n * Fetches template JSON from the appropriate endpoint\n */\n const fetchTemplateJson = async (id: string, sourceModule: string) => {\n if (sourceModule === 'default') {\n // Default templates provided by frontend are served on this separate endpoint\n return fetch(api.fileURL(`/templates/${id}.json`)).then((r) => r.json())\n } else {\n return fetch(\n api.apiURL(`/workflow_templates/${sourceModule}/${id}.json`)\n ).then((r) => r.json())\n }\n }\n\n return {\n // State\n selectedTemplate,\n loadingTemplateId,\n\n // Computed\n isTemplatesLoaded,\n allTemplateGroups,\n\n // Methods\n loadTemplates,\n selectFirstTemplateCategory,\n selectTemplateCategory,\n getTemplateThumbnailUrl,\n getTemplateTitle,\n getTemplateDescription,\n loadWorkflowTemplate\n }\n}\n","import type { CSSProperties } from 'vue'\n\ninterface GridOptions {\n /** Minimum width for each grid item (default: 15rem) */\n minWidth?: string\n /** Maximum width for each grid item (default: 1fr) */\n maxWidth?: string\n /** Padding around the grid (default: 0) */\n padding?: string\n /** Gap between grid items (default: 1rem) */\n gap?: string\n /** Fixed number of columns (overrides auto-fill with minmax) */\n columns?: number\n}\n\n/**\n * @deprecated Just use tailwind utilities directly.\n * TODO: Create a common grid layout component if needed.\n * Creates CSS grid styles for responsive grid layouts\n * @param options Grid configuration options\n * @returns CSS properties object for grid styling\n */\nexport function createGridStyle(options: GridOptions = {}): CSSProperties {\n const {\n minWidth = '15rem',\n maxWidth = '1fr',\n padding = '0',\n gap = '1rem',\n columns\n } = options\n\n // Runtime validation for columns\n if (columns !== undefined && columns < 1) {\n console.warn('createGridStyle: columns must be >= 1, defaulting to 1')\n }\n\n const gridTemplateColumns = columns\n ? `repeat(${Math.max(1, columns ?? 1)}, 1fr)`\n : `repeat(auto-fill, minmax(${minWidth}, ${maxWidth}))`\n\n return {\n display: 'grid',\n gridTemplateColumns,\n padding,\n gap\n }\n}\n","<template>\n <BaseModalLayout\n :content-title=\"$t('templateWorkflows.title', 'Workflow Templates')\"\n class=\"workflow-template-selector-dialog\"\n >\n <template #leftPanel>\n <LeftSidePanel v-model=\"selectedNavItem\" :nav-items=\"navItems\">\n <template #header-icon>\n <i class=\"icon-[comfy--template]\" />\n </template>\n <template #header-title>\n <span class=\"text-neutral text-base\">{{\n $t('sideToolbar.templates', 'Templates')\n }}</span>\n </template>\n </LeftSidePanel>\n </template>\n\n <template #header>\n <SearchBox v-model=\"searchQuery\" size=\"lg\" class=\"max-w-[384px]\" />\n </template>\n\n <template #header-right-area>\n <div class=\"flex gap-2\">\n <Button\n v-if=\"filteredCount !== totalCount\"\n variant=\"secondary\"\n size=\"lg\"\n @click=\"resetFilters\"\n >\n <i class=\"icon-[lucide--filter-x]\" />\n <span>{{\n $t('templateWorkflows.resetFilters', 'Clear Filters')\n }}</span>\n </Button>\n </div>\n </template>\n\n <template #contentFilter>\n <div class=\"relative flex flex-wrap justify-between gap-2 px-6 pb-4\">\n <div class=\"flex flex-wrap gap-2\">\n <!-- Model Filter -->\n <MultiSelect\n v-model=\"selectedModelObjects\"\n v-model:search-query=\"modelSearchText\"\n class=\"w-[250px]\"\n :label=\"modelFilterLabel\"\n :options=\"modelOptions\"\n :show-search-box=\"true\"\n :show-selected-count=\"true\"\n :show-clear-button=\"true\"\n >\n <template #icon>\n <i class=\"icon-[lucide--cpu]\" />\n </template>\n </MultiSelect>\n\n <!-- Use Case Filter -->\n <MultiSelect\n v-model=\"selectedUseCaseObjects\"\n :label=\"useCaseFilterLabel\"\n :options=\"useCaseOptions\"\n :show-search-box=\"true\"\n :show-selected-count=\"true\"\n :show-clear-button=\"true\"\n >\n <template #icon>\n <i class=\"icon-[lucide--target]\" />\n </template>\n </MultiSelect>\n\n <!-- Runs On Filter -->\n <MultiSelect\n v-model=\"selectedRunsOnObjects\"\n :label=\"runsOnFilterLabel\"\n :options=\"runsOnOptions\"\n :show-search-box=\"true\"\n :show-selected-count=\"true\"\n :show-clear-button=\"true\"\n >\n <template #icon>\n <i class=\"icon-[lucide--server]\" />\n </template>\n </MultiSelect>\n </div>\n\n <!-- Sort Options -->\n <div>\n <SingleSelect\n v-model=\"sortBy\"\n :label=\"$t('templateWorkflows.sorting', 'Sort by')\"\n :options=\"sortOptions\"\n class=\"w-62.5\"\n >\n <template #icon>\n <i class=\"icon-[lucide--arrow-up-down] text-muted-foreground\" />\n </template>\n </SingleSelect>\n </div>\n </div>\n <div\n v-if=\"!isLoading\"\n class=\"text-neutral px-6 pt-4 pb-2 text-2xl font-semibold\"\n >\n <span>\n {{ pageTitle }}\n </span>\n </div>\n </template>\n\n <template #content>\n <!-- No Results State (only show when loaded and no results) -->\n <div\n v-if=\"!isLoading && filteredTemplates.length === 0\"\n class=\"flex h-64 flex-col items-center justify-center text-neutral-500\"\n >\n <i class=\"mb-4 icon-[lucide--search] h-12 w-12 opacity-50\" />\n <p class=\"mb-2 text-lg\">\n {{ $t('templateWorkflows.noResults', 'No templates found') }}\n </p>\n <p class=\"text-sm\">\n {{\n $t(\n 'templateWorkflows.noResultsHint',\n 'Try adjusting your search or filters'\n )\n }}\n </p>\n </div>\n <div v-else>\n <!-- Title -->\n <span\n v-if=\"isLoading\"\n class=\"inline-block h-8 w-48 animate-pulse rounded bg-dialog-surface\"\n ></span>\n\n <!-- Template Cards Grid -->\n <div\n :key=\"templateListKey\"\n :style=\"gridStyle\"\n data-testid=\"template-workflows-content\"\n >\n <!-- Loading Skeletons (show while loading initial data) -->\n <CardContainer\n v-for=\"n in isLoading ? 12 : 0\"\n :key=\"`initial-skeleton-${n}`\"\n size=\"compact\"\n variant=\"ghost\"\n rounded=\"lg\"\n class=\"hover:bg-base-background\"\n >\n <template #top>\n <CardTop ratio=\"landscape\">\n <template #default>\n <div\n class=\"h-full w-full animate-pulse bg-dialog-surface\"\n ></div>\n </template>\n </CardTop>\n </template>\n <template #bottom>\n <CardBottom>\n <div class=\"px-4 py-3\">\n <div\n class=\"mb-2 h-6 animate-pulse rounded bg-dialog-surface\"\n ></div>\n <div\n class=\"h-4 animate-pulse rounded bg-dialog-surface\"\n ></div>\n </div>\n </CardBottom>\n </template>\n </CardContainer>\n\n <!-- Actual Template Cards -->\n <CardContainer\n v-for=\"template in isLoading ? [] : displayTemplates\"\n v-show=\"isTemplateVisibleOnDistribution(template)\"\n :key=\"template.name\"\n ref=\"cardRefs\"\n size=\"compact\"\n variant=\"ghost\"\n rounded=\"lg\"\n :data-testid=\"`template-workflow-${template.name}`\"\n class=\"hover:bg-base-background\"\n @mouseenter=\"hoveredTemplate = template.name\"\n @mouseleave=\"hoveredTemplate = null\"\n @click=\"onLoadWorkflow(template)\"\n >\n <template #top>\n <CardTop ratio=\"square\">\n <template #default>\n <!-- Template Thumbnail -->\n <div\n class=\"relative h-full w-full overflow-hidden rounded-lg\"\n >\n <template v-if=\"template.mediaType === 'audio'\">\n <AudioThumbnail :src=\"getBaseThumbnailSrc(template)\" />\n </template>\n <template\n v-else-if=\"template.thumbnailVariant === 'compareSlider'\"\n >\n <CompareSliderThumbnail\n :base-image-src=\"getBaseThumbnailSrc(template)\"\n :overlay-image-src=\"getOverlayThumbnailSrc(template)\"\n :alt=\"\n getTemplateTitle(\n template,\n getEffectiveSourceModule(template)\n )\n \"\n :is-hovered=\"hoveredTemplate === template.name\"\n :is-video=\"\n template.mediaType === 'video' ||\n template.mediaSubtype === 'webp'\n \"\n />\n </template>\n <template\n v-else-if=\"template.thumbnailVariant === 'hoverDissolve'\"\n >\n <HoverDissolveThumbnail\n :base-image-src=\"getBaseThumbnailSrc(template)\"\n :overlay-image-src=\"getOverlayThumbnailSrc(template)\"\n :alt=\"\n getTemplateTitle(\n template,\n getEffectiveSourceModule(template)\n )\n \"\n :is-hovered=\"hoveredTemplate === template.name\"\n :is-video=\"\n template.mediaType === 'video' ||\n template.mediaSubtype === 'webp'\n \"\n />\n </template>\n <template v-else>\n <DefaultThumbnail\n :src=\"getBaseThumbnailSrc(template)\"\n :alt=\"\n getTemplateTitle(\n template,\n getEffectiveSourceModule(template)\n )\n \"\n :is-hovered=\"hoveredTemplate === template.name\"\n :is-video=\"\n template.mediaType === 'video' ||\n template.mediaSubtype === 'webp'\n \"\n :hover-zoom=\"\n template.thumbnailVariant === 'zoomHover' ? 16 : 5\n \"\n />\n </template>\n <ProgressSpinner\n v-if=\"loadingTemplate === template.name\"\n class=\"absolute inset-0 z-10 m-auto h-12 w-12\"\n />\n </div>\n </template>\n <template #bottom-right>\n <template v-if=\"template.tags && template.tags.length > 0\">\n <SquareChip\n v-for=\"tag in template.tags\"\n :key=\"tag\"\n :label=\"tag\"\n />\n </template>\n </template>\n </CardTop>\n </template>\n <template #bottom>\n <CardBottom>\n <div class=\"flex flex-col gap-2 pt-3\">\n <h3\n class=\"m-0 line-clamp-1 text-sm\"\n :title=\"\n getTemplateTitle(\n template,\n getEffectiveSourceModule(template)\n )\n \"\n >\n {{\n getTemplateTitle(\n template,\n getEffectiveSourceModule(template)\n )\n }}\n </h3>\n <div class=\"flex justify-between gap-2\">\n <div class=\"flex-1\">\n <p\n class=\"m-0 line-clamp-2 text-sm text-muted\"\n :title=\"getTemplateDescription(template)\"\n >\n {{ getTemplateDescription(template) }}\n </p>\n </div>\n <div\n v-if=\"template.tutorialUrl\"\n class=\"flex flex-col-reverse justify-center\"\n >\n <Button\n v-if=\"hoveredTemplate === template.name\"\n v-tooltip.bottom=\"$t('g.seeTutorial')\"\n v-bind=\"$attrs\"\n variant=\"inverted\"\n size=\"icon\"\n @click.stop=\"openTutorial(template)\"\n >\n <i class=\"icon-[lucide--info] size-4\" />\n </Button>\n </div>\n </div>\n </div>\n </CardBottom>\n </template>\n </CardContainer>\n\n <!-- Loading More Skeletons -->\n <CardContainer\n v-for=\"n in isLoadingMore ? 6 : 0\"\n :key=\"`skeleton-${n}`\"\n size=\"compact\"\n variant=\"ghost\"\n rounded=\"lg\"\n class=\"hover:bg-base-background\"\n >\n <template #top>\n <CardTop ratio=\"square\">\n <template #default>\n <div\n class=\"h-full w-full animate-pulse bg-dialog-surface\"\n ></div>\n </template>\n </CardTop>\n </template>\n <template #bottom>\n <CardBottom>\n <div class=\"px-4 py-3\">\n <div\n class=\"mb-2 h-6 animate-pulse rounded bg-dialog-surface\"\n ></div>\n <div\n class=\"h-4 animate-pulse rounded bg-dialog-surface\"\n ></div>\n </div>\n </CardBottom>\n </template>\n </CardContainer>\n </div>\n </div>\n\n <!-- Load More Trigger -->\n <div\n v-if=\"!isLoading && hasMoreTemplates\"\n ref=\"loadTrigger\"\n class=\"mt-4 flex h-4 w-full items-center justify-center\"\n >\n <div v-if=\"isLoadingMore\" class=\"text-sm text-muted\">\n {{ $t('templateWorkflows.loadingMore', 'Loading more...') }}\n </div>\n </div>\n\n <!-- Results Summary -->\n <div v-if=\"!isLoading\" class=\"mt-6 px-6 text-sm text-muted\">\n {{\n $t('templateWorkflows.resultsCount', {\n count: filteredCount,\n total: totalCount\n })\n }}\n </div>\n </template>\n </BaseModalLayout>\n</template>\n\n<script setup lang=\"ts\">\nimport { useAsyncState } from '@vueuse/core'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport CardBottom from '@/components/card/CardBottom.vue'\nimport CardContainer from '@/components/card/CardContainer.vue'\nimport CardTop from '@/components/card/CardTop.vue'\nimport SquareChip from '@/components/chip/SquareChip.vue'\nimport SearchBox from '@/components/common/SearchBox.vue'\nimport MultiSelect from '@/components/input/MultiSelect.vue'\nimport SingleSelect from '@/components/input/SingleSelect.vue'\nimport AudioThumbnail from '@/components/templates/thumbnails/AudioThumbnail.vue'\nimport CompareSliderThumbnail from '@/components/templates/thumbnails/CompareSliderThumbnail.vue'\nimport DefaultThumbnail from '@/components/templates/thumbnails/DefaultThumbnail.vue'\nimport HoverDissolveThumbnail from '@/components/templates/thumbnails/HoverDissolveThumbnail.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'\nimport LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'\nimport { useIntersectionObserver } from '@/composables/useIntersectionObserver'\nimport { useLazyPagination } from '@/composables/useLazyPagination'\nimport { useTemplateFiltering } from '@/composables/useTemplateFiltering'\nimport { isCloud } from '@/platform/distribution/types'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useTemplateWorkflows } from '@/platform/workflow/templates/composables/useTemplateWorkflows'\nimport { useWorkflowTemplatesStore } from '@/platform/workflow/templates/repositories/workflowTemplatesStore'\nimport type { TemplateInfo } from '@/platform/workflow/templates/types/template'\nimport { TemplateIncludeOnDistributionEnum } from '@/platform/workflow/templates/types/template'\nimport { useSystemStatsStore } from '@/stores/systemStatsStore'\nimport type { NavGroupData, NavItemData } from '@/types/navTypes'\nimport { OnCloseKey } from '@/types/widgetTypes'\nimport { createGridStyle } from '@/utils/gridUtil'\n\nconst { t } = useI18n()\n\nconst { onClose: originalOnClose } = defineProps<{\n onClose: () => void\n}>()\n\n// Track session time for telemetry\nconst sessionStartTime = ref<number>(0)\nconst templateWasSelected = ref(false)\n\nonMounted(() => {\n sessionStartTime.value = Date.now()\n})\n\nconst systemStatsStore = useSystemStatsStore()\n\nconst distributions = computed(() => {\n // eslint-disable-next-line no-undef\n switch (__DISTRIBUTION__) {\n case 'cloud':\n return [TemplateIncludeOnDistributionEnum.Cloud]\n case 'localhost':\n return [TemplateIncludeOnDistributionEnum.Local]\n case 'desktop':\n default:\n if (systemStatsStore.systemStats?.system.os === 'darwin') {\n return [\n TemplateIncludeOnDistributionEnum.Desktop,\n TemplateIncludeOnDistributionEnum.Mac\n ]\n }\n return [\n TemplateIncludeOnDistributionEnum.Desktop,\n TemplateIncludeOnDistributionEnum.Windows\n ]\n }\n})\n\n// Wrap onClose to track session end\nconst onClose = () => {\n if (isCloud) {\n const timeSpentSeconds = Math.floor(\n (Date.now() - sessionStartTime.value) / 1000\n )\n\n useTelemetry()?.trackTemplateLibraryClosed({\n template_selected: templateWasSelected.value,\n time_spent_seconds: timeSpentSeconds\n })\n }\n\n originalOnClose()\n}\n\nprovide(OnCloseKey, onClose)\n\n// Workflow templates store and composable\nconst workflowTemplatesStore = useWorkflowTemplatesStore()\nconst {\n loadTemplates,\n loadWorkflowTemplate,\n getTemplateThumbnailUrl,\n getTemplateTitle,\n getTemplateDescription\n} = useTemplateWorkflows()\n\nconst getEffectiveSourceModule = (template: TemplateInfo) =>\n template.sourceModule || 'default'\n\nconst getBaseThumbnailSrc = (template: TemplateInfo) => {\n const sm = getEffectiveSourceModule(template)\n return getTemplateThumbnailUrl(template, sm, sm === 'default' ? '1' : '')\n}\n\nconst getOverlayThumbnailSrc = (template: TemplateInfo) => {\n const sm = getEffectiveSourceModule(template)\n return getTemplateThumbnailUrl(template, sm, sm === 'default' ? '2' : '')\n}\n\n// Open tutorial in new tab\nconst openTutorial = (template: TemplateInfo) => {\n if (template.tutorialUrl) {\n window.open(template.tutorialUrl, '_blank')\n }\n}\n\n// Get navigation items from the store, with skeleton items while loading\nconst navItems = computed<(NavItemData | NavGroupData)[]>(() => {\n // Show skeleton navigation items while loading\n if (isLoading.value) {\n return [\n {\n id: 'skeleton-all',\n label: 'All Templates',\n icon: 'icon-[lucide--layout-grid]'\n },\n {\n id: 'skeleton-basics',\n label: 'Basics',\n icon: 'icon-[lucide--graduation-cap]'\n },\n {\n title: 'Generation Type',\n items: [\n { id: 'skeleton-1', label: '...', icon: 'icon-[lucide--loader-2]' },\n { id: 'skeleton-2', label: '...', icon: 'icon-[lucide--loader-2]' }\n ]\n },\n {\n title: 'Closed Source Models',\n items: [\n { id: 'skeleton-3', label: '...', icon: 'icon-[lucide--loader-2]' }\n ]\n }\n ]\n }\n return workflowTemplatesStore.navGroupedTemplates\n})\n\nconst gridStyle = computed(() => createGridStyle())\n\n// Get enhanced templates for better filtering\nconst allTemplates = computed(() => {\n return workflowTemplatesStore.enhancedTemplates\n})\n\n// Navigation\nconst selectedNavItem = ref<string | null>('all')\n\n// Filter templates based on selected navigation item\nconst navigationFilteredTemplates = computed(() => {\n if (!selectedNavItem.value) {\n return allTemplates.value\n }\n\n return workflowTemplatesStore.filterTemplatesByCategory(selectedNavItem.value)\n})\n\n// Template filtering\nconst {\n searchQuery,\n selectedModels,\n selectedUseCases,\n selectedRunsOn,\n sortBy,\n filteredTemplates,\n availableModels,\n availableUseCases,\n availableRunsOn,\n filteredCount,\n totalCount,\n resetFilters,\n loadFuseOptions\n} = useTemplateFiltering(navigationFilteredTemplates)\n\n/**\n * Coordinates state between the selected navigation item and the sort order to\n * create deterministic, predictable behavior.\n * @param source The origin of the change ('nav' or 'sort').\n */\nconst coordinateNavAndSort = (source: 'nav' | 'sort') => {\n const isPopularNav = selectedNavItem.value === 'popular'\n const isPopularSort = sortBy.value === 'popular'\n\n if (source === 'nav') {\n if (isPopularNav && !isPopularSort) {\n // When navigating to 'Popular' category, automatically set sort to 'Popular'.\n sortBy.value = 'popular'\n } else if (!isPopularNav && isPopularSort) {\n // When navigating away from 'Popular' category while sort is 'Popular', reset sort to default.\n sortBy.value = 'default'\n }\n } else if (source === 'sort') {\n // When sort is changed away from 'Popular' while in the 'Popular' category,\n // reset the category to 'All Templates' to avoid a confusing state.\n if (isPopularNav && !isPopularSort) {\n selectedNavItem.value = 'all'\n }\n }\n}\n\n// Watch for changes from the two sources ('nav' and 'sort') and trigger the coordinator.\nwatch(selectedNavItem, () => coordinateNavAndSort('nav'))\nwatch(sortBy, () => coordinateNavAndSort('sort'))\n\n// Convert between string array and object array for MultiSelect component\nconst selectedModelObjects = computed({\n get() {\n return selectedModels.value.map((model) => ({ name: model, value: model }))\n },\n set(value: { name: string; value: string }[]) {\n selectedModels.value = value.map((item) => item.value)\n }\n})\n\nconst selectedUseCaseObjects = computed({\n get() {\n return selectedUseCases.value.map((useCase) => ({\n name: useCase,\n value: useCase\n }))\n },\n set(value: { name: string; value: string }[]) {\n selectedUseCases.value = value.map((item) => item.value)\n }\n})\n\nconst selectedRunsOnObjects = computed({\n get() {\n return selectedRunsOn.value.map((runsOn) => ({\n name: runsOn,\n value: runsOn\n }))\n },\n set(value: { name: string; value: string }[]) {\n selectedRunsOn.value = value.map((item) => item.value)\n }\n})\n\n// Loading states\nconst loadingTemplate = ref<string | null>(null)\nconst hoveredTemplate = ref<string | null>(null)\nconst cardRefs = ref<HTMLElement[]>([])\n\n// Force re-render key for templates when sorting changes\nconst templateListKey = ref(0)\n\n// Search text for model filter\nconst modelSearchText = ref<string>('')\n\n// Filter options\nconst modelOptions = computed(() =>\n availableModels.value.map((model) => ({\n name: model,\n value: model\n }))\n)\n\nconst useCaseOptions = computed(() =>\n availableUseCases.value.map((useCase) => ({\n name: useCase,\n value: useCase\n }))\n)\n\nconst runsOnOptions = computed(() =>\n availableRunsOn.value.map((runsOn) => ({\n name: runsOn,\n value: runsOn\n }))\n)\n\n// Filter labels\nconst modelFilterLabel = computed(() => {\n if (selectedModelObjects.value.length === 0) {\n return t('templateWorkflows.modelFilter', 'Model Filter')\n } else if (selectedModelObjects.value.length === 1) {\n return selectedModelObjects.value[0].name\n } else {\n return t('templateWorkflows.modelsSelected', {\n count: selectedModelObjects.value.length\n })\n }\n})\n\nconst useCaseFilterLabel = computed(() => {\n if (selectedUseCaseObjects.value.length === 0) {\n return t('templateWorkflows.useCaseFilter', 'Use Case')\n } else if (selectedUseCaseObjects.value.length === 1) {\n return selectedUseCaseObjects.value[0].name\n } else {\n return t('templateWorkflows.useCasesSelected', {\n count: selectedUseCaseObjects.value.length\n })\n }\n})\n\nconst runsOnFilterLabel = computed(() => {\n if (selectedRunsOnObjects.value.length === 0) {\n return t('templateWorkflows.runsOnFilter', 'Runs On')\n } else if (selectedRunsOnObjects.value.length === 1) {\n return selectedRunsOnObjects.value[0].name\n } else {\n return t('templateWorkflows.runsOnSelected', {\n count: selectedRunsOnObjects.value.length\n })\n }\n})\n\n// Sort options\nconst sortOptions = computed(() => [\n {\n name: t('templateWorkflows.sort.default', 'Default'),\n value: 'default'\n },\n {\n name: t('templateWorkflows.sort.recommended', 'Recommended'),\n value: 'recommended'\n },\n {\n name: t('templateWorkflows.sort.popular', 'Popular'),\n value: 'popular'\n },\n { name: t('templateWorkflows.sort.newest', 'Newest'), value: 'newest' },\n {\n name: t('templateWorkflows.sort.vramLowToHigh', 'VRAM Usage (Low to High)'),\n value: 'vram-low-to-high'\n },\n {\n name: t(\n 'templateWorkflows.sort.modelSizeLowToHigh',\n 'Model Size (Low to High)'\n ),\n value: 'model-size-low-to-high'\n },\n {\n name: t('templateWorkflows.sort.alphabetical', 'Alphabetical (A-Z)'),\n value: 'alphabetical'\n }\n])\n\n// Lazy pagination setup\nconst loadTrigger = ref<HTMLElement | null>(null)\nconst shouldUsePagination = computed(() => !searchQuery.value.trim())\n\nconst {\n paginatedItems: paginatedTemplates,\n isLoading: isLoadingMore,\n hasMoreItems: hasMoreTemplates,\n loadNextPage,\n reset: resetPagination\n} = useLazyPagination(filteredTemplates, { itemsPerPage: 24 }) // Load 24 items per page\n\n// Display templates (all when searching, paginated when not)\nconst displayTemplates = computed(() => {\n return shouldUsePagination.value\n ? paginatedTemplates.value\n : filteredTemplates.value\n})\n\n// Set up intersection observer for lazy loading\nuseIntersectionObserver(loadTrigger, () => {\n if (\n shouldUsePagination.value &&\n hasMoreTemplates.value &&\n !isLoadingMore.value\n ) {\n void loadNextPage()\n }\n})\n\n// Reset pagination when filters change\nwatch(\n [\n searchQuery,\n selectedNavItem,\n sortBy,\n selectedModels,\n selectedUseCases,\n selectedRunsOn\n ],\n () => {\n resetPagination()\n // Clear loading state and force re-render of template list\n loadingTemplate.value = null\n templateListKey.value++\n }\n)\n\n// Methods\nconst onLoadWorkflow = async (template: any) => {\n loadingTemplate.value = template.name\n try {\n await loadWorkflowTemplate(\n template.name,\n getEffectiveSourceModule(template)\n )\n templateWasSelected.value = true\n onClose()\n } finally {\n loadingTemplate.value = null\n }\n}\n\nconst pageTitle = computed(() => {\n const navItem = navItems.value.find((item) =>\n 'id' in item\n ? item.id === selectedNavItem.value\n : item.items?.some((sub) => sub.id === selectedNavItem.value)\n )\n\n if (!navItem) {\n return t('templateWorkflows.allTemplates', 'All Templates')\n }\n\n return 'id' in navItem\n ? navItem.label\n : navItem.items?.find((i) => i.id === selectedNavItem.value)?.label ||\n t('templateWorkflows.allTemplates', 'All Templates')\n})\n\n// Initialize templates loading with useAsyncState\nconst { isLoading } = useAsyncState(\n async () => {\n await Promise.all([\n loadTemplates(),\n workflowTemplatesStore.loadWorkflowTemplates(),\n loadFuseOptions()\n ])\n return true\n },\n false, // initial state\n {\n immediate: true // Start loading immediately\n }\n)\n\nconst isTemplateVisibleOnDistribution = (template: TemplateInfo) => {\n return (template.includeOnDistributions?.length ?? 0) > 0\n ? distributions.value.some((d) =>\n template.includeOnDistributions?.includes(d)\n )\n : true\n}\n\nonBeforeUnmount(() => {\n cardRefs.value = [] // Release DOM refs\n})\n</script>\n\n<style>\n/* Ensure the workflow template selector dialog fits within provided dialog */\n.workflow-template-selector-dialog.base-widget-layout {\n width: 100% !important;\n max-width: 1400px;\n height: 100% !important;\n aspect-ratio: auto !important;\n}\n\n@media (min-width: 1600px) {\n .workflow-template-selector-dialog.base-widget-layout {\n max-width: 1600px;\n }\n}\n</style>\n","import WorkflowTemplateSelectorDialog from '@/components/custom/widget/WorkflowTemplateSelectorDialog.vue'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useDialogService } from '@/services/dialogService'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst DIALOG_KEY = 'global-workflow-template-selector'\n\nexport const useWorkflowTemplateSelectorDialog = () => {\n const dialogService = useDialogService()\n const dialogStore = useDialogStore()\n\n function hide() {\n dialogStore.closeDialog({ key: DIALOG_KEY })\n }\n\n function show(source: 'sidebar' | 'menu' | 'command' = 'command') {\n useTelemetry()?.trackTemplateLibraryOpened({ source })\n\n dialogService.showLayoutDialog({\n key: DIALOG_KEY,\n component: WorkflowTemplateSelectorDialog,\n props: {\n onClose: hide\n },\n dialogComponentProps: {\n pt: {\n content: { class: '!px-0 overflow-hidden h-full !py-0' },\n root: {\n style:\n 'width: 90vw; height: 85vh; max-width: 1400px; display: flex;'\n }\n }\n }\n })\n }\n\n return {\n show,\n hide\n }\n}\n","import { useEventListener } from '@vueuse/core'\n\nexport const whileMouseDown = (\n elementOrEvent: HTMLElement | Event,\n callback: (iteration: number) => void,\n interval: number = 30\n) => {\n const element =\n elementOrEvent instanceof HTMLElement\n ? elementOrEvent\n : (elementOrEvent.target as HTMLElement)\n\n let iteration = 0\n\n const intervalId = setInterval(() => {\n callback(iteration++)\n }, interval)\n\n const dispose = () => {\n clearInterval(intervalId)\n disposeGlobal()\n disposeLocal()\n }\n\n // Listen for mouseup globally to catch cases where user drags out of element\n const disposeGlobal = useEventListener(document, 'mouseup', dispose)\n const disposeLocal = useEventListener(element, 'mouseup', dispose)\n\n return {\n dispose: dispose\n }\n}\n","<template>\n <div\n v-tooltip=\"{\n value: t('sideToolbar.labels.menu'),\n showDelay: 300,\n hideDelay: 300\n }\"\n class=\"comfy-menu-button-wrapper flex shrink-0 cursor-pointer flex-col items-center justify-center p-2 transition-colors\"\n :class=\"{\n 'comfy-menu-button-active': menuRef?.visible\n }\"\n @click=\"onLogoMenuClick($event)\"\n >\n <div class=\"flex h-8 w-8 items-center justify-center rounded-lg bg-black\">\n <ComfyLogo\n alt=\"ComfyUI Logo\"\n class=\"comfyui-logo h-[18px] w-[18px] text-white\"\n mode=\"fill\"\n />\n </div>\n </div>\n\n <TieredMenu\n ref=\"menuRef\"\n :model=\"translatedItems\"\n :popup=\"true\"\n class=\"comfy-command-menu\"\n @show=\"onMenuShow\"\n >\n <template #item=\"{ item, props }\">\n <a\n v-if=\"item.key !== 'nodes-2.0-toggle'\"\n class=\"p-menubar-item-link px-4 py-2\"\n v-bind=\"props.action\"\n :href=\"item.url\"\n target=\"_blank\"\n :class=\"typeof item.class === 'function' ? item.class() : item.class\"\n @mousedown=\"\n isZoomCommand(item) ? handleZoomMouseDown(item, $event) : undefined\n \"\n @click=\"handleItemClick(item, $event)\"\n >\n <i\n v-if=\"hasActiveStateSiblings(item)\"\n class=\"p-menubar-item-icon pi pi-check text-sm\"\n :class=\"{ invisible: !item.comfyCommand?.active?.() }\"\n />\n <span\n v-else-if=\"\n item.icon && item.comfyCommand?.id !== 'Comfy.NewBlankWorkflow'\n \"\n class=\"p-menubar-item-icon text-sm\"\n :class=\"item.icon\"\n />\n <span class=\"p-menubar-item-label text-nowrap\">{{ item.label }}</span>\n <i\n v-if=\"item.comfyCommand?.id === 'Comfy.NewBlankWorkflow'\"\n class=\"ml-auto\"\n :class=\"item.icon\"\n />\n <span\n v-if=\"item?.comfyCommand?.keybinding\"\n class=\"keybinding-tag ml-auto rounded border border-surface p-1 text-xs text-nowrap text-muted\"\n >\n {{ item.comfyCommand.keybinding.combo.toString() }}\n </span>\n <i v-if=\"item.items\" class=\"pi pi-angle-right ml-auto\" />\n </a>\n <div\n v-else\n class=\"flex items-center justify-between px-4 py-2\"\n @click.stop=\"handleNodes2ToggleClick\"\n >\n <span class=\"p-menubar-item-label text-nowrap\">{{ item.label }}</span>\n <Tag severity=\"info\" class=\"ml-2 text-xs\">{{ $t('g.beta') }}</Tag>\n <ToggleSwitch\n v-model=\"nodes2Enabled\"\n class=\"ml-4\"\n :aria-label=\"item.label\"\n :pt=\"{\n root: {\n style: {\n width: '38px',\n height: '20px'\n }\n },\n handle: {\n style: {\n width: '16px',\n height: '16px'\n }\n }\n }\"\n @click.stop\n @update:model-value=\"onNodes2ToggleChange\"\n />\n </div>\n </template>\n </TieredMenu>\n</template>\n\n<script setup lang=\"ts\">\nimport type { MenuItem } from 'primevue/menuitem'\nimport Tag from 'primevue/tag'\nimport TieredMenu from 'primevue/tieredmenu'\nimport type { TieredMenuMethods, TieredMenuState } from 'primevue/tieredmenu'\nimport ToggleSwitch from 'primevue/toggleswitch'\nimport { computed, nextTick, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'\nimport ComfyLogo from '@/components/icons/ComfyLogo.vue'\nimport { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'\nimport SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useColorPaletteService } from '@/services/colorPaletteService'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport { useMenuItemStore } from '@/stores/menuItemStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { normalizeI18nKey } from '@/utils/formatUtil'\nimport { whileMouseDown } from '@/utils/mouseDownUtil'\nimport { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'\nimport { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'\n\nconst { t } = useI18n()\nconst commandStore = useCommandStore()\nconst menuItemStore = useMenuItemStore()\nconst colorPaletteStore = useColorPaletteStore()\nconst colorPaletteService = useColorPaletteService()\nconst dialogStore = useDialogStore()\nconst managerState = useManagerState()\nconst settingStore = useSettingStore()\n\nconst menuRef = ref<\n ({ dirty: boolean } & TieredMenuMethods & TieredMenuState) | null\n>(null)\n\nconst nodes2Enabled = computed({\n get: () => settingStore.get('Comfy.VueNodes.Enabled') ?? false,\n set: async (value: boolean) => {\n await settingStore.set('Comfy.VueNodes.Enabled', value)\n }\n})\n\nconst telemetry = useTelemetry()\n\nfunction onLogoMenuClick(event: MouseEvent) {\n telemetry?.trackUiButtonClicked({\n button_id: 'sidebar_comfy_menu_opened'\n })\n menuRef.value?.toggle(event)\n}\n\nconst translateMenuItem = (item: MenuItem): MenuItem => {\n const label = typeof item.label === 'function' ? item.label() : item.label\n const translatedLabel = label\n ? t(`menuLabels.${normalizeI18nKey(label)}`, label)\n : undefined\n\n return {\n ...item,\n label: translatedLabel,\n items: item.items?.map(translateMenuItem)\n }\n}\n\nconst showSettings = (defaultPanel?: string) => {\n dialogStore.showDialog({\n key: 'global-settings',\n headerComponent: SettingDialogHeader,\n component: SettingDialogContent,\n props: {\n defaultPanel\n }\n })\n}\n\nconst showManageExtensions = async () => {\n await managerState.openManager({\n initialTab: ManagerTab.All,\n showToastOnLegacyError: false\n })\n}\n\nconst themeMenuItems = computed(() => {\n return colorPaletteStore.palettes.map((palette) => ({\n key: `theme-${palette.id}`,\n label: palette.name,\n parentPath: 'theme',\n comfyCommand: {\n active: () => colorPaletteStore.activePaletteId === palette.id\n },\n command: async () => {\n await colorPaletteService.loadColorPalette(palette.id)\n }\n }))\n})\n\nconst extraMenuItems = computed(() => [\n { separator: true },\n {\n key: 'theme',\n label: t('menu.theme'),\n items: themeMenuItems.value\n },\n {\n key: 'nodes-2.0-toggle',\n label: 'Nodes 2.0'\n },\n { separator: true },\n {\n key: 'browse-templates',\n label: t('menuLabels.Browse Templates'),\n icon: 'icon-[comfy--template]',\n command: () => useWorkflowTemplateSelectorDialog().show('menu')\n },\n {\n key: 'settings',\n label: t('g.settings'),\n icon: 'mdi mdi-cog-outline',\n command: () => {\n telemetry?.trackUiButtonClicked({\n button_id: 'sidebar_settings_menu_opened'\n })\n showSettings()\n }\n },\n {\n key: 'manage-extensions',\n label: t('menu.manageExtensions'),\n icon: 'mdi mdi-puzzle-outline',\n command: showManageExtensions\n }\n])\n\nconst translatedItems = computed(() => {\n const items = menuItemStore.menuItems.map(translateMenuItem)\n let helpIndex = items.findIndex((item) => item.key === 'Help')\n let helpItem: MenuItem | undefined\n\n if (helpIndex !== -1) {\n items[helpIndex].icon = 'mdi mdi-help-circle-outline'\n // If help is not the last item (i.e. we have extension commands), separate them\n const isLastItem = helpIndex !== items.length - 1\n helpItem = items.splice(\n helpIndex,\n 1,\n ...(isLastItem\n ? [\n {\n separator: true\n }\n ]\n : [])\n )[0]\n }\n helpIndex = items.length\n\n items.splice(\n helpIndex,\n 0,\n ...extraMenuItems.value,\n ...(helpItem\n ? [\n {\n separator: true\n },\n helpItem\n ]\n : [])\n )\n\n return items\n})\n\nconst onMenuShow = () => {\n void nextTick(() => {\n // Force the menu to show submenus on hover\n if (menuRef.value) {\n menuRef.value.dirty = true\n }\n })\n}\n\nconst isZoomCommand = (item: MenuItem) => {\n return (\n item.comfyCommand?.id === 'Comfy.Canvas.ZoomIn' ||\n item.comfyCommand?.id === 'Comfy.Canvas.ZoomOut'\n )\n}\n\nconst handleZoomMouseDown = (item: MenuItem, event: MouseEvent) => {\n if (item.comfyCommand) {\n whileMouseDown(\n event,\n async () => {\n await commandStore.execute(item.comfyCommand!.id)\n },\n 50\n )\n }\n}\n\nconst handleItemClick = (item: MenuItem, event: MouseEvent) => {\n // Prevent the menu from closing for zoom commands or commands that have active state\n if (isZoomCommand(item) || item.comfyCommand?.active) {\n event.preventDefault()\n event.stopPropagation()\n if (item.comfyCommand?.active) {\n item.command?.({\n item,\n originalEvent: event\n })\n }\n return false\n }\n}\n\nconst hasActiveStateSiblings = (item: MenuItem): boolean => {\n // Check if this item has siblings with active state (either from store or theme items)\n return (\n item.parentPath &&\n (item.parentPath === 'theme' ||\n menuItemStore.menuItemHasActiveStateChildren[item.parentPath])\n )\n}\n\nconst handleNodes2ToggleClick = () => {\n return false\n}\n\nconst onNodes2ToggleChange = async (value: boolean) => {\n await settingStore.set('Comfy.VueNodes.Enabled', value)\n telemetry?.trackUiButtonClicked({\n button_id: `menu_nodes_2.0_toggle_${value ? 'enabled' : 'disabled'}`\n })\n}\n</script>\n\n<style scoped>\n.comfy-menu-button-wrapper {\n width: var(--sidebar-width);\n height: var(--sidebar-item-height);\n}\n\n.comfy-menu-button-wrapper:hover {\n background: var(--interface-panel-hover-surface);\n}\n\n.comfy-menu-button-active,\n.comfy-menu-button-active:hover {\n background: var(--interface-panel-selected-surface);\n}\n\n.keybinding-tag {\n background: var(--p-content-hover-background);\n border-color: var(--p-content-border-color);\n border-style: solid;\n}\n</style>\n\n<style>\n.comfy-command-menu {\n --p-tieredmenu-item-focus-background: color-mix(\n in srgb,\n var(--fg-color) 15%,\n transparent\n );\n --p-tieredmenu-item-active-background: color-mix(\n in srgb,\n var(--fg-color) 10%,\n transparent\n );\n}\n\n.comfy-command-menu ul {\n background-color: var(--comfy-menu-bg) !important;\n}\n</style>\n","<template>\n <Button\n v-tooltip=\"{\n value: computedTooltip,\n showDelay: 300,\n hideDelay: 300\n }\"\n :class=\"\n cn(\n 'side-bar-button cursor-pointer border-none',\n selected && 'side-bar-button-selected'\n )\n \"\n variant=\"muted-textonly\"\n :aria-label=\"computedTooltip\"\n @click=\"emit('click', $event)\"\n >\n <div class=\"side-bar-button-content\">\n <slot name=\"icon\">\n <OverlayBadge v-if=\"shouldShowBadge\" :value=\"overlayValue\">\n <i\n v-if=\"typeof icon === 'string'\"\n :class=\"icon + ' side-bar-button-icon'\"\n />\n <component :is=\"icon\" v-else class=\"side-bar-button-icon\" />\n </OverlayBadge>\n <i\n v-else-if=\"typeof icon === 'string'\"\n :class=\"icon + ' side-bar-button-icon'\"\n />\n <component\n :is=\"icon\"\n v-else-if=\"typeof icon === 'object'\"\n class=\"side-bar-button-icon\"\n />\n </slot>\n <span v-if=\"label && !isSmall\" class=\"side-bar-button-label\">{{\n t(label)\n }}</span>\n </div>\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport OverlayBadge from 'primevue/overlaybadge'\nimport { computed } from 'vue'\nimport type { Component } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { t } = useI18n()\nconst {\n icon = '',\n selected = false,\n tooltip = '',\n tooltipSuffix = '',\n iconBadge = '',\n label = '',\n isSmall = false\n} = defineProps<{\n icon?: string | Component\n selected?: boolean\n tooltip?: string\n tooltipSuffix?: string\n iconBadge?: string | (() => string | null)\n label?: string\n isSmall?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'click', event: MouseEvent): void\n}>()\nconst overlayValue = computed(() =>\n typeof iconBadge === 'function' ? (iconBadge() ?? '') : iconBadge\n)\nconst shouldShowBadge = computed(() => !!overlayValue.value)\nconst computedTooltip = computed(() => t(tooltip) + tooltipSuffix)\n</script>\n\n<style>\n.side-bar-button-icon {\n font-size: var(--sidebar-icon-size) !important;\n}\n\n.side-bar-button-selected {\n background-color: var(--interface-panel-selected-surface);\n color: var(--content-hover-fg);\n}\n.side-bar-button:hover {\n background-color: var(--interface-panel-hover-surface);\n color: var(--content-hover-fg);\n}\n\n.side-bar-button-selected .side-bar-button-icon {\n font-size: var(--sidebar-icon-size) !important;\n}\n</style>\n\n<style scoped>\n@reference '../../assets/css/style.css';\n\n.side-bar-button {\n width: var(--sidebar-width);\n height: var(--sidebar-item-height);\n border-radius: 0;\n flex-shrink: 0;\n}\n\n.side-tool-bar-end .side-bar-button {\n height: var(--sidebar-width);\n}\n\n.side-bar-button-content {\n @apply flex flex-col items-center gap-2;\n}\n\n.side-bar-button-label {\n @apply text-[10px] text-center;\n line-height: 1;\n}\n\n.comfyui-body-left .side-bar-button.side-bar-button-selected,\n.comfyui-body-left .side-bar-button.side-bar-button-selected:hover {\n border-left: 4px solid var(--p-button-text-primary-color);\n}\n\n.comfyui-body-right .side-bar-button.side-bar-button-selected,\n.comfyui-body-right .side-bar-button.side-bar-button-selected:hover {\n border-right: 4px solid var(--p-button-text-primary-color);\n}\n</style>\n","<template>\n <SidebarIcon\n icon=\"icon-[ph--terminal-bold]\"\n :label=\"$t('sideToolbar.labels.console')\"\n :tooltip=\"$t('menu.toggleBottomPanel')\"\n :selected=\"bottomPanelStore.activePanel == 'terminal'\"\n @click=\"toggleConsole\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'\n\nimport SidebarIcon from './SidebarIcon.vue'\n\nconst bottomPanelStore = useBottomPanelStore()\n\n/**\n * Toggle console bottom panel and track UI button click.\n */\nconst toggleConsole = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'sidebar_bottom_panel_console_toggled'\n })\n bottomPanelStore.toggleBottomPanel()\n}\n</script>\n","<template>\n <SidebarIcon\n icon=\"icon-[lucide--settings]\"\n :label=\"$t('g.settings')\"\n :tooltip=\"tooltipText\"\n @click=\"showSettingsDialog\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCommandStore } from '@/stores/commandStore'\n\nimport SidebarIcon from './SidebarIcon.vue'\n\nconst { t } = useI18n()\nconst { getCommand, formatKeySequence } = useCommandStore()\nconst command = getCommand('Comfy.ShowSettingsDialog')\n\nconst tooltipText = computed(\n () => `${t('g.settings')} (${formatKeySequence(command)})`\n)\n\n/**\n * Toggle keyboard shortcuts panel and track UI button click.\n */\nconst showSettingsDialog = () => {\n command.function()\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'sidebar_settings_button_clicked'\n })\n}\n</script>\n","<template>\n <SidebarIcon\n icon=\"icon-[lucide--keyboard]\"\n :label=\"$t('shortcuts.shortcuts')\"\n :tooltip=\"tooltipText\"\n :selected=\"isShortcutsPanelVisible\"\n @click=\"toggleShortcutsPanel\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'\n\nimport SidebarIcon from './SidebarIcon.vue'\n\nconst { t } = useI18n()\nconst bottomPanelStore = useBottomPanelStore()\nconst commandStore = useCommandStore()\nconst command = commandStore.getCommand('Workspace.ToggleBottomPanel.Shortcuts')\nconst { formatKeySequence } = commandStore\n\nconst isShortcutsPanelVisible = computed(\n () => bottomPanelStore.activePanel === 'shortcuts'\n)\n\nconst tooltipText = computed(\n () => `${t('shortcuts.keyboardShortcuts')} (${formatKeySequence(command)})`\n)\n\n/**\n * Toggle keyboard shortcuts panel and track UI button click.\n */\nconst toggleShortcutsPanel = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'sidebar_shortcuts_panel_toggled'\n })\n bottomPanelStore.togglePanel('shortcuts')\n}\n</script>\n","<template>\n <SidebarIcon\n icon=\"pi pi-question-circle\"\n class=\"comfy-help-center-btn\"\n :label=\"$t('menu.help')\"\n :tooltip=\"$t('sideToolbar.helpCenter')\"\n :icon-badge=\"shouldShowRedDot ? '•' : ''\"\n :is-small=\"isSmall\"\n @click=\"toggleHelpCenter\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { useHelpCenter } from '@/composables/useHelpCenter'\n\nimport SidebarIcon from './SidebarIcon.vue'\n\ndefineProps<{\n isSmall: boolean\n}>()\n\nconst { shouldShowRedDot, toggleHelpCenter } = useHelpCenter()\n</script>\n\n<style scoped>\n:deep(.p-badge) {\n background: #ff3b30;\n color: #ff3b30;\n min-width: 8px;\n height: 8px;\n padding: 0;\n border-radius: 9999px;\n font-size: 0;\n margin-top: 4px;\n margin-right: 4px;\n border: none;\n outline: none;\n box-shadow: none;\n}\n\n:deep(.p-badge.p-badge-dot) {\n width: 8px !important;\n}\n</style>\n","<template>\n <SidebarIcon\n icon=\"pi pi-sign-out\"\n :tooltip=\"tooltip\"\n :label=\"$t('sideToolbar.logout')\"\n @click=\"logout\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useUserStore } from '@/stores/userStore'\n\nimport SidebarIcon from './SidebarIcon.vue'\n\nconst { t } = useI18n()\nconst userStore = useUserStore()\n\nconst tooltip = computed(\n () => `${t('sideToolbar.logout')} (${userStore.currentUser?.username})`\n)\nconst logout = async () => {\n await userStore.logout()\n window.location.reload()\n}\n</script>\n","<template>\n <SidebarIcon\n icon=\"icon-[comfy--template]\"\n :tooltip=\"$t('sideToolbar.templates')\"\n :label=\"$t('sideToolbar.labels.templates')\"\n :is-small=\"isSmall\"\n class=\"templates-tab-button\"\n @click=\"openTemplates\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\n\nimport SidebarIcon from './SidebarIcon.vue'\n\nconst settingStore = useSettingStore()\n\nconst isSmall = computed(\n () => settingStore.get('Comfy.Sidebar.Size') === 'small'\n)\n\n/**\n * Open templates dialog from sidebar and track UI button click.\n */\nconst openTemplates = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'sidebar_templates_dialog_opened'\n })\n useWorkflowTemplateSelectorDialog().show('sidebar')\n}\n</script>\n","<template>\n <nav\n ref=\"sideToolbarRef\"\n class=\"side-tool-bar-container flex h-full flex-col items-center bg-transparent [.floating-sidebar]:-mr-2\"\n :class=\"{\n 'small-sidebar': isSmall,\n 'connected-sidebar pointer-events-auto': isConnected,\n 'floating-sidebar': !isConnected,\n 'overflowing-sidebar': isOverflowing,\n 'border-r border-[var(--interface-stroke)] shadow-interface': isConnected\n }\"\n >\n <div\n :class=\"\n isOverflowing\n ? 'side-tool-bar-container overflow-y-auto'\n : 'flex flex-col h-full'\n \"\n >\n <div ref=\"topToolbarRef\" :class=\"groupClasses\">\n <ComfyMenuButton />\n <SidebarIcon\n v-for=\"tab in tabs\"\n :key=\"tab.id\"\n :icon=\"tab.icon\"\n :icon-badge=\"tab.iconBadge\"\n :tooltip=\"tab.tooltip\"\n :tooltip-suffix=\"getTabTooltipSuffix(tab)\"\n :label=\"tab.label || tab.title\"\n :is-small=\"isSmall\"\n :selected=\"tab.id === selectedTab?.id\"\n :class=\"tab.id + '-tab-button'\"\n @click=\"onTabClick(tab)\"\n />\n <SidebarTemplatesButton />\n </div>\n\n <div ref=\"bottomToolbarRef\" class=\"mt-auto\" :class=\"groupClasses\">\n <SidebarLogoutIcon\n v-if=\"userStore.isMultiUserServer\"\n :is-small=\"isSmall\"\n />\n <SidebarHelpCenterIcon v-if=\"!isIntegratedTabBar\" :is-small=\"isSmall\" />\n <SidebarBottomPanelToggleButton :is-small=\"isSmall\" />\n <SidebarShortcutsToggleButton :is-small=\"isSmall\" />\n <SidebarSettingsButton :is-small=\"isSmall\" />\n </div>\n </div>\n <HelpCenterPopups :is-small=\"isSmall\" />\n </nav>\n</template>\n\n<script setup lang=\"ts\">\nimport { useResizeObserver } from '@vueuse/core'\nimport { debounce } from 'es-toolkit/compat'\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport HelpCenterPopups from '@/components/helpcenter/HelpCenterPopups.vue'\nimport ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'\nimport SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'\nimport SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue'\nimport SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useKeybindingStore } from '@/stores/keybindingStore'\nimport { useUserStore } from '@/stores/userStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\nimport type { SidebarTabExtension } from '@/types/extensionTypes'\nimport { cn } from '@/utils/tailwindUtil'\n\nimport SidebarHelpCenterIcon from './SidebarHelpCenterIcon.vue'\nimport SidebarIcon from './SidebarIcon.vue'\nimport SidebarLogoutIcon from './SidebarLogoutIcon.vue'\nimport SidebarTemplatesButton from './SidebarTemplatesButton.vue'\n\nconst workspaceStore = useWorkspaceStore()\nconst settingStore = useSettingStore()\nconst userStore = useUserStore()\nconst commandStore = useCommandStore()\nconst canvasStore = useCanvasStore()\nconst sideToolbarRef = ref<HTMLElement>()\nconst topToolbarRef = ref<HTMLElement>()\nconst bottomToolbarRef = ref<HTMLElement>()\n\nconst isSmall = computed(\n () => settingStore.get('Comfy.Sidebar.Size') === 'small'\n)\nconst sidebarLocation = computed<'left' | 'right'>(() =>\n settingStore.get('Comfy.Sidebar.Location')\n)\nconst sidebarStyle = computed(() => settingStore.get('Comfy.Sidebar.Style'))\nconst isIntegratedTabBar = computed(\n () => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'\n)\nconst isConnected = computed(\n () =>\n selectedTab.value ||\n isOverflowing.value ||\n sidebarStyle.value === 'connected'\n)\n\nconst tabs = computed(() => workspaceStore.getSidebarTabs())\nconst selectedTab = computed(() => workspaceStore.sidebarTab.activeSidebarTab)\n\n/**\n * Handle sidebar tab icon click.\n * - Emits UI button telemetry for known tabs\n * - Delegates to the corresponding toggle command\n */\nconst onTabClick = async (item: SidebarTabExtension) => {\n const telemetry = useTelemetry()\n\n const isNodeLibraryTab = item.id === 'node-library'\n const isModelLibraryTab = item.id === 'model-library'\n const isWorkflowsTab = item.id === 'workflows'\n const isAssetsTab = item.id === 'assets'\n\n if (isNodeLibraryTab)\n telemetry?.trackUiButtonClicked({\n button_id: 'sidebar_tab_node_library_selected'\n })\n else if (isModelLibraryTab)\n telemetry?.trackUiButtonClicked({\n button_id: 'sidebar_tab_model_library_selected'\n })\n else if (isWorkflowsTab)\n telemetry?.trackUiButtonClicked({\n button_id: 'sidebar_tab_workflows_selected'\n })\n else if (isAssetsTab)\n telemetry?.trackUiButtonClicked({\n button_id: 'sidebar_tab_assets_media_selected'\n })\n\n await commandStore.commands\n .find((cmd) => cmd.id === `Workspace.ToggleSidebarTab.${item.id}`)\n ?.function?.()\n}\n\nconst keybindingStore = useKeybindingStore()\nconst getTabTooltipSuffix = (tab: SidebarTabExtension) => {\n const keybinding = keybindingStore.getKeybindingByCommandId(\n `Workspace.ToggleSidebarTab.${tab.id}`\n )\n return keybinding ? ` (${keybinding.combo.toString()})` : ''\n}\n\nconst isOverflowing = ref(false)\nconst groupClasses = computed(() =>\n cn(\n 'sidebar-item-group flex flex-col items-center overflow-hidden flex-shrink-0',\n !isConnected.value && 'rounded-lg shadow-interface pointer-events-auto'\n )\n)\n\nconst ENTER_OVERFLOW_MARGIN = 20\nconst EXIT_OVERFLOW_MARGIN = 50\n\nconst checkOverflow = debounce(() => {\n if (!sideToolbarRef.value || !topToolbarRef.value || !bottomToolbarRef.value)\n return\n\n const containerHeight = sideToolbarRef.value.clientHeight\n const topHeight = topToolbarRef.value.scrollHeight\n const bottomHeight = bottomToolbarRef.value.scrollHeight\n const contentHeight = topHeight + bottomHeight\n\n if (isOverflowing.value) {\n isOverflowing.value = containerHeight < contentHeight + EXIT_OVERFLOW_MARGIN\n } else {\n isOverflowing.value =\n containerHeight < contentHeight + ENTER_OVERFLOW_MARGIN\n }\n}, 16)\n\nonMounted(() => {\n if (!sideToolbarRef.value) return\n\n const overflowObserver = useResizeObserver(\n sideToolbarRef.value,\n checkOverflow\n )\n\n checkOverflow()\n\n onBeforeUnmount(() => {\n overflowObserver.stop()\n })\n\n watch(\n [isSmall, sidebarLocation],\n async () => {\n if (canvasStore.canvas) {\n if (sidebarLocation.value === 'left') {\n await nextTick()\n canvasStore.canvas.fpsInfoLocation = [\n sideToolbarRef.value?.getBoundingClientRect()?.right,\n null\n ]\n } else {\n canvasStore.canvas.fpsInfoLocation = null\n }\n canvasStore.canvas.setDirty(false, true)\n }\n },\n { immediate: true }\n )\n})\n</script>\n\n<style>\n/* Global CSS variables for sidebar\n * These variables need to be global (not scoped) because they are used by\n * teleported components like WhatsNewPopup that render outside the sidebar\n * but need to reference sidebar dimensions for proper positioning.\n */\n:root {\n --sidebar-padding: 4px;\n --sidebar-icon-size: 1rem;\n\n --sidebar-default-floating-width: 48px;\n --sidebar-default-connected-width: calc(\n var(--sidebar-default-floating-width) + var(--sidebar-padding) * 2\n );\n --sidebar-default-item-height: 56px;\n\n --sidebar-small-floating-width: 48px;\n --sidebar-small-connected-width: calc(\n var(--sidebar-small-floating-width) + var(--sidebar-padding) * 2\n );\n --sidebar-small-item-height: 48px;\n\n --sidebar-width: var(--sidebar-default-floating-width);\n --sidebar-item-height: var(--sidebar-default-item-height);\n}\n\n:root:has(.side-tool-bar-container.small-sidebar) {\n --sidebar-width: var(--sidebar-small-floating-width);\n --sidebar-item-height: var(--sidebar-small-item-height);\n}\n\n:root:has(.side-tool-bar-container.connected-sidebar) {\n --sidebar-width: var(--sidebar-default-connected-width);\n}\n\n:root:has(.side-tool-bar-container.small-sidebar.connected-sidebar) {\n --sidebar-width: var(--sidebar-small-connected-width);\n}\n</style>\n\n<style scoped>\n@reference \"tailwindcss\";\n\n.floating-sidebar {\n padding: var(--sidebar-padding);\n}\n\n.floating-sidebar .sidebar-item-group {\n border-color: var(--p-panel-border-color);\n}\n\n.connected-sidebar {\n padding: var(--sidebar-padding) 0;\n background-color: var(--comfy-menu-bg);\n}\n\n.sidebar-item-group {\n background-color: var(--comfy-menu-bg);\n border: 1px solid transparent;\n}\n\n.overflowing-sidebar :deep(.comfy-menu-button-wrapper) {\n position: sticky;\n top: 0;\n z-index: 1;\n background-color: var(--comfy-menu-bg);\n}\n</style>\n","import { defineStore } from 'pinia'\nimport { computed } from 'vue'\n\nimport type { TopbarBadge } from '@/types/comfy'\n\nimport { useExtensionStore } from './extensionStore'\n\nexport const useTopbarBadgeStore = defineStore('topbarBadge', () => {\n const extensionStore = useExtensionStore()\n\n const badges = computed<TopbarBadge[]>(() =>\n extensionStore.extensions.flatMap((e) => e.topbarBadges ?? [])\n )\n\n return {\n badges\n }\n})\n","<template>\n <div class=\"flex h-full shrink-0 items-center\">\n <TopbarBadge\n v-for=\"badge in topbarBadgeStore.badges\"\n :key=\"badge.text\"\n :badge\n :display-mode=\"displayMode\"\n :reverse-order=\"reverseOrder\"\n :no-padding=\"noPadding\"\n />\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { breakpointsTailwind, useBreakpoints } from '@vueuse/core'\nimport { computed } from 'vue'\n\nimport { useTopbarBadgeStore } from '@/stores/topbarBadgeStore'\n\nimport TopbarBadge from './TopbarBadge.vue'\n\nwithDefaults(\n defineProps<{\n reverseOrder?: boolean\n noPadding?: boolean\n }>(),\n {\n reverseOrder: false,\n noPadding: false\n }\n)\n\nconst breakpoints = useBreakpoints(breakpointsTailwind)\nconst isXl = breakpoints.greaterOrEqual('xl')\nconst isLg = breakpoints.greaterOrEqual('lg')\n\nconst displayMode = computed<'full' | 'compact' | 'icon-only'>(() => {\n if (isXl.value) return 'full'\n if (isLg.value) return 'compact'\n return 'icon-only'\n})\n\nconst topbarBadgeStore = useTopbarBadgeStore()\n</script>\n","<template>\n <span class=\"relative inline-flex items-center justify-center size-[1em]\">\n <i :class=\"mainIcon\" class=\"text-[1em]\" />\n <i\n :class=\"\n cn(\n subIcon,\n 'absolute leading-none pointer-events-none',\n positionX === 'left' ? 'left-0' : 'right-0',\n positionY === 'top' ? 'top-0' : 'bottom-0'\n )\n \"\n :style=\"subIconStyle\"\n />\n </span>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { cn } from '@/utils/tailwindUtil'\n\ntype Position = 'top' | 'bottom' | 'left' | 'right'\n\nexport interface OverlayIconProps {\n mainIcon: string\n subIcon: string\n positionX?: Position\n positionY?: Position\n offsetX?: number\n offsetY?: number\n subIconScale?: number\n}\nconst {\n mainIcon,\n subIcon,\n positionX = 'right',\n positionY = 'bottom',\n offsetX = 0,\n offsetY = 0,\n subIconScale = 0.6\n} = defineProps<OverlayIconProps>()\n\nconst textShadow = [\n `-1px -1px 0 rgba(0, 0, 0, 0.7)`,\n `1px -1px 0 rgba(0, 0, 0, 0.7)`,\n `-1px 1px 0 rgba(0, 0, 0, 0.7)`,\n `1px 1px 0 rgba(0, 0, 0, 0.7)`,\n `-1px 0 0 rgba(0, 0, 0, 0.7)`,\n `1px 0 0 rgba(0, 0, 0, 0.7)`,\n `0 -1px 0 rgba(0, 0, 0, 0.7)`,\n `0 1px 0 rgba(0, 0, 0, 0.7)`\n].join(', ')\n\nconst subIconStyle = computed(() => ({\n fontSize: `${subIconScale}em`,\n textShadow,\n ...(offsetX !== 0 && {\n [positionX === 'left' ? 'left' : 'right']: `${offsetX}px`\n }),\n ...(offsetY !== 0 && {\n [positionY === 'top' ? 'top' : 'bottom']: `${offsetY}px`\n })\n}))\n</script>\n","<template>\n <Button\n class=\"comfy-help-center-btn relative text-base-foreground\"\n variant=\"textonly\"\n @click=\"toggleHelpCenter\"\n >\n {{ $t('menu.helpAndFeedback') }}\n <i class=\"icon-[lucide--circle-help] ml-0.5\" />\n <span\n v-if=\"shouldShowRedDot\"\n class=\"absolute top-[7px] right-[7px] size-1.5 rounded-full bg-[#ff3b30]\"\n />\n </Button>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\nimport { useHelpCenter } from '@/composables/useHelpCenter'\n\nconst { shouldShowRedDot, toggleHelpCenter } = useHelpCenter('topbar')\n</script>\n","<template>\n <div\n ref=\"positionRef\"\n class=\"absolute bottom-0 left-1/2 -translate-x-1/2\"\n ></div>\n <Popover\n ref=\"popoverRef\"\n append-to=\"body\"\n :pt=\"{\n root: {\n class: 'workflow-popover-fade fit-content',\n 'data-popover-id': id\n }\n }\"\n @mouseenter=\"cancelHidePopover\"\n @mouseleave=\"hidePopover\"\n >\n <div class=\"workflow-preview-content\">\n <div\n v-if=\"thumbnailUrl && !isActiveTab\"\n class=\"workflow-preview-thumbnail relative\"\n >\n <img\n :src=\"thumbnailUrl\"\n class=\"block h-[200px] rounded-lg object-cover p-2\"\n :style=\"{ width: `${POPOVER_WIDTH}px` }\"\n />\n </div>\n <div class=\"workflow-preview-footer\">\n <span class=\"workflow-preview-name\">{{ workflowFilename }}</span>\n </div>\n </div>\n </Popover>\n</template>\n\n<script setup lang=\"ts\">\nimport Popover from 'primevue/popover'\nimport { nextTick, ref, toRefs, useId } from 'vue'\n\nconst POPOVER_WIDTH = 250\n\ninterface Props {\n workflowFilename: string\n thumbnailUrl?: string\n isActiveTab: boolean\n}\n\nconst props = defineProps<Props>()\nconst { thumbnailUrl, isActiveTab } = toRefs(props)\nconst popoverRef = ref<InstanceType<typeof Popover> | null>(null)\nconst positionRef = ref<HTMLElement | null>(null)\nlet hideTimeout: ReturnType<typeof setTimeout> | null = null\nlet showTimeout: ReturnType<typeof setTimeout> | null = null\nconst id = useId()\n\nconst showPopover = (event: Event) => {\n // Clear any existing timeouts\n if (hideTimeout) {\n clearTimeout(hideTimeout)\n hideTimeout = null\n }\n if (showTimeout) {\n clearTimeout(showTimeout)\n showTimeout = null\n }\n\n // Show popover after a short delay\n showTimeout = setTimeout(async () => {\n if (popoverRef.value && positionRef.value) {\n popoverRef.value.show(event, positionRef.value)\n await nextTick()\n // PrimeVue has a bug where when the tabs are scrolled, it positions the element incorrectly\n // Manually set the position to the middle of the tab and prevent it from going off the left/right edge\n const el = document.querySelector(\n `.workflow-popover-fade[data-popover-id=\"${id}\"]`\n ) as HTMLElement\n if (el) {\n const middle = positionRef.value!.getBoundingClientRect().left\n const popoverWidth = el.getBoundingClientRect().width\n const halfWidth = popoverWidth / 2\n let pos = middle - halfWidth\n let shift = 0\n\n // Calculate shift when clamping is needed\n if (pos < 0) {\n shift = pos - 8 // Negative shift to move arrow left\n pos = 8\n } else if (pos + popoverWidth > window.innerWidth) {\n const newPos = window.innerWidth - popoverWidth - 16\n shift = pos - newPos // Positive shift to move arrow right\n pos = newPos\n }\n\n if (shift + halfWidth < 0) {\n shift = -halfWidth + 24\n }\n\n el.style.left = `${pos}px`\n el.style.setProperty('--shift', `${shift}px`)\n }\n }\n }, 200) // 200ms delay before showing\n}\n\nconst cancelHidePopover = () => {\n // Temporarily disable this functionality until we need the popover to be interactive:\n /*\n if (hideTimeout) {\n clearTimeout(hideTimeout)\n hideTimeout = null\n }\n */\n}\n\nconst hidePopover = () => {\n // Clear show timeout if mouse leaves before popover appears\n if (showTimeout) {\n clearTimeout(showTimeout)\n showTimeout = null\n }\n\n hideTimeout = setTimeout(() => {\n if (popoverRef.value) {\n popoverRef.value.hide()\n }\n }, 100) // Minimal delay to allow moving to popover\n}\n\nconst togglePopover = (event: Event) => {\n if (popoverRef.value) {\n popoverRef.value.toggle(event)\n }\n}\n\ndefineExpose({\n showPopover,\n hidePopover,\n togglePopover\n})\n</script>\n\n<style scoped>\n@reference '../../assets/css/style.css';\n\n.workflow-preview-content {\n @apply flex flex-col rounded-xl overflow-hidden;\n max-width: var(--popover-width);\n background-color: var(--comfy-menu-bg);\n color: var(--fg-color);\n}\n\n.workflow-preview-thumbnail {\n @apply relative p-2;\n}\n\n.workflow-preview-thumbnail img {\n @apply shadow-md;\n background-color: color-mix(in srgb, var(--comfy-menu-bg) 70%, black);\n}\n\n.dark-theme .workflow-preview-thumbnail img {\n @apply shadow-lg;\n}\n\n.workflow-preview-footer {\n @apply pt-1 pb-2 px-3;\n}\n\n.workflow-preview-name {\n @apply block text-sm font-medium overflow-hidden text-ellipsis whitespace-nowrap;\n color: var(--fg-color);\n}\n</style>\n\n<style>\n@reference '../../assets/css/style.css';\n\n.workflow-popover-fade {\n --p-popover-background: transparent;\n --p-popover-content-padding: 0;\n @apply bg-transparent rounded-xl shadow-lg;\n transition: opacity 0.15s ease-out !important;\n}\n\n.workflow-popover-fade.p-popover-flipped {\n @apply -translate-y-full;\n}\n\n.dark-theme .workflow-popover-fade {\n @apply shadow-2xl;\n}\n\n.workflow-popover-fade.p-popover::after,\n.workflow-popover-fade.p-popover::before {\n --p-popover-border-color: var(--comfy-menu-secondary-bg);\n left: 50%;\n transform: translateX(calc(-50% + var(--shift)));\n margin-left: 0;\n}\n</style>\n","<template>\n <div\n ref=\"workflowTabRef\"\n class=\"workflow-tab group flex gap-2 p-2\"\n v-bind=\"$attrs\"\n @mouseenter=\"handleMouseEnter\"\n @mouseleave=\"handleMouseLeave\"\n @click=\"handleClick\"\n >\n <Button\n v-if=\"isActiveTab\"\n class=\"context-menu-button -mx-1 w-auto px-1 py-0\"\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n @click.stop=\"handleMenuClick\"\n >\n <i class=\"pi pi-bars\" />\n </Button>\n <span class=\"workflow-label inline-block max-w-[150px] truncate text-sm\">\n {{ workflowOption.workflow.filename }}\n </span>\n <div class=\"relative\">\n <span\n v-if=\"shouldShowStatusIndicator\"\n class=\"absolute top-1/2 left-1/2 z-10 w-4 -translate-1/2 bg-(--comfy-menu-bg) text-2xl font-bold group-hover:hidden\"\n >•</span\n >\n <Button\n class=\"close-button invisible w-auto p-0\"\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n :aria-label=\"t('g.close')\"\n @click.stop=\"onCloseWorkflow(workflowOption)\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n </div>\n </div>\n\n <WorkflowTabPopover\n ref=\"popoverRef\"\n :workflow-filename=\"workflowOption.workflow.filename\"\n :thumbnail-url=\"thumbnailUrl\"\n :is-active-tab=\"isActiveTab\"\n />\n\n <Menu\n v-if=\"isActiveTab\"\n ref=\"menu\"\n :model=\"menuItems\"\n :popup=\"true\"\n :pt=\"{\n root: {\n style: 'background-color: var(--comfy-menu-bg)'\n },\n itemLink: {\n class: 'py-2'\n }\n }\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport type { MenuState } from 'primevue/menu'\nimport Menu from 'primevue/menu'\nimport { computed, onUnmounted, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport {\n usePragmaticDraggable,\n usePragmaticDroppable\n} from '@/composables/usePragmaticDragAndDrop'\nimport { useWorkflowActionsMenu } from '@/composables/useWorkflowActionsMenu'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\n\nimport WorkflowTabPopover from './WorkflowTabPopover.vue'\n\ninterface WorkflowOption {\n value: string\n workflow: ComfyWorkflow\n}\n\nconst props = defineProps<{\n workflowOption: WorkflowOption\n}>()\n\nconst { t } = useI18n()\n\nconst workspaceStore = useWorkspaceStore()\nconst workflowStore = useWorkflowStore()\nconst settingStore = useSettingStore()\nconst workflowTabRef = ref<HTMLElement | null>(null)\nconst popoverRef = ref<InstanceType<typeof WorkflowTabPopover> | null>(null)\nconst workflowThumbnail = useWorkflowThumbnail()\n\n// Use computed refs to cache autosave settings\nconst autoSaveSetting = computed(() =>\n settingStore.get('Comfy.Workflow.AutoSave')\n)\nconst autoSaveDelay = computed(() =>\n settingStore.get('Comfy.Workflow.AutoSaveDelay')\n)\n\nconst shouldShowStatusIndicator = computed(() => {\n if (workspaceStore.shiftDown) {\n // Branch 1: Shift key is held down, do not show the status indicator.\n return false\n }\n if (!props.workflowOption.workflow.isPersisted) {\n // Branch 2: Workflow is not persisted, show the status indicator.\n return true\n }\n if (props.workflowOption.workflow.isModified) {\n // Branch 3: Workflow is modified.\n if (autoSaveSetting.value === 'off') {\n // Sub-branch 3a: Autosave is off, so show the status indicator.\n return true\n }\n if (autoSaveSetting.value === 'after delay' && autoSaveDelay.value > 3000) {\n // Sub-branch 3b: Autosave delay is too high, so show the status indicator.\n return true\n }\n // Sub-branch 3c: Workflow is modified but no condition applies, do not show the status indicator.\n return false\n }\n // Default: do not show the status indicator. This should not be reachable.\n return false\n})\n\nconst isActiveTab = computed(() => {\n return workflowStore.activeWorkflow?.key === props.workflowOption.workflow.key\n})\n\nconst thumbnailUrl = computed(() => {\n return workflowThumbnail.getThumbnail(props.workflowOption.workflow.key)\n})\n\nconst menu = ref<InstanceType<typeof Menu> & MenuState>()\n\nconst { menuItems } = useWorkflowActionsMenu(() =>\n useCommandStore().execute('Comfy.RenameWorkflow')\n)\n\n// Event handlers that delegate to the popover component\nconst handleMouseEnter = (event: Event) => {\n popoverRef.value?.showPopover(event)\n}\n\nconst handleMouseLeave = () => {\n popoverRef.value?.hidePopover()\n}\n\nconst handleClick = (event: Event) => {\n popoverRef.value?.togglePopover(event)\n}\n\nconst handleMenuClick = (event: MouseEvent) => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'workflow_tab_menu_selected'\n })\n // Show breadcrumb menu instead of emitting context click\n menu.value?.toggle(event)\n}\n\nconst closeWorkflows = async (options: WorkflowOption[]) => {\n for (const opt of options) {\n if (\n !(await useWorkflowService().closeWorkflow(opt.workflow, {\n warnIfUnsaved: !workspaceStore.shiftDown,\n hint: t('sideToolbar.workflowTab.dirtyCloseHint')\n }))\n ) {\n // User clicked cancel\n break\n }\n }\n}\n\nconst onCloseWorkflow = async (option: WorkflowOption) => {\n await closeWorkflows([option])\n}\nconst tabGetter = () => workflowTabRef.value as HTMLElement\n\nusePragmaticDraggable(tabGetter, {\n getInitialData: () => {\n return {\n workflowKey: props.workflowOption.workflow.key\n }\n }\n})\n\nusePragmaticDroppable(tabGetter, {\n getData: () => {\n return {\n workflowKey: props.workflowOption.workflow.key\n }\n },\n onDrop: (e) => {\n const fromIndex = workflowStore.openWorkflows.findIndex(\n (wf) => wf.key === e.source.data.workflowKey\n )\n const toIndex = workflowStore.openWorkflows.findIndex(\n (wf) => wf.key === e.location.current.dropTargets[0]?.data.workflowKey\n )\n if (fromIndex !== toIndex) {\n workflowStore.reorderWorkflows(fromIndex, toIndex)\n }\n }\n})\n\nonUnmounted(() => {\n popoverRef.value?.hidePopover()\n})\n</script>\n\n<style>\n.p-tooltip.workflow-tab-tooltip {\n z-index: 1200 !important;\n}\n</style>\n","<template>\n <div>\n <Button\n v-tooltip=\"{ value: $t('g.moreWorkflows'), showDelay: 300 }\"\n class=\"rounded-none h-full w-auto aspect-square\"\n variant=\"muted-textonly\"\n size=\"icon\"\n :aria-label=\"$t('g.moreWorkflows')\"\n @click=\"menu?.toggle($event)\"\n >\n <i class=\"pi pi-ellipsis-h\" />\n </Button>\n <Menu\n ref=\"menu\"\n :model=\"menuItems\"\n :popup=\"true\"\n class=\"max-h-[40vh] overflow-auto\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Menu from 'primevue/menu'\nimport { computed, ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'\n\nconst props = defineProps<{\n workflows: ComfyWorkflow[]\n activeWorkflow: ComfyWorkflow | null\n}>()\n\nconst menu = ref<InstanceType<typeof Menu> | null>(null)\nconst workflowService = useWorkflowService()\n\nconst menuItems = computed(() =>\n props.workflows.map((workflow: ComfyWorkflow) => ({\n label: workflow.filename,\n icon:\n props.activeWorkflow?.key === workflow.key ? 'pi pi-check' : undefined,\n command: () => {\n void workflowService.openWorkflow(workflow)\n }\n }))\n)\n</script>\n","<template>\n <div\n ref=\"containerRef\"\n class=\"workflow-tabs-container flex h-full max-w-full flex-auto flex-row overflow-hidden\"\n :class=\"{ 'workflow-tabs-container-desktop': isDesktop }\"\n >\n <Button\n v-if=\"showOverflowArrows\"\n variant=\"muted-textonly\"\n size=\"icon\"\n class=\"overflow-arrow overflow-arrow-left h-full w-auto aspect-square\"\n :aria-label=\"$t('g.scrollLeft')\"\n :disabled=\"!leftArrowEnabled\"\n @mousedown=\"whileMouseDown($event, () => scroll(-1))\"\n >\n <i class=\"icon-[lucide--chevron-left] size-full\" />\n </Button>\n <ScrollPanel\n class=\"no-drag overflow-hidden\"\n :pt:content=\"{\n class: 'p-0 w-full flex',\n onwheel: handleWheel\n }\"\n pt:bar-x=\"h-1\"\n >\n <SelectButton\n class=\"workflow-tabs bg-transparent\"\n :class=\"props.class\"\n :model-value=\"selectedWorkflow\"\n :options=\"options\"\n option-label=\"label\"\n data-key=\"value\"\n @update:model-value=\"onWorkflowChange\"\n >\n <template #option=\"{ option }\">\n <WorkflowTab\n :workflow-option=\"option\"\n @contextmenu=\"showContextMenu($event, option)\"\n @click.middle=\"onCloseWorkflow(option)\"\n />\n </template>\n </SelectButton>\n </ScrollPanel>\n <Button\n v-if=\"showOverflowArrows\"\n variant=\"muted-textonly\"\n size=\"icon\"\n class=\"overflow-arrow overflow-arrow-right h-full w-auto aspect-square\"\n :aria-label=\"$t('g.scrollRight')\"\n :disabled=\"!rightArrowEnabled\"\n @mousedown=\"whileMouseDown($event, () => scroll(1))\"\n >\n <i class=\"icon-[lucide--chevron-right] size-full\" />\n </Button>\n <WorkflowOverflowMenu\n v-if=\"showOverflowArrows\"\n :workflows=\"workflowStore.openWorkflows\"\n :active-workflow=\"workflowStore.activeWorkflow\"\n />\n <Button\n v-tooltip=\"{ value: $t('sideToolbar.newBlankWorkflow'), showDelay: 300 }\"\n class=\"new-blank-workflow-button no-drag shrink-0 rounded-none h-full w-auto aspect-square\"\n variant=\"muted-textonly\"\n size=\"icon\"\n :aria-label=\"$t('sideToolbar.newBlankWorkflow')\"\n @click=\"() => commandStore.execute('Comfy.NewBlankWorkflow')\"\n >\n <i class=\"pi pi-plus\" />\n </Button>\n <div\n v-if=\"isIntegratedTabBar\"\n class=\"ml-auto flex shrink-0 items-center gap-2 px-2\"\n >\n <TopMenuHelpButton />\n <CurrentUserButton\n v-if=\"isLoggedIn\"\n :show-arrow=\"false\"\n compact\n class=\"shrink-0 p-1\"\n />\n <LoginButton v-else-if=\"isDesktop\" class=\"p-1\" />\n </div>\n <ContextMenu ref=\"menu\" :model=\"contextMenuItems\">\n <template #itemicon=\"{ item }\">\n <OverlayIcon v-if=\"item.overlayIcon\" v-bind=\"item.overlayIcon\" />\n <i v-else-if=\"item.icon\" :class=\"item.icon\" />\n </template>\n </ContextMenu>\n <div v-if=\"isDesktop\" class=\"window-actions-spacer app-drag shrink-0\" />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useScroll } from '@vueuse/core'\nimport ContextMenu from 'primevue/contextmenu'\nimport ScrollPanel from 'primevue/scrollpanel'\nimport SelectButton from 'primevue/selectbutton'\nimport { computed, nextTick, onUpdated, ref, watch } from 'vue'\nimport type { WatchStopHandle } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport OverlayIcon from '@/components/common/OverlayIcon.vue'\nimport type { OverlayIconProps } from '@/components/common/OverlayIcon.vue'\nimport CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'\nimport LoginButton from '@/components/topbar/LoginButton.vue'\nimport TopMenuHelpButton from '@/components/topbar/TopMenuHelpButton.vue'\nimport WorkflowTab from '@/components/topbar/WorkflowTab.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useOverflowObserver } from '@/composables/element/useOverflowObserver'\nimport { useWorkflowActionsMenu } from '@/composables/useWorkflowActionsMenu'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\nimport { isElectron } from '@/utils/envUtil'\nimport { whileMouseDown } from '@/utils/mouseDownUtil'\n\nimport WorkflowOverflowMenu from './WorkflowOverflowMenu.vue'\n\ninterface WorkflowOption {\n value: string\n workflow: ComfyWorkflow\n}\n\nconst props = defineProps<{\n class?: string\n}>()\n\nconst { t } = useI18n()\nconst settingStore = useSettingStore()\nconst workspaceStore = useWorkspaceStore()\nconst workflowStore = useWorkflowStore()\nconst workflowService = useWorkflowService()\nconst commandStore = useCommandStore()\nconst { isLoggedIn } = useCurrentUser()\n\nconst isIntegratedTabBar = computed(\n () => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'\n)\n\nconst rightClickedTab = ref<WorkflowOption | undefined>()\nconst menu = ref()\nconst containerRef = ref<HTMLElement | null>(null)\nconst showOverflowArrows = ref(false)\nconst leftArrowEnabled = ref(false)\nconst rightArrowEnabled = ref(false)\n\nconst isDesktop = isElectron()\n\nconst workflowToOption = (workflow: ComfyWorkflow): WorkflowOption => ({\n value: workflow.path,\n workflow\n})\n\nconst options = computed<WorkflowOption[]>(() =>\n workflowStore.openWorkflows.map(workflowToOption)\n)\nconst selectedWorkflow = computed<WorkflowOption | null>(() =>\n workflowStore.activeWorkflow\n ? workflowToOption(workflowStore.activeWorkflow as ComfyWorkflow)\n : null\n)\n\nconst onWorkflowChange = async (option: WorkflowOption) => {\n // Prevent unselecting the current workflow\n if (!option) {\n return\n }\n // Prevent reloading the current workflow\n if (selectedWorkflow.value?.value === option.value) {\n return\n }\n\n await workflowService.openWorkflow(option.workflow)\n}\n\nconst closeWorkflows = async (options: WorkflowOption[]) => {\n for (const opt of options) {\n if (\n !(await workflowService.closeWorkflow(opt.workflow, {\n warnIfUnsaved: !workspaceStore.shiftDown\n }))\n ) {\n // User clicked cancel\n break\n }\n }\n}\n\nconst onCloseWorkflow = async (option: WorkflowOption) => {\n await closeWorkflows([option])\n}\n\nconst showContextMenu = (event: MouseEvent, option: WorkflowOption) => {\n rightClickedTab.value = option\n menu.value.show(event)\n}\n\nconst rightClickedWorkflow = computed(\n () => rightClickedTab.value?.workflow ?? null\n)\n\nconst { menuItems: baseMenuItems } = useWorkflowActionsMenu(\n () => commandStore.execute('Comfy.RenameWorkflow'),\n {\n includeDelete: false,\n workflow: rightClickedWorkflow\n }\n)\n\nconst contextMenuItems = computed(() => {\n const tab = rightClickedTab.value\n if (!tab) return []\n const index = options.value.findIndex((v) => v.workflow === tab.workflow)\n\n return [\n ...baseMenuItems.value,\n {\n label: t('tabMenu.closeTab'),\n icon: 'pi pi-times',\n command: () => onCloseWorkflow(tab)\n },\n {\n label: t('tabMenu.closeTabsToLeft'),\n overlayIcon: {\n mainIcon: 'pi pi-times',\n subIcon: 'pi pi-arrow-left',\n positionX: 'right',\n positionY: 'bottom',\n subIconScale: 0.5\n } as OverlayIconProps,\n command: () => closeWorkflows(options.value.slice(0, index)),\n disabled: index <= 0\n },\n {\n label: t('tabMenu.closeTabsToRight'),\n overlayIcon: {\n mainIcon: 'pi pi-times',\n subIcon: 'pi pi-arrow-right',\n positionX: 'right',\n positionY: 'bottom',\n subIconScale: 0.5\n } as OverlayIconProps,\n command: () => closeWorkflows(options.value.slice(index + 1)),\n disabled: index === options.value.length - 1\n },\n {\n label: t('tabMenu.closeOtherTabs'),\n overlayIcon: {\n mainIcon: 'pi pi-times',\n subIcon: 'pi pi-arrows-h',\n positionX: 'right',\n positionY: 'bottom',\n subIconScale: 0.5\n } as OverlayIconProps,\n command: () =>\n closeWorkflows([\n ...options.value.slice(index + 1),\n ...options.value.slice(0, index)\n ]),\n disabled: options.value.length <= 1\n }\n ]\n})\n\n// Horizontal scroll on wheel\nconst handleWheel = (event: WheelEvent) => {\n const scrollElement = event.currentTarget as HTMLElement\n const scrollAmount = event.deltaX || event.deltaY\n scrollElement.scroll({\n left: scrollElement.scrollLeft + scrollAmount\n })\n}\n\nconst scrollContent = computed(\n () =>\n (containerRef.value?.querySelector(\n '.p-scrollpanel-content'\n ) as HTMLElement | null) ?? null\n)\n\nconst scroll = (direction: number) => {\n const el = scrollContent.value\n if (!el) return\n el.scrollBy({ left: direction * 20 })\n}\n\nconst ensureActiveTabVisible = async (\n options: { waitForDom?: boolean } = {}\n) => {\n if (!selectedWorkflow.value) return\n\n if (options.waitForDom !== false) {\n await nextTick()\n }\n\n const containerElement = containerRef.value\n if (!containerElement) return\n\n const activeTabElement = containerElement.querySelector(\n '.p-togglebutton-checked'\n )\n if (!activeTabElement) return\n\n activeTabElement.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n}\n\n// Scroll to active offscreen tab when opened\nwatch(\n () => workflowStore.activeWorkflow,\n () => {\n void ensureActiveTabVisible()\n },\n { immediate: true }\n)\n\nlet overflowObserver: ReturnType<typeof useOverflowObserver> | null = null\nlet stopArrivedWatch: WatchStopHandle | null = null\nlet stopOverflowWatch: WatchStopHandle | null = null\n\nwatch(\n scrollContent,\n (el, _prev, onCleanup) => {\n stopArrivedWatch?.()\n stopOverflowWatch?.()\n overflowObserver?.dispose()\n\n if (!el) return\n\n const scrollState = useScroll(el)\n\n stopArrivedWatch = watch(\n [\n () => scrollState.arrivedState.left,\n () => scrollState.arrivedState.right\n ],\n ([atLeft, atRight]) => {\n leftArrowEnabled.value = !atLeft\n rightArrowEnabled.value = !atRight\n },\n { immediate: true }\n )\n\n overflowObserver = useOverflowObserver(el)\n stopOverflowWatch = watch(\n overflowObserver.isOverflowing,\n (isOverflow) => {\n showOverflowArrows.value = isOverflow\n if (!isOverflow) return\n void nextTick(() => {\n // Force a new check after arrows are updated\n scrollState.measure()\n void ensureActiveTabVisible({ waitForDom: false })\n })\n },\n { immediate: true }\n )\n\n onCleanup(() => {\n stopArrivedWatch?.()\n stopOverflowWatch?.()\n overflowObserver?.dispose()\n })\n },\n { immediate: true }\n)\n\nonUpdated(() => {\n if (!overflowObserver?.disposed.value) {\n overflowObserver?.checkOverflow()\n }\n})\n</script>\n\n<style scoped>\n@reference '../../assets/css/style.css';\n\n.workflow-tabs-container {\n background-color: var(--comfy-menu-bg);\n}\n\n:deep(.p-togglebutton) {\n @apply p-0 bg-transparent rounded-none shrink relative border-0 border-r border-solid;\n border-right-color: var(--border-color);\n min-width: 90px;\n}\n\n.overflow-arrow {\n @apply px-2 rounded-none;\n}\n\n.overflow-arrow[disabled] {\n @apply opacity-25;\n}\n\n:deep(.p-togglebutton > .p-togglebutton-content) {\n @apply max-w-full;\n}\n\n:deep(.workflow-tab) {\n @apply max-w-full;\n}\n\n:deep(.p-togglebutton::before) {\n @apply hidden;\n}\n\n:deep(.p-togglebutton:first-child) {\n @apply border-l border-solid;\n border-left-color: var(--border-color);\n}\n\n:deep(.p-togglebutton:not(:first-child)) {\n @apply border-l-0;\n}\n\n:deep(.p-togglebutton.p-togglebutton-checked) {\n @apply border-b border-solid h-full;\n border-bottom-color: var(--p-button-text-primary-color);\n}\n\n:deep(.p-togglebutton:not(.p-togglebutton-checked)) {\n @apply opacity-75;\n}\n\n:deep(.p-togglebutton-checked) .close-button,\n:deep(.p-togglebutton:hover) .close-button {\n @apply visible;\n}\n\n:deep(.p-scrollpanel-content) {\n @apply h-full;\n}\n\n:deep(.workflow-tabs) {\n display: flex;\n}\n\n/* Scrollbar half opacity to avoid blocking the active tab bottom border */\n:deep(.p-scrollpanel:hover .p-scrollpanel-bar),\n:deep(.p-scrollpanel:active .p-scrollpanel-bar) {\n @apply opacity-50;\n}\n\n:deep(.p-selectbutton) {\n @apply rounded-none h-full;\n}\n\n.workflow-tabs-container-desktop {\n max-width: env(titlebar-area-width, 100vw);\n}\n\n.window-actions-spacer {\n @apply flex-auto;\n /* If we are using custom titlebar, then we need to add a gap for the user to drag the window */\n --window-actions-spacer-width: min(75px, env(titlebar-area-width, 0) * 9999);\n min-width: var(--window-actions-spacer-width);\n}\n</style>\n","/**\n * Composable for syncing LiteGraph with the Layout system\n *\n * Implements one-way sync from Layout Store to LiteGraph.\n * The layout store is the single source of truth.\n */\nimport { onUnmounted, ref } from 'vue'\n\nimport type { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\n\n/**\n * Composable for syncing LiteGraph with the Layout system\n * This replaces the bidirectional sync with a one-way sync\n */\nexport function useLayoutSync() {\n const unsubscribe = ref<() => void>()\n\n /**\n * Start syncing from Layout → LiteGraph\n */\n function startSync(canvas: ReturnType<typeof useCanvasStore>['canvas']) {\n if (!canvas?.graph) return\n\n // Cancel last subscription\n stopSync()\n // Subscribe to layout changes\n unsubscribe.value = layoutStore.onChange((change) => {\n // Apply changes to LiteGraph regardless of source\n // The layout store is the single source of truth\n for (const nodeId of change.nodeIds) {\n const layout = layoutStore.getNodeLayoutRef(nodeId).value\n if (!layout) continue\n\n const liteNode = canvas.graph?.getNodeById(parseInt(nodeId))\n if (!liteNode) continue\n\n if (\n liteNode.pos[0] !== layout.position.x ||\n liteNode.pos[1] !== layout.position.y\n ) {\n liteNode.pos[0] = layout.position.x\n liteNode.pos[1] = layout.position.y\n }\n\n // Note: layout.size.height is the content height without title.\n // LiteGraph's measure() will add titleHeight to get boundingRect.\n // Do NOT use addNodeTitleHeight here - that would double-count the title.\n if (\n liteNode.size[0] !== layout.size.width ||\n liteNode.size[1] !== layout.size.height\n ) {\n // Use setSize() to trigger onResize callback\n liteNode.setSize([layout.size.width, layout.size.height])\n }\n }\n\n // Trigger single redraw for all changes\n canvas.setDirty(true, true)\n })\n }\n\n function stopSync() {\n unsubscribe.value?.()\n unsubscribe.value = undefined\n }\n\n onUnmounted(stopSync)\n\n return {\n startSync,\n stopSync\n }\n}\n","import { createSharedComposable, whenever } from '@vueuse/core'\nimport { shallowRef, watch } from 'vue'\n\nimport { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'\nimport type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'\nimport { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'\nimport { removeNodeTitleHeight } from '@/renderer/core/layout/utils/nodeSizeUtil'\nimport { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'\nimport { app as comfyApp } from '@/scripts/app'\n\nfunction useVueNodeLifecycleIndividual() {\n const canvasStore = useCanvasStore()\n const layoutMutations = useLayoutMutations()\n const { shouldRenderVueNodes } = useVueFeatureFlags()\n const nodeManager = shallowRef<GraphNodeManager | null>(null)\n const { startSync } = useLayoutSync()\n\n const initializeNodeManager = () => {\n // Use canvas graph if available (handles subgraph contexts), fallback to app graph\n const activeGraph = comfyApp.canvas?.graph\n if (!activeGraph || nodeManager.value) return\n\n // Initialize the core node manager\n const manager = useGraphNodeManager(activeGraph)\n nodeManager.value = manager\n\n // Initialize layout system with existing nodes from active graph\n const nodes = activeGraph._nodes.map((node: LGraphNode) => ({\n id: node.id.toString(),\n pos: [node.pos[0], node.pos[1]] as [number, number],\n size: [node.size[0], removeNodeTitleHeight(node.size[1])] as [\n number,\n number\n ]\n }))\n layoutStore.initializeFromLiteGraph(nodes)\n\n // Seed reroutes into the Layout Store so hit-testing uses the new path\n for (const reroute of activeGraph.reroutes.values()) {\n const [x, y] = reroute.pos\n const parent = reroute.parentId ?? undefined\n const linkIds = Array.from(reroute.linkIds)\n layoutMutations.createReroute(reroute.id, { x, y }, parent, linkIds)\n }\n\n // Seed existing links into the Layout Store (topology only)\n for (const link of activeGraph._links.values()) {\n layoutMutations.createLink(\n link.id,\n link.origin_id,\n link.origin_slot,\n link.target_id,\n link.target_slot\n )\n }\n\n // Initialize layout sync (one-way: Layout Store → LiteGraph)\n startSync(canvasStore.canvas)\n }\n\n const disposeNodeManagerAndSyncs = () => {\n if (!nodeManager.value) return\n\n try {\n nodeManager.value.cleanup()\n } catch {\n /* empty */\n }\n nodeManager.value = null\n }\n\n // Watch for Vue nodes enabled state changes\n watch(\n () => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph),\n (enabled) => {\n if (enabled) {\n initializeNodeManager()\n ensureCorrectLayoutScale(\n comfyApp.canvas?.graph?.extra.workflowRendererVersion\n )\n }\n },\n { immediate: true }\n )\n\n whenever(\n () => !shouldRenderVueNodes.value,\n () => {\n ensureCorrectLayoutScale(\n comfyApp.canvas?.graph?.extra.workflowRendererVersion\n )\n disposeNodeManagerAndSyncs()\n comfyApp.canvas?.setDirty(true, true)\n }\n )\n\n // Consolidated watch for slot layout sync management\n watch(\n () => shouldRenderVueNodes.value,\n (vueMode, oldVueMode) => {\n const modeChanged = vueMode !== oldVueMode\n\n // Clear stale slot layouts when switching modes\n if (modeChanged) {\n layoutStore.clearAllSlotLayouts()\n }\n },\n { immediate: true, flush: 'sync' }\n )\n\n // Handle case where Vue nodes are enabled but graph starts empty\n const setupEmptyGraphListener = () => {\n const activeGraph = comfyApp.canvas?.graph\n if (\n !shouldRenderVueNodes.value ||\n nodeManager.value ||\n activeGraph?._nodes.length !== 0\n ) {\n return\n }\n const originalOnNodeAdded = activeGraph.onNodeAdded\n activeGraph.onNodeAdded = function (node: LGraphNode) {\n // Restore original handler\n activeGraph.onNodeAdded = originalOnNodeAdded\n\n // Initialize node manager if needed\n if (shouldRenderVueNodes.value && !nodeManager.value) {\n initializeNodeManager()\n }\n\n // Call original handler\n if (originalOnNodeAdded) {\n originalOnNodeAdded.call(this, node)\n }\n }\n }\n\n // Cleanup function for component unmounting\n const cleanup = () => {\n if (nodeManager.value) {\n nodeManager.value.cleanup()\n nodeManager.value = null\n }\n }\n\n return {\n nodeManager,\n\n // Lifecycle methods\n initializeNodeManager,\n disposeNodeManagerAndSyncs,\n setupEmptyGraphListener,\n cleanup\n }\n}\n\nexport const useVueNodeLifecycle = createSharedComposable(\n useVueNodeLifecycleIndividual\n)\n","import { formatCreditsFromUsd } from '@/base/credits/comfyCredits'\nimport type { INodeInputSlot, LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport type { IComboWidget } from '@/lib/litegraph/src/types/widgets'\n\n/**\n * Meshy credit pricing constant.\n * 1 Meshy credit = $0.04 USD\n * Change this value to update all Meshy node prices.\n */\nconst MESHY_CREDIT_PRICE_USD = 0.04\n\n/** Convert Meshy credits to USD */\nconst meshyCreditsToUsd = (credits: number): number =>\n credits * MESHY_CREDIT_PRICE_USD\n\nconst DEFAULT_NUMBER_OPTIONS: Intl.NumberFormatOptions = {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n}\n\ntype CreditFormatOptions = {\n suffix?: string\n note?: string\n approximate?: boolean\n separator?: string\n}\n\nconst formatCreditsValue = (usd: number): string =>\n formatCreditsFromUsd({\n usd,\n numberOptions: DEFAULT_NUMBER_OPTIONS\n })\n\nconst makePrefix = (approximate?: boolean) => (approximate ? '~' : '')\n\nconst makeSuffix = (suffix?: string) => suffix ?? '/Run'\n\nconst appendNote = (note?: string) => (note ? ` ${note}` : '')\n\nconst formatCreditsLabel = (\n usd: number,\n { suffix, note, approximate }: CreditFormatOptions = {}\n): string =>\n `${makePrefix(approximate)}${formatCreditsValue(usd)} credits${makeSuffix(suffix)}${appendNote(note)}`\n\nconst formatCreditsRangeLabel = (\n minUsd: number,\n maxUsd: number,\n { suffix, note, approximate }: CreditFormatOptions = {}\n): string => {\n const min = formatCreditsValue(minUsd)\n const max = formatCreditsValue(maxUsd)\n const rangeValue = min === max ? min : `${min}-${max}`\n return `${makePrefix(approximate)}${rangeValue} credits${makeSuffix(suffix)}${appendNote(note)}`\n}\n\nconst formatCreditsListLabel = (\n usdValues: number[],\n { suffix, note, approximate, separator }: CreditFormatOptions = {}\n): string => {\n const parts = usdValues.map((value) => formatCreditsValue(value))\n const value = parts.join(separator ?? '/')\n return `${makePrefix(approximate)}${value} credits${makeSuffix(suffix)}${appendNote(note)}`\n}\n\n/**\n * Function that calculates dynamic pricing based on node widget values\n */\ntype PricingFunction = (node: LGraphNode) => string\n\n/**\n * Safely executes a pricing function with error handling\n * Returns null if the function throws an error, allowing the node to still render\n */\nfunction safePricingExecution(\n fn: PricingFunction,\n node: LGraphNode,\n fallback: string = ''\n): string {\n try {\n return fn(node)\n } catch (error) {\n // Log error in development but don't throw to avoid breaking node rendering\n if (process.env.NODE_ENV === 'development') {\n console.warn(\n 'Pricing calculation failed for node:',\n node.constructor?.nodeData?.name,\n error\n )\n }\n return fallback\n }\n}\n\n/**\n * Helper function to calculate Runway duration-based pricing\n * @param node - The LiteGraph node\n * @returns Formatted price string\n */\nconst calculateRunwayDurationPrice = (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n\n if (!durationWidget) return formatCreditsLabel(0.0715, { suffix: '/second' })\n\n const duration = Number(durationWidget.value)\n const validDuration = isNaN(duration) ? 5 : duration\n const cost = 0.0715 * validDuration\n return formatCreditsLabel(cost)\n}\n\nconst makeOmniProDurationCalculator =\n (pricePerSecond: number): PricingFunction =>\n (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n if (!durationWidget)\n return formatCreditsLabel(pricePerSecond, { suffix: '/second' })\n\n const seconds = parseFloat(String(durationWidget.value))\n if (!Number.isFinite(seconds))\n return formatCreditsLabel(pricePerSecond, { suffix: '/second' })\n\n const cost = pricePerSecond * seconds\n return formatCreditsLabel(cost)\n }\n\nconst klingMotionControlPricingCalculator: PricingFunction = (\n node: LGraphNode\n): string => {\n const modeWidget = node.widgets?.find(\n (w) => w.name === 'mode'\n ) as IComboWidget\n\n if (!modeWidget) {\n return formatCreditsListLabel([0.07, 0.112], {\n suffix: '/second',\n note: '(std/pro)'\n })\n }\n\n const mode = String(modeWidget.value).toLowerCase()\n\n if (mode === 'pro') return formatCreditsLabel(0.112, { suffix: '/second' })\n if (mode === 'std') return formatCreditsLabel(0.07, { suffix: '/second' })\n\n return formatCreditsListLabel([0.07, 0.112], {\n suffix: '/second',\n note: '(std/pro)'\n })\n}\n\nconst pixversePricingCalculator = (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration_seconds'\n ) as IComboWidget\n const qualityWidget = node.widgets?.find(\n (w) => w.name === 'quality'\n ) as IComboWidget\n const motionModeWidget = node.widgets?.find(\n (w) => w.name === 'motion_mode'\n ) as IComboWidget\n\n if (!durationWidget || !qualityWidget) {\n return formatCreditsRangeLabel(0.45, 1.2, {\n note: '(varies with duration, quality & motion mode)'\n })\n }\n\n const duration = String(durationWidget.value)\n const quality = String(qualityWidget.value)\n const motionMode = String(motionModeWidget?.value)\n\n // Basic pricing based on duration and quality\n if (duration.includes('5')) {\n if (quality.includes('1080p')) return formatCreditsLabel(1.2)\n if (quality.includes('720p') && motionMode?.includes('fast'))\n return formatCreditsLabel(1.2)\n if (quality.includes('720p') && motionMode?.includes('normal'))\n return formatCreditsLabel(0.6)\n if (quality.includes('540p') && motionMode?.includes('fast'))\n return formatCreditsLabel(0.9)\n if (quality.includes('540p') && motionMode?.includes('normal'))\n return formatCreditsLabel(0.45)\n if (quality.includes('360p') && motionMode?.includes('fast'))\n return formatCreditsLabel(0.9)\n if (quality.includes('360p') && motionMode?.includes('normal'))\n return formatCreditsLabel(0.45)\n } else if (duration.includes('8')) {\n if (quality.includes('540p') && motionMode?.includes('normal'))\n return formatCreditsLabel(0.9)\n if (quality.includes('540p') && motionMode?.includes('fast'))\n return formatCreditsLabel(1.2)\n if (quality.includes('360p') && motionMode?.includes('normal'))\n return formatCreditsLabel(0.9)\n if (quality.includes('360p') && motionMode?.includes('fast'))\n return formatCreditsLabel(1.2)\n if (quality.includes('1080p') && motionMode?.includes('normal'))\n return formatCreditsLabel(1.2)\n if (quality.includes('1080p') && motionMode?.includes('fast'))\n return formatCreditsLabel(1.2)\n if (quality.includes('720p') && motionMode?.includes('normal'))\n return formatCreditsLabel(1.2)\n if (quality.includes('720p') && motionMode?.includes('fast'))\n return formatCreditsLabel(1.2)\n }\n\n return formatCreditsLabel(0.9)\n}\n\nconst byteDanceVideoPricingCalculator = (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n const generateAudioWidget = node.widgets?.find(\n (w) => w.name === 'generate_audio'\n ) as IComboWidget | undefined\n\n if (!modelWidget || !durationWidget || !resolutionWidget) return 'Token-based'\n\n const model = String(modelWidget.value).toLowerCase()\n const resolution = String(resolutionWidget.value).toLowerCase()\n const seconds = parseFloat(String(durationWidget.value))\n const generateAudio =\n generateAudioWidget &&\n String(generateAudioWidget.value).toLowerCase() === 'true'\n const priceByModel: Record<string, Record<string, [number, number]>> = {\n 'seedance-1-5-pro': {\n '480p': [0.12, 0.12],\n '720p': [0.26, 0.26],\n '1080p': [0.58, 0.59]\n },\n 'seedance-1-0-pro': {\n '480p': [0.23, 0.24],\n '720p': [0.51, 0.56],\n '1080p': [1.18, 1.22]\n },\n 'seedance-1-0-pro-fast': {\n '480p': [0.09, 0.1],\n '720p': [0.21, 0.23],\n '1080p': [0.47, 0.49]\n },\n 'seedance-1-0-lite': {\n '480p': [0.17, 0.18],\n '720p': [0.37, 0.41],\n '1080p': [0.85, 0.88]\n }\n }\n\n const modelKey = model.includes('seedance-1-5-pro')\n ? 'seedance-1-5-pro'\n : model.includes('seedance-1-0-pro-fast')\n ? 'seedance-1-0-pro-fast'\n : model.includes('seedance-1-0-pro')\n ? 'seedance-1-0-pro'\n : model.includes('seedance-1-0-lite')\n ? 'seedance-1-0-lite'\n : ''\n\n const resKey = resolution.includes('1080')\n ? '1080p'\n : resolution.includes('720')\n ? '720p'\n : resolution.includes('480')\n ? '480p'\n : ''\n\n const baseRange =\n modelKey && resKey ? priceByModel[modelKey]?.[resKey] : undefined\n if (!baseRange) return 'Token-based'\n\n const [min10s, max10s] = baseRange\n const scale = seconds / 10\n const audioMultiplier =\n modelKey === 'seedance-1-5-pro' && generateAudio ? 2 : 1\n const minCost = min10s * scale * audioMultiplier\n const maxCost = max10s * scale * audioMultiplier\n\n if (minCost === maxCost) return formatCreditsLabel(minCost)\n return formatCreditsRangeLabel(minCost, maxCost)\n}\n\nconst ltxvPricingCalculator = (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n\n const fallback = formatCreditsRangeLabel(0.04, 0.24, {\n suffix: '/second'\n })\n if (!modelWidget || !durationWidget || !resolutionWidget) return fallback\n\n const model = String(modelWidget.value).toLowerCase()\n const resolution = String(resolutionWidget.value).toLowerCase()\n const seconds = parseFloat(String(durationWidget.value))\n const priceByModel: Record<string, Record<string, number>> = {\n 'ltx-2 (pro)': {\n '1920x1080': 0.06,\n '2560x1440': 0.12,\n '3840x2160': 0.24\n },\n 'ltx-2 (fast)': {\n '1920x1080': 0.04,\n '2560x1440': 0.08,\n '3840x2160': 0.16\n }\n }\n\n const modelTable = priceByModel[model]\n if (!modelTable) return fallback\n\n const pps = modelTable[resolution]\n if (!pps) return fallback\n\n const cost = pps * seconds\n return formatCreditsLabel(cost)\n}\n\nconst klingVideoWithAudioPricingCalculator: PricingFunction = (\n node: LGraphNode\n): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const generateAudioWidget = node.widgets?.find(\n (w) => w.name === 'generate_audio'\n ) as IComboWidget\n\n if (!durationWidget || !generateAudioWidget) {\n return formatCreditsRangeLabel(0.35, 1.4, {\n note: '(varies with duration & audio)'\n })\n }\n\n const duration = String(durationWidget.value)\n const generateAudio =\n String(generateAudioWidget.value).toLowerCase() === 'true'\n\n if (duration === '5') {\n return generateAudio ? formatCreditsLabel(0.7) : formatCreditsLabel(0.35)\n }\n\n if (duration === '10') {\n return generateAudio ? formatCreditsLabel(1.4) : formatCreditsLabel(0.7)\n }\n\n // Fallback for unexpected duration values\n return formatCreditsRangeLabel(0.35, 1.4, {\n note: '(varies with duration & audio)'\n })\n}\n\n// ---- constants ----\nconst SORA_SIZES = {\n BASIC: new Set(['720x1280', '1280x720']),\n PRO: new Set(['1024x1792', '1792x1024'])\n}\nconst ALL_SIZES = new Set([...SORA_SIZES.BASIC, ...SORA_SIZES.PRO])\n\n// ---- sora-2 pricing helpers ----\nfunction validateSora2Selection(\n modelRaw: string,\n duration: number,\n sizeRaw: string\n): string | undefined {\n const model = modelRaw?.toLowerCase() ?? ''\n const size = sizeRaw?.toLowerCase() ?? ''\n\n if (!duration || Number.isNaN(duration)) return 'Set duration (4s / 8s / 12s)'\n if (!size) return 'Set size (720x1280, 1280x720, 1024x1792, 1792x1024)'\n if (!ALL_SIZES.has(size))\n return 'Invalid size. Must be 720x1280, 1280x720, 1024x1792, or 1792x1024.'\n\n if (model.includes('sora-2-pro')) return undefined\n\n if (model.includes('sora-2') && !SORA_SIZES.BASIC.has(size))\n return 'sora-2 supports only 720x1280 or 1280x720'\n\n if (!model.includes('sora-2')) return 'Unsupported model'\n\n return undefined\n}\n\nfunction perSecForSora2(modelRaw: string, sizeRaw: string): number {\n const model = modelRaw?.toLowerCase() ?? ''\n const size = sizeRaw?.toLowerCase() ?? ''\n\n if (model.includes('sora-2-pro')) {\n return SORA_SIZES.PRO.has(size) ? 0.5 : 0.3\n }\n if (model.includes('sora-2')) return 0.1\n\n return SORA_SIZES.PRO.has(size) ? 0.5 : 0.1\n}\n\nfunction formatRunPrice(perSec: number, duration: number) {\n return formatCreditsLabel(Number((perSec * duration).toFixed(2)))\n}\n\n// ---- pricing calculator ----\nconst sora2PricingCalculator: PricingFunction = (node: LGraphNode): string => {\n const getWidgetValue = (name: string) =>\n String(node.widgets?.find((w) => w.name === name)?.value ?? '')\n\n const model = getWidgetValue('model')\n const size = getWidgetValue('size')\n const duration = Number(\n node.widgets?.find((w) => ['duration', 'duration_s'].includes(w.name))\n ?.value\n )\n\n if (!model || !size || !duration) return 'Set model, duration & size'\n\n const validationError = validateSora2Selection(model, duration, size)\n if (validationError) return validationError\n\n const perSec = perSecForSora2(model, size)\n return formatRunPrice(perSec, duration)\n}\n\n/**\n * Pricing for Tripo 3D generation nodes (Text / Image / Multiview)\n * based on Tripo credits:\n *\n * Turbo / V3 / V2.5 / V2.0:\n * Text -> 10 (no texture) / 20 (standard texture)\n * Image -> 20 (no texture) / 30 (standard texture)\n * Multiview -> 20 (no texture) / 30 (standard texture)\n *\n * V1.4:\n * Text -> 20\n * Image -> 30\n * (Multiview treated same as Image if used)\n *\n * Advanced extras (added on top of generation credits):\n * quad -> +5 credits\n * style -> +5 credits (if style != \"None\")\n * HD texture -> +10 credits (texture_quality = \"detailed\")\n * detailed geometry -> +20 credits (geometry_quality = \"detailed\")\n *\n * 1 credit = $0.01\n */\nconst calculateTripo3DGenerationPrice = (\n node: LGraphNode,\n task: 'text' | 'image' | 'multiview'\n): string => {\n const getWidget = (name: string): IComboWidget | undefined =>\n node.widgets?.find((w) => w.name === name) as IComboWidget | undefined\n\n const getString = (name: string, defaultValue: string): string => {\n const widget = getWidget(name)\n if (!widget || widget.value === undefined || widget.value === null) {\n return defaultValue\n }\n return String(widget.value)\n }\n\n const getBool = (name: string, defaultValue: boolean): boolean => {\n const widget = getWidget(name)\n if (!widget || widget.value === undefined || widget.value === null) {\n return defaultValue\n }\n\n const v = widget.value\n if (typeof v === 'number') return v !== 0\n const lower = String(v).toLowerCase()\n if (lower === 'true') return true\n if (lower === 'false') return false\n\n return defaultValue\n }\n\n // ---- read widget values with sensible defaults (mirroring backend) ----\n const modelVersionRaw = getString('model_version', '').toLowerCase()\n if (modelVersionRaw === '')\n return formatCreditsRangeLabel(0.1, 0.65, {\n note: '(varies with quad, style, texture & quality)'\n })\n const styleRaw = getString('style', 'None')\n const hasStyle = styleRaw.toLowerCase() !== 'none'\n\n // Backend defaults: texture=true, pbr=true, quad=false, qualities=\"standard\"\n const hasTexture = getBool('texture', false)\n const hasPbr = getBool('pbr', false)\n const quad = getBool('quad', false)\n\n const textureQualityRaw = getString(\n 'texture_quality',\n 'standard'\n ).toLowerCase()\n const geometryQualityRaw = getString(\n 'geometry_quality',\n 'standard'\n ).toLowerCase()\n\n const isHdTexture = textureQualityRaw === 'detailed'\n const isDetailedGeometry = geometryQualityRaw === 'detailed'\n\n const withTexture = hasTexture || hasPbr\n\n let baseCredits: number\n\n if (modelVersionRaw.includes('v1.4')) {\n // V1.4 model: Text=20, Image=30, Refine=30\n if (task === 'text') {\n baseCredits = 20\n } else {\n // treat Multiview same as Image if V1.4 is ever used there\n baseCredits = 30\n }\n } else {\n // V3.0, V2.5, V2.0 models\n if (!withTexture) {\n if (task === 'text') {\n baseCredits = 10 // Text to 3D without texture\n } else {\n baseCredits = 20 // Image/Multiview to 3D without texture\n }\n } else {\n if (task === 'text') {\n baseCredits = 20 // Text to 3D with standard texture\n } else {\n baseCredits = 30 // Image/Multiview to 3D with standard texture\n }\n }\n }\n\n // ---- advanced extras on top of base generation ----\n let credits = baseCredits\n\n if (hasStyle) credits += 5 // Style\n if (quad) credits += 5 // Quad Topology\n if (isHdTexture) credits += 10 // HD Texture\n if (isDetailedGeometry) credits += 20 // Detailed Geometry Quality\n\n const dollars = credits * 0.01\n return formatCreditsLabel(dollars)\n}\n\n/**\n * Meshy Image to 3D pricing calculator.\n * Pricing based on should_texture widget:\n * - Without texture: 20 credits\n * - With texture: 30 credits\n */\nconst calculateMeshyImageToModelPrice = (node: LGraphNode): string => {\n const shouldTextureWidget = node.widgets?.find(\n (w) => w.name === 'should_texture'\n ) as IComboWidget\n\n if (!shouldTextureWidget) {\n return formatCreditsRangeLabel(\n meshyCreditsToUsd(20),\n meshyCreditsToUsd(30),\n { note: '(varies with texture)' }\n )\n }\n\n const shouldTexture = String(shouldTextureWidget.value).toLowerCase()\n const credits = shouldTexture === 'true' ? 30 : 20\n return formatCreditsLabel(meshyCreditsToUsd(credits))\n}\n\n/**\n * Meshy Multi-Image to 3D pricing calculator.\n * Pricing based on should_texture widget:\n * - Without texture: 5 credits\n * - With texture: 15 credits\n */\nconst calculateMeshyMultiImageToModelPrice = (node: LGraphNode): string => {\n const shouldTextureWidget = node.widgets?.find(\n (w) => w.name === 'should_texture'\n ) as IComboWidget\n\n if (!shouldTextureWidget) {\n return formatCreditsRangeLabel(\n meshyCreditsToUsd(5),\n meshyCreditsToUsd(15),\n { note: '(varies with texture)' }\n )\n }\n\n const shouldTexture = String(shouldTextureWidget.value).toLowerCase()\n const credits = shouldTexture === 'true' ? 15 : 5\n return formatCreditsLabel(meshyCreditsToUsd(credits))\n}\n\n/**\n * Static pricing data for API nodes, now supporting both strings and functions\n */\nconst apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =\n {\n FluxProCannyNode: {\n displayPrice: formatCreditsLabel(0.05)\n },\n FluxProDepthNode: {\n displayPrice: formatCreditsLabel(0.05)\n },\n FluxProExpandNode: {\n displayPrice: formatCreditsLabel(0.05)\n },\n FluxProFillNode: {\n displayPrice: formatCreditsLabel(0.05)\n },\n FluxProUltraImageNode: {\n displayPrice: formatCreditsLabel(0.06)\n },\n FluxProKontextProNode: {\n displayPrice: formatCreditsLabel(0.04)\n },\n FluxProKontextMaxNode: {\n displayPrice: formatCreditsLabel(0.08)\n },\n Flux2ProImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const widthW = node.widgets?.find(\n (w) => w.name === 'width'\n ) as IComboWidget\n const heightW = node.widgets?.find(\n (w) => w.name === 'height'\n ) as IComboWidget\n\n const w = Number(widthW?.value)\n const h = Number(heightW?.value)\n if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {\n // global min/max for this node given schema bounds (1MP..4MP output)\n return formatCreditsRangeLabel(0.03, 0.15)\n }\n\n // Is the 'images' input connected?\n const imagesInput = node.inputs?.find(\n (i) => i.name === 'images'\n ) as INodeInputSlot\n const hasRefs =\n typeof imagesInput?.link !== 'undefined' && imagesInput.link != null\n\n // Output cost: ceil((w*h)/MP); first MP $0.03, each additional $0.015\n const MP = 1024 * 1024\n const outMP = Math.max(1, Math.floor((w * h + MP - 1) / MP))\n const outputCost = 0.03 + 0.015 * Math.max(outMP - 1, 0)\n\n if (hasRefs) {\n // Unknown ref count/size on the frontend:\n // min extra is $0.015, max extra is $0.120 (8 MP cap / 8 refs)\n const minTotal = outputCost + 0.015\n const maxTotal = outputCost + 0.12\n return formatCreditsRangeLabel(minTotal, maxTotal, {\n approximate: true\n })\n }\n\n // Precise text-to-image price\n return formatCreditsLabel(outputCost)\n }\n },\n Flux2MaxImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const widthW = node.widgets?.find(\n (w) => w.name === 'width'\n ) as IComboWidget\n const heightW = node.widgets?.find(\n (w) => w.name === 'height'\n ) as IComboWidget\n\n const w = Number(widthW?.value)\n const h = Number(heightW?.value)\n if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {\n // global min/max for this node given schema bounds (1MP..4MP output)\n return formatCreditsRangeLabel(0.07, 0.35)\n }\n\n // Is the 'images' input connected?\n const imagesInput = node.inputs?.find(\n (i) => i.name === 'images'\n ) as INodeInputSlot\n const hasRefs =\n typeof imagesInput?.link !== 'undefined' && imagesInput.link != null\n\n // Output cost: ceil((w*h)/MP); first MP $0.07, each additional $0.03\n const MP = 1024 * 1024\n const outMP = Math.max(1, Math.floor((w * h + MP - 1) / MP))\n const outputCost = 0.07 + 0.03 * Math.max(outMP - 1, 0)\n\n if (hasRefs) {\n // Unknown ref count/size on the frontend:\n // min extra is $0.03, max extra is $0.24 (8 MP cap / 8 refs)\n const minTotal = outputCost + 0.03\n const maxTotal = outputCost + 0.24\n return formatCreditsRangeLabel(minTotal, maxTotal)\n }\n\n return formatCreditsLabel(outputCost)\n }\n },\n OpenAIVideoSora2: {\n displayPrice: sora2PricingCalculator\n },\n IdeogramV1: {\n displayPrice: (node: LGraphNode): string => {\n const numImagesWidget = node.widgets?.find(\n (w) => w.name === 'num_images'\n ) as IComboWidget\n const turboWidget = node.widgets?.find(\n (w) => w.name === 'turbo'\n ) as IComboWidget\n\n if (!numImagesWidget)\n return formatCreditsRangeLabel(0.03, 0.09, {\n suffix: ' x num_images/Run'\n })\n\n const numImages = Number(numImagesWidget.value) || 1\n const turbo = String(turboWidget?.value).toLowerCase() === 'true'\n const basePrice = turbo ? 0.0286 : 0.0858\n const cost = Number((basePrice * numImages).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n IdeogramV2: {\n displayPrice: (node: LGraphNode): string => {\n const numImagesWidget = node.widgets?.find(\n (w) => w.name === 'num_images'\n ) as IComboWidget\n const turboWidget = node.widgets?.find(\n (w) => w.name === 'turbo'\n ) as IComboWidget\n\n if (!numImagesWidget)\n return formatCreditsRangeLabel(0.07, 0.11, {\n suffix: ' x num_images/Run'\n })\n\n const numImages = Number(numImagesWidget.value) || 1\n const turbo = String(turboWidget?.value).toLowerCase() === 'true'\n const basePrice = turbo ? 0.0715 : 0.1144\n const cost = Number((basePrice * numImages).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n IdeogramV3: {\n displayPrice: (node: LGraphNode): string => {\n const renderingSpeedWidget = node.widgets?.find(\n (w) => w.name === 'rendering_speed'\n ) as IComboWidget\n const numImagesWidget = node.widgets?.find(\n (w) => w.name === 'num_images'\n ) as IComboWidget\n const characterInput = node.inputs?.find(\n (i) => i.name === 'character_image'\n ) as INodeInputSlot\n const hasCharacter =\n typeof characterInput?.link !== 'undefined' &&\n characterInput.link != null\n\n if (!renderingSpeedWidget)\n return formatCreditsRangeLabel(0.04, 0.11, {\n suffix: ' x num_images/Run',\n note: '(varies with rendering speed & num_images)'\n })\n\n const numImages = Number(numImagesWidget?.value) || 1\n let basePrice = 0.0858 // default balanced price\n\n const renderingSpeed = String(renderingSpeedWidget.value)\n if (renderingSpeed.toLowerCase().includes('quality')) {\n if (hasCharacter) {\n basePrice = 0.286\n } else {\n basePrice = 0.1287\n }\n } else if (renderingSpeed.toLowerCase().includes('default')) {\n if (hasCharacter) {\n basePrice = 0.2145\n } else {\n basePrice = 0.0858\n }\n } else if (renderingSpeed.toLowerCase().includes('turbo')) {\n if (hasCharacter) {\n basePrice = 0.143\n } else {\n basePrice = 0.0429\n }\n }\n\n const totalCost = Number((basePrice * numImages).toFixed(2))\n return formatCreditsLabel(totalCost)\n }\n },\n KlingCameraControlI2VNode: {\n displayPrice: formatCreditsLabel(0.49)\n },\n KlingCameraControlT2VNode: {\n displayPrice: formatCreditsLabel(0.14)\n },\n KlingDualCharacterVideoEffectNode: {\n displayPrice: (node: LGraphNode): string => {\n const modeWidget = node.widgets?.find(\n (w) => w.name === 'mode'\n ) as IComboWidget\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model_name'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n if (!modeWidget || !modelWidget || !durationWidget)\n return formatCreditsRangeLabel(0.14, 2.8, {\n note: '(varies with model, mode & duration)'\n })\n\n const modeValue = String(modeWidget.value)\n const durationValue = String(durationWidget.value)\n const modelValue = String(modelWidget.value)\n\n // Same pricing matrix as KlingTextToVideoNode\n if (modelValue.includes('v1-6') || modelValue.includes('v1-5')) {\n if (modeValue.includes('pro')) {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.56)\n : formatCreditsLabel(0.28)\n }\n } else if (modelValue.includes('v1')) {\n if (modeValue.includes('pro')) {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.28)\n : formatCreditsLabel(0.14)\n }\n }\n\n return formatCreditsLabel(0.14)\n }\n },\n KlingImage2VideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const modeWidget = node.widgets?.find(\n (w) => w.name === 'mode'\n ) as IComboWidget\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model_name'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n\n if (!modeWidget) {\n if (!modelWidget)\n return formatCreditsRangeLabel(0.14, 2.8, {\n note: '(varies with model, mode & duration)'\n })\n\n const modelValue = String(modelWidget.value)\n if (\n modelValue.includes('v2-1-master') ||\n modelValue.includes('v2-master')\n ) {\n return formatCreditsLabel(1.4)\n } else if (\n modelValue.includes('v1-6') ||\n modelValue.includes('v1-5')\n ) {\n return formatCreditsLabel(0.28)\n }\n return formatCreditsLabel(0.14)\n }\n\n const modeValue = String(modeWidget.value)\n const durationValue = String(durationWidget.value)\n const modelValue = String(modelWidget.value)\n\n // Same pricing matrix as KlingTextToVideoNode\n if (modelValue.includes('v2-5-turbo')) {\n if (durationValue.includes('10')) {\n return formatCreditsLabel(0.7)\n }\n return formatCreditsLabel(0.35) // 5s default\n } else if (\n modelValue.includes('v2-1-master') ||\n modelValue.includes('v2-master')\n ) {\n if (durationValue.includes('10')) {\n return formatCreditsLabel(2.8)\n }\n return formatCreditsLabel(1.4) // 5s default\n } else if (\n modelValue.includes('v2-1') ||\n modelValue.includes('v1-6') ||\n modelValue.includes('v1-5')\n ) {\n if (modeValue.includes('pro')) {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.56)\n : formatCreditsLabel(0.28)\n }\n } else if (modelValue.includes('v1')) {\n if (modeValue.includes('pro')) {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return durationValue.includes('10')\n ? formatCreditsLabel(0.28)\n : formatCreditsLabel(0.14)\n }\n }\n\n return formatCreditsLabel(0.14)\n }\n },\n KlingImageGenerationNode: {\n displayPrice: (node: LGraphNode): string => {\n const imageInputWidget = node.inputs?.find((i) => i.name === 'image')\n // If link is not null => image is connected => modality is image to image\n const modality = imageInputWidget?.link\n ? 'image to image'\n : 'text to image'\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model_name'\n ) as IComboWidget\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n\n if (!modelWidget)\n return formatCreditsRangeLabel(0.0035, 0.028, {\n suffix: ' x n/Run',\n note: '(varies with modality & model)'\n })\n\n const model = String(modelWidget.value)\n const n = Number(nWidget?.value) || 1\n let basePrice = 0.014 // default\n\n if (modality.includes('text to image')) {\n if (model.includes('kling-v1-5') || model.includes('kling-v2')) {\n basePrice = 0.014\n } else if (model.includes('kling-v1')) {\n basePrice = 0.0035\n }\n } else if (modality.includes('image to image')) {\n if (model.includes('kling-v1-5')) {\n basePrice = 0.028\n } else if (model.includes('kling-v1')) {\n basePrice = 0.0035\n }\n }\n\n const totalCost = basePrice * n\n return formatCreditsLabel(totalCost)\n }\n },\n KlingLipSyncAudioToVideoNode: {\n displayPrice: formatCreditsLabel(0.1, { approximate: true })\n },\n KlingLipSyncTextToVideoNode: {\n displayPrice: formatCreditsLabel(0.1, { approximate: true })\n },\n KlingSingleImageVideoEffectNode: {\n displayPrice: (node: LGraphNode): string => {\n const effectSceneWidget = node.widgets?.find(\n (w) => w.name === 'effect_scene'\n ) as IComboWidget\n\n if (!effectSceneWidget)\n return formatCreditsRangeLabel(0.28, 0.49, {\n note: '(varies with effect scene)'\n })\n\n const effectScene = String(effectSceneWidget.value)\n if (\n effectScene.includes('fuzzyfuzzy') ||\n effectScene.includes('squish')\n ) {\n return formatCreditsLabel(0.28)\n } else if (effectScene.includes('dizzydizzy')) {\n return formatCreditsLabel(0.49)\n } else if (effectScene.includes('bloombloom')) {\n return formatCreditsLabel(0.49)\n } else if (effectScene.includes('expansion')) {\n return formatCreditsLabel(0.28)\n }\n\n return formatCreditsLabel(0.28)\n }\n },\n KlingStartEndFrameNode: {\n displayPrice: (node: LGraphNode): string => {\n // Same pricing as KlingTextToVideoNode per CSV (\"Same as text to video\")\n const modeWidget = node.widgets?.find(\n (w) => w.name === 'mode'\n ) as IComboWidget\n if (!modeWidget)\n return formatCreditsRangeLabel(0.14, 2.8, {\n note: '(varies with model, mode & duration)'\n })\n\n const modeValue = String(modeWidget.value)\n\n // Same pricing matrix as KlingTextToVideoNode\n if (modeValue.includes('v2-5-turbo')) {\n if (modeValue.includes('10')) {\n return formatCreditsLabel(0.7)\n }\n return formatCreditsLabel(0.35) // 5s default\n } else if (modeValue.includes('v2-1')) {\n if (modeValue.includes('10s')) {\n return formatCreditsLabel(0.98) // pro, 10s\n }\n return formatCreditsLabel(0.49) // pro, 5s default\n } else if (modeValue.includes('v2-master')) {\n if (modeValue.includes('10s')) {\n return formatCreditsLabel(2.8)\n }\n return formatCreditsLabel(1.4) // 5s default\n } else if (modeValue.includes('v1-6')) {\n if (modeValue.includes('pro')) {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.56)\n : formatCreditsLabel(0.28)\n }\n } else if (modeValue.includes('v1')) {\n if (modeValue.includes('pro')) {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.28)\n : formatCreditsLabel(0.14)\n }\n }\n\n return formatCreditsLabel(0.14)\n }\n },\n KlingTextToVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const modeWidget = node.widgets?.find(\n (w) => w.name === 'mode'\n ) as IComboWidget\n if (!modeWidget)\n return formatCreditsRangeLabel(0.14, 2.8, {\n note: '(varies with model, mode & duration)'\n })\n\n const modeValue = String(modeWidget.value)\n\n // Pricing matrix from CSV data based on mode string content\n if (modeValue.includes('v2-5-turbo')) {\n if (modeValue.includes('10')) {\n return formatCreditsLabel(0.7)\n }\n return formatCreditsLabel(0.35) // 5s default\n } else if (modeValue.includes('v2-1-master')) {\n if (modeValue.includes('10s')) {\n return formatCreditsLabel(2.8) // price is the same as for v2-master model\n }\n return formatCreditsLabel(1.4) // price is the same as for v2-master model\n } else if (modeValue.includes('v2-master')) {\n if (modeValue.includes('10s')) {\n return formatCreditsLabel(2.8)\n }\n return formatCreditsLabel(1.4) // 5s default\n } else if (modeValue.includes('v1-6')) {\n if (modeValue.includes('pro')) {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.56)\n : formatCreditsLabel(0.28)\n }\n } else if (modeValue.includes('v1')) {\n if (modeValue.includes('pro')) {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.98)\n : formatCreditsLabel(0.49)\n } else {\n return modeValue.includes('10s')\n ? formatCreditsLabel(0.28)\n : formatCreditsLabel(0.14)\n }\n }\n\n return formatCreditsLabel(0.14)\n }\n },\n KlingVideoExtendNode: {\n displayPrice: formatCreditsLabel(0.28)\n },\n KlingVirtualTryOnNode: {\n displayPrice: formatCreditsLabel(0.07)\n },\n KlingOmniProTextToVideoNode: {\n displayPrice: makeOmniProDurationCalculator(0.112)\n },\n KlingOmniProFirstLastFrameNode: {\n displayPrice: makeOmniProDurationCalculator(0.112)\n },\n KlingOmniProImageToVideoNode: {\n displayPrice: makeOmniProDurationCalculator(0.112)\n },\n KlingOmniProVideoToVideoNode: {\n displayPrice: makeOmniProDurationCalculator(0.168)\n },\n KlingMotionControl: {\n displayPrice: klingMotionControlPricingCalculator\n },\n KlingOmniProEditVideoNode: {\n displayPrice: formatCreditsLabel(0.168, { suffix: '/second' })\n },\n KlingOmniProImageNode: {\n displayPrice: formatCreditsLabel(0.028)\n },\n KlingTextToVideoWithAudio: {\n displayPrice: klingVideoWithAudioPricingCalculator\n },\n KlingImageToVideoWithAudio: {\n displayPrice: klingVideoWithAudioPricingCalculator\n },\n LumaImageToVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n // Same pricing as LumaVideoNode per CSV\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n\n if (!modelWidget || !resolutionWidget || !durationWidget) {\n return formatCreditsRangeLabel(0.2, 16.4, {\n note: '(varies with model, resolution & duration)'\n })\n }\n\n const model = String(modelWidget.value)\n const resolution = String(resolutionWidget.value).toLowerCase()\n const duration = String(durationWidget.value)\n\n if (model.includes('ray-flash-2')) {\n if (duration.includes('5s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(3.13)\n if (resolution.includes('1080p')) return formatCreditsLabel(0.79)\n if (resolution.includes('720p')) return formatCreditsLabel(0.34)\n if (resolution.includes('540p')) return formatCreditsLabel(0.2)\n } else if (duration.includes('9s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(5.65)\n if (resolution.includes('1080p')) return formatCreditsLabel(1.42)\n if (resolution.includes('720p')) return formatCreditsLabel(0.61)\n if (resolution.includes('540p')) return formatCreditsLabel(0.36)\n }\n } else if (model.includes('ray-2')) {\n if (duration.includes('5s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(9.11)\n if (resolution.includes('1080p')) return formatCreditsLabel(2.27)\n if (resolution.includes('720p')) return formatCreditsLabel(1.02)\n if (resolution.includes('540p')) return formatCreditsLabel(0.57)\n } else if (duration.includes('9s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(16.4)\n if (resolution.includes('1080p')) return formatCreditsLabel(4.1)\n if (resolution.includes('720p')) return formatCreditsLabel(1.83)\n if (resolution.includes('540p')) return formatCreditsLabel(1.03)\n }\n } else if (model.includes('ray-1-6')) {\n return formatCreditsLabel(0.5)\n }\n\n return formatCreditsLabel(0.79)\n }\n },\n LumaVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n\n if (!modelWidget || !resolutionWidget || !durationWidget) {\n return formatCreditsRangeLabel(0.2, 16.4, {\n note: '(varies with model, resolution & duration)'\n })\n }\n\n const model = String(modelWidget.value)\n const resolution = String(resolutionWidget.value).toLowerCase()\n const duration = String(durationWidget.value)\n\n if (model.includes('ray-flash-2')) {\n if (duration.includes('5s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(3.13)\n if (resolution.includes('1080p')) return formatCreditsLabel(0.79)\n if (resolution.includes('720p')) return formatCreditsLabel(0.34)\n if (resolution.includes('540p')) return formatCreditsLabel(0.2)\n } else if (duration.includes('9s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(5.65)\n if (resolution.includes('1080p')) return formatCreditsLabel(1.42)\n if (resolution.includes('720p')) return formatCreditsLabel(0.61)\n if (resolution.includes('540p')) return formatCreditsLabel(0.36)\n }\n } else if (model.includes('ray-2')) {\n if (duration.includes('5s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(9.11)\n if (resolution.includes('1080p')) return formatCreditsLabel(2.27)\n if (resolution.includes('720p')) return formatCreditsLabel(1.02)\n if (resolution.includes('540p')) return formatCreditsLabel(0.57)\n } else if (duration.includes('9s')) {\n if (resolution.includes('4k')) return formatCreditsLabel(16.4)\n if (resolution.includes('1080p')) return formatCreditsLabel(4.1)\n if (resolution.includes('720p')) return formatCreditsLabel(1.83)\n if (resolution.includes('540p')) return formatCreditsLabel(1.03)\n }\n } else if (model.includes('ray-1-6')) {\n return formatCreditsLabel(0.5)\n }\n\n return formatCreditsLabel(0.79)\n }\n },\n MinimaxImageToVideoNode: {\n displayPrice: formatCreditsLabel(0.43)\n },\n MinimaxTextToVideoNode: {\n displayPrice: formatCreditsLabel(0.43)\n },\n MinimaxHailuoVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n\n if (!resolutionWidget || !durationWidget) {\n return formatCreditsRangeLabel(0.28, 0.56, {\n note: '(varies with resolution & duration)'\n })\n }\n\n const resolution = String(resolutionWidget.value)\n const duration = String(durationWidget.value)\n\n if (resolution.includes('768P')) {\n if (duration.includes('6')) return formatCreditsLabel(0.28)\n if (duration.includes('10')) return formatCreditsLabel(0.56)\n } else if (resolution.includes('1080P')) {\n if (duration.includes('6')) return formatCreditsLabel(0.49)\n }\n\n return formatCreditsLabel(0.43) // default median\n }\n },\n OpenAIDalle2: {\n displayPrice: (node: LGraphNode): string => {\n const sizeWidget = node.widgets?.find(\n (w) => w.name === 'size'\n ) as IComboWidget\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n\n if (!sizeWidget)\n return formatCreditsRangeLabel(0.016, 0.02, {\n suffix: ' x n/Run',\n note: '(varies with size & n)'\n })\n\n const size = String(sizeWidget.value)\n const n = Number(nWidget?.value) || 1\n let basePrice = 0.02 // default\n\n if (size.includes('1024x1024')) {\n basePrice = 0.02\n } else if (size.includes('512x512')) {\n basePrice = 0.018\n } else if (size.includes('256x256')) {\n basePrice = 0.016\n }\n\n const totalCost = Number((basePrice * n).toFixed(3))\n return formatCreditsLabel(totalCost)\n }\n },\n OpenAIDalle3: {\n displayPrice: (node: LGraphNode): string => {\n // Get size and quality widgets\n const sizeWidget = node.widgets?.find(\n (w) => w.name === 'size'\n ) as IComboWidget\n const qualityWidget = node.widgets?.find(\n (w) => w.name === 'quality'\n ) as IComboWidget\n\n if (!sizeWidget || !qualityWidget)\n return formatCreditsRangeLabel(0.04, 0.12, {\n note: '(varies with size & quality)'\n })\n\n const size = String(sizeWidget.value)\n const quality = String(qualityWidget.value)\n\n // Pricing matrix based on CSV data\n if (size.includes('1024x1024')) {\n return quality.includes('hd')\n ? formatCreditsLabel(0.08)\n : formatCreditsLabel(0.04)\n } else if (size.includes('1792x1024') || size.includes('1024x1792')) {\n return quality.includes('hd')\n ? formatCreditsLabel(0.12)\n : formatCreditsLabel(0.08)\n }\n\n // Default value\n return formatCreditsLabel(0.04)\n }\n },\n OpenAIGPTImage1: {\n displayPrice: (node: LGraphNode): string => {\n const qualityWidget = node.widgets?.find(\n (w) => w.name === 'quality'\n ) as IComboWidget\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n\n if (!qualityWidget)\n return formatCreditsRangeLabel(0.011, 0.3, {\n suffix: ' x n/Run',\n note: '(varies with quality & n)'\n })\n\n const quality = String(qualityWidget.value)\n const n = Number(nWidget?.value) || 1\n let range: [number, number] = [0.046, 0.07] // default medium\n\n if (quality.includes('high')) {\n range = [0.167, 0.3]\n } else if (quality.includes('medium')) {\n range = [0.046, 0.07]\n } else if (quality.includes('low')) {\n range = [0.011, 0.02]\n }\n\n if (n === 1) {\n return formatCreditsRangeLabel(range[0], range[1])\n }\n return formatCreditsRangeLabel(range[0], range[1], {\n suffix: ` x ${n}/Run`\n })\n }\n },\n PixverseImageToVideoNode: {\n displayPrice: pixversePricingCalculator\n },\n PixverseTextToVideoNode: {\n displayPrice: pixversePricingCalculator\n },\n PixverseTransitionVideoNode: {\n displayPrice: pixversePricingCalculator\n },\n RecraftCreativeUpscaleNode: {\n displayPrice: formatCreditsLabel(0.25)\n },\n RecraftCrispUpscaleNode: {\n displayPrice: formatCreditsLabel(0.004)\n },\n RecraftGenerateColorFromImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.04, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.04 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n RecraftGenerateImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.04, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.04 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n RecraftGenerateVectorImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.08, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.08 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n RecraftImageInpaintingNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.04, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.04 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n RecraftImageToImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.04, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.04 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n RecraftRemoveBackgroundNode: {\n displayPrice: formatCreditsLabel(0.01)\n },\n RecraftReplaceBackgroundNode: {\n displayPrice: formatCreditsLabel(0.04)\n },\n RecraftTextToImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.04, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.04 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n RecraftTextToVectorNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.08, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.08 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n RecraftVectorizeImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const nWidget = node.widgets?.find(\n (w) => w.name === 'n'\n ) as IComboWidget\n if (!nWidget) return formatCreditsLabel(0.01, { suffix: ' x n/Run' })\n\n const n = Number(nWidget.value) || 1\n const cost = Number((0.01 * n).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n StabilityStableImageSD_3_5Node: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n\n if (!modelWidget)\n return formatCreditsRangeLabel(0.035, 0.065, {\n note: '(varies with model)'\n })\n\n const model = String(modelWidget.value).toLowerCase()\n if (model.includes('large')) {\n return formatCreditsLabel(0.065)\n } else if (model.includes('medium')) {\n return formatCreditsLabel(0.035)\n }\n\n return formatCreditsLabel(0.035)\n }\n },\n StabilityStableImageUltraNode: {\n displayPrice: formatCreditsLabel(0.08)\n },\n StabilityUpscaleConservativeNode: {\n displayPrice: formatCreditsLabel(0.25)\n },\n StabilityUpscaleCreativeNode: {\n displayPrice: formatCreditsLabel(0.25)\n },\n StabilityUpscaleFastNode: {\n displayPrice: formatCreditsLabel(0.01)\n },\n StabilityTextToAudio: {\n displayPrice: formatCreditsLabel(0.2)\n },\n StabilityAudioToAudio: {\n displayPrice: formatCreditsLabel(0.2)\n },\n StabilityAudioInpaint: {\n displayPrice: formatCreditsLabel(0.2)\n },\n VeoVideoGenerationNode: {\n displayPrice: (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration_seconds'\n ) as IComboWidget\n\n if (!durationWidget)\n return formatCreditsRangeLabel(2.5, 5.0, {\n note: '(varies with duration)'\n })\n\n const price = 0.5 * Number(durationWidget.value)\n return formatCreditsLabel(price)\n }\n },\n Veo3VideoGenerationNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const generateAudioWidget = node.widgets?.find(\n (w) => w.name === 'generate_audio'\n ) as IComboWidget\n\n if (!modelWidget || !generateAudioWidget) {\n return formatCreditsRangeLabel(0.8, 3.2, {\n note: '(varies with model & audio generation)'\n })\n }\n\n const model = String(modelWidget.value)\n const generateAudio =\n String(generateAudioWidget.value).toLowerCase() === 'true'\n\n if (\n model.includes('veo-3.0-fast-generate-001') ||\n model.includes('veo-3.1-fast-generate')\n ) {\n return generateAudio\n ? formatCreditsLabel(1.2)\n : formatCreditsLabel(0.8)\n } else if (\n model.includes('veo-3.0-generate-001') ||\n model.includes('veo-3.1-generate')\n ) {\n return generateAudio\n ? formatCreditsLabel(3.2)\n : formatCreditsLabel(1.6)\n }\n\n // Default fallback\n return formatCreditsRangeLabel(0.8, 3.2)\n }\n },\n Veo3FirstLastFrameNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const generateAudioWidget = node.widgets?.find(\n (w) => w.name === 'generate_audio'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n\n if (!modelWidget || !generateAudioWidget || !durationWidget) {\n return formatCreditsRangeLabel(0.4, 3.2, {\n note: '(varies with model & audio generation)'\n })\n }\n\n const model = String(modelWidget.value)\n const generateAudio =\n String(generateAudioWidget.value).toLowerCase() === 'true'\n const seconds = parseFloat(String(durationWidget.value))\n\n let pricePerSecond: number | null = null\n if (model.includes('veo-3.1-fast-generate')) {\n pricePerSecond = generateAudio ? 0.15 : 0.1\n } else if (model.includes('veo-3.1-generate')) {\n pricePerSecond = generateAudio ? 0.4 : 0.2\n }\n if (pricePerSecond === null) {\n return formatCreditsRangeLabel(0.4, 3.2)\n }\n const cost = pricePerSecond * seconds\n return formatCreditsLabel(cost)\n }\n },\n LumaImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const aspectRatioWidget = node.widgets?.find(\n (w) => w.name === 'aspect_ratio'\n ) as IComboWidget\n\n if (!modelWidget || !aspectRatioWidget) {\n return formatCreditsRangeLabel(0.0064, 0.026, {\n note: '(varies with model & aspect ratio)'\n })\n }\n\n const model = String(modelWidget.value)\n\n if (model.includes('photon-flash-1')) {\n return formatCreditsLabel(0.0027)\n } else if (model.includes('photon-1')) {\n return formatCreditsLabel(0.0104)\n }\n\n return formatCreditsLabel(0.0246)\n }\n },\n LumaImageModifyNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n\n if (!modelWidget) {\n return formatCreditsRangeLabel(0.0027, 0.0104, {\n note: '(varies with model)'\n })\n }\n\n const model = String(modelWidget.value)\n\n if (model.includes('photon-flash-1')) {\n return formatCreditsLabel(0.0027)\n } else if (model.includes('photon-1')) {\n return formatCreditsLabel(0.0104)\n }\n\n return formatCreditsLabel(0.0246)\n }\n },\n MoonvalleyTxt2VideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const lengthWidget = node.widgets?.find(\n (w) => w.name === 'length'\n ) as IComboWidget\n\n // If no length widget exists, default to 5s pricing\n if (!lengthWidget) return formatCreditsLabel(1.5)\n\n const length = String(lengthWidget.value)\n if (length === '5s') {\n return formatCreditsLabel(1.5)\n } else if (length === '10s') {\n return formatCreditsLabel(3.0)\n }\n\n return formatCreditsLabel(1.5)\n }\n },\n MoonvalleyImg2VideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const lengthWidget = node.widgets?.find(\n (w) => w.name === 'length'\n ) as IComboWidget\n\n // If no length widget exists, default to 5s pricing\n if (!lengthWidget) return formatCreditsLabel(1.5)\n\n const length = String(lengthWidget.value)\n if (length === '5s') {\n return formatCreditsLabel(1.5)\n } else if (length === '10s') {\n return formatCreditsLabel(3.0)\n }\n\n return formatCreditsLabel(1.5)\n }\n },\n MoonvalleyVideo2VideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const lengthWidget = node.widgets?.find(\n (w) => w.name === 'length'\n ) as IComboWidget\n\n // If no length widget exists, default to 5s pricing\n if (!lengthWidget) return formatCreditsLabel(2.25)\n\n const length = String(lengthWidget.value)\n if (length === '5s') {\n return formatCreditsLabel(2.25)\n } else if (length === '10s') {\n return formatCreditsLabel(4.0)\n }\n\n return formatCreditsLabel(2.25)\n }\n },\n // Runway nodes - using actual node names from ComfyUI\n RunwayTextToImageNode: {\n displayPrice: formatCreditsLabel(0.11)\n },\n RunwayImageToVideoNodeGen3a: {\n displayPrice: calculateRunwayDurationPrice\n },\n RunwayImageToVideoNodeGen4: {\n displayPrice: calculateRunwayDurationPrice\n },\n RunwayFirstLastFrameNode: {\n displayPrice: calculateRunwayDurationPrice\n },\n // Rodin nodes - all have the same pricing structure\n Rodin3D_Regular: {\n displayPrice: formatCreditsLabel(0.4)\n },\n Rodin3D_Detail: {\n displayPrice: formatCreditsLabel(0.4)\n },\n Rodin3D_Smooth: {\n displayPrice: formatCreditsLabel(0.4)\n },\n Rodin3D_Sketch: {\n displayPrice: formatCreditsLabel(0.4)\n },\n // Tripo nodes - using actual node names from ComfyUI\n TripoTextToModelNode: {\n displayPrice: (node: LGraphNode): string =>\n calculateTripo3DGenerationPrice(node, 'text')\n },\n TripoImageToModelNode: {\n displayPrice: (node: LGraphNode): string =>\n calculateTripo3DGenerationPrice(node, 'image')\n },\n TripoMultiviewToModelNode: {\n displayPrice: (node: LGraphNode): string =>\n calculateTripo3DGenerationPrice(node, 'multiview')\n },\n TripoTextureNode: {\n displayPrice: (node: LGraphNode): string => {\n const textureQualityWidget = node.widgets?.find(\n (w) => w.name === 'texture_quality'\n ) as IComboWidget\n\n if (!textureQualityWidget)\n return formatCreditsRangeLabel(0.1, 0.2, {\n note: '(varies with quality)'\n })\n\n const textureQuality = String(textureQualityWidget.value)\n return textureQuality.includes('detailed')\n ? formatCreditsLabel(0.2)\n : formatCreditsLabel(0.1)\n }\n },\n TripoRigNode: {\n displayPrice: '$0.25/Run'\n },\n TripoConversionNode: {\n displayPrice: (node: LGraphNode): string => {\n const getWidgetValue = (name: string) =>\n node.widgets?.find((w) => w.name === name)?.value\n\n const getNumber = (name: string, defaultValue: number): number => {\n const raw = getWidgetValue(name)\n if (raw === undefined || raw === null || raw === '')\n return defaultValue\n if (typeof raw === 'number')\n return Number.isFinite(raw) ? raw : defaultValue\n const n = Number(raw)\n return Number.isFinite(n) ? n : defaultValue\n }\n\n const getBool = (name: string, defaultValue: boolean): boolean => {\n const v = getWidgetValue(name)\n if (v === undefined || v === null) return defaultValue\n\n if (typeof v === 'number') return v !== 0\n const lower = String(v).toLowerCase()\n if (lower === 'true') return true\n if (lower === 'false') return false\n return defaultValue\n }\n\n let hasAdvancedParam = false\n\n // ---- booleans that trigger advanced when true ----\n if (getBool('quad', false)) hasAdvancedParam = true\n if (getBool('force_symmetry', false)) hasAdvancedParam = true\n if (getBool('flatten_bottom', false)) hasAdvancedParam = true\n if (getBool('pivot_to_center_bottom', false)) hasAdvancedParam = true\n if (getBool('with_animation', false)) hasAdvancedParam = true\n if (getBool('pack_uv', false)) hasAdvancedParam = true\n if (getBool('bake', false)) hasAdvancedParam = true\n if (getBool('export_vertex_colors', false)) hasAdvancedParam = true\n if (getBool('animate_in_place', false)) hasAdvancedParam = true\n\n // ---- numeric params with special default sentinels ----\n const faceLimit = getNumber('face_limit', -1)\n if (faceLimit !== -1) hasAdvancedParam = true\n\n const textureSize = getNumber('texture_size', 4096)\n if (textureSize !== 4096) hasAdvancedParam = true\n\n const flattenBottomThreshold = getNumber(\n 'flatten_bottom_threshold',\n 0.0\n )\n if (flattenBottomThreshold !== 0.0) hasAdvancedParam = true\n\n const scaleFactor = getNumber('scale_factor', 1.0)\n if (scaleFactor !== 1.0) hasAdvancedParam = true\n\n // ---- string / combo params with non-default values ----\n const textureFormatRaw = String(\n getWidgetValue('texture_format') ?? 'JPEG'\n ).toUpperCase()\n if (textureFormatRaw !== 'JPEG') hasAdvancedParam = true\n\n const partNamesRaw = String(getWidgetValue('part_names') ?? '')\n if (partNamesRaw.trim().length > 0) hasAdvancedParam = true\n\n const fbxPresetRaw = String(\n getWidgetValue('fbx_preset') ?? 'blender'\n ).toLowerCase()\n if (fbxPresetRaw !== 'blender') hasAdvancedParam = true\n\n const exportOrientationRaw = String(\n getWidgetValue('export_orientation') ?? 'default'\n ).toLowerCase()\n if (exportOrientationRaw !== 'default') hasAdvancedParam = true\n\n const credits = hasAdvancedParam ? 10 : 5\n return formatCreditsLabel(credits * 0.01)\n }\n },\n TripoRetargetNode: {\n displayPrice: formatCreditsLabel(0.1)\n },\n TripoRefineNode: {\n displayPrice: formatCreditsLabel(0.3)\n },\n MeshyTextToModelNode: {\n displayPrice: formatCreditsLabel(meshyCreditsToUsd(20))\n },\n MeshyRefineNode: {\n displayPrice: formatCreditsLabel(meshyCreditsToUsd(10))\n },\n MeshyImageToModelNode: {\n displayPrice: calculateMeshyImageToModelPrice\n },\n MeshyMultiImageToModelNode: {\n displayPrice: calculateMeshyMultiImageToModelPrice\n },\n MeshyRigModelNode: {\n displayPrice: formatCreditsLabel(meshyCreditsToUsd(5))\n },\n MeshyAnimateModelNode: {\n displayPrice: formatCreditsLabel(meshyCreditsToUsd(3))\n },\n MeshyTextureNode: {\n displayPrice: formatCreditsLabel(meshyCreditsToUsd(10))\n },\n // Google/Gemini nodes\n GeminiNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n\n if (!modelWidget) return 'Token-based'\n\n const model = String(modelWidget.value)\n\n if (model.includes('gemini-2.5-flash-preview-04-17')) {\n return formatCreditsListLabel([0.0003, 0.0025], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gemini-2.5-flash')) {\n return formatCreditsListLabel([0.0003, 0.0025], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gemini-2.5-pro-preview-05-06')) {\n return formatCreditsListLabel([0.00125, 0.01], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gemini-2.5-pro')) {\n return formatCreditsListLabel([0.00125, 0.01], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gemini-3-pro-preview')) {\n return formatCreditsListLabel([0.002, 0.012], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n }\n // For other Gemini models, show token-based pricing info\n return 'Token-based'\n }\n },\n GeminiImageNode: {\n displayPrice: formatCreditsLabel(0.039, {\n suffix: '/Image (1K)',\n approximate: true\n })\n },\n GeminiImage2Node: {\n displayPrice: (node: LGraphNode): string => {\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n\n if (!resolutionWidget) return 'Token-based'\n\n const resolution = String(resolutionWidget.value)\n if (resolution.includes('1K')) {\n return formatCreditsLabel(0.134, {\n suffix: '/Image',\n approximate: true\n })\n } else if (resolution.includes('2K')) {\n return formatCreditsLabel(0.134, {\n suffix: '/Image',\n approximate: true\n })\n } else if (resolution.includes('4K')) {\n return formatCreditsLabel(0.24, {\n suffix: '/Image',\n approximate: true\n })\n }\n return 'Token-based'\n }\n },\n // OpenAI nodes\n OpenAIChatNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n\n if (!modelWidget) return 'Token-based'\n\n const model = String(modelWidget.value)\n\n // Specific pricing for exposed models based on official pricing data (converted to per 1K tokens)\n if (model.includes('o4-mini')) {\n return formatCreditsListLabel([0.0011, 0.0044], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('o1-pro')) {\n return formatCreditsListLabel([0.15, 0.6], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('o1')) {\n return formatCreditsListLabel([0.015, 0.06], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('o3-mini')) {\n return formatCreditsListLabel([0.0011, 0.0044], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('o3')) {\n return formatCreditsListLabel([0.01, 0.04], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gpt-4o')) {\n return formatCreditsListLabel([0.0025, 0.01], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gpt-4.1-nano')) {\n return formatCreditsListLabel([0.0001, 0.0004], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gpt-4.1-mini')) {\n return formatCreditsListLabel([0.0004, 0.0016], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gpt-4.1')) {\n return formatCreditsListLabel([0.002, 0.008], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gpt-5-nano')) {\n return formatCreditsListLabel([0.00005, 0.0004], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gpt-5-mini')) {\n return formatCreditsListLabel([0.00025, 0.002], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n } else if (model.includes('gpt-5')) {\n return formatCreditsListLabel([0.00125, 0.01], {\n suffix: ' per 1K tokens',\n approximate: true,\n separator: '-'\n })\n }\n return 'Token-based'\n }\n },\n ViduTextToVideoNode: {\n displayPrice: formatCreditsLabel(0.4)\n },\n ViduImageToVideoNode: {\n displayPrice: formatCreditsLabel(0.4)\n },\n ViduReferenceVideoNode: {\n displayPrice: formatCreditsLabel(0.4)\n },\n ViduStartEndToVideoNode: {\n displayPrice: formatCreditsLabel(0.4)\n },\n ByteDanceImageNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n\n if (!modelWidget) return 'Token-based'\n\n const model = String(modelWidget.value)\n\n if (model.includes('seedream-3-0-t2i')) {\n return formatCreditsLabel(0.03)\n }\n return 'Token-based'\n }\n },\n ByteDanceImageEditNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n\n if (!modelWidget) return 'Token-based'\n\n const model = String(modelWidget.value)\n\n if (model.includes('seededit-3-0-i2i')) {\n return formatCreditsLabel(0.03)\n }\n return 'Token-based'\n }\n },\n ByteDanceSeedreamNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n\n const model = String(modelWidget?.value ?? '').toLowerCase()\n let pricePerImage = 0.03 // default for seedream-4-0-250828 and fallback\n if (model.includes('seedream-4-5-251128')) {\n pricePerImage = 0.04\n } else if (model.includes('seedream-4-0-250828')) {\n pricePerImage = 0.03\n }\n return formatCreditsLabel(pricePerImage, {\n suffix: ' x images/Run',\n approximate: true\n })\n }\n },\n ByteDanceTextToVideoNode: {\n displayPrice: byteDanceVideoPricingCalculator\n },\n ByteDanceImageToVideoNode: {\n displayPrice: byteDanceVideoPricingCalculator\n },\n ByteDanceFirstLastFrameNode: {\n displayPrice: byteDanceVideoPricingCalculator\n },\n ByteDanceImageReferenceNode: {\n displayPrice: byteDanceVideoPricingCalculator\n },\n WanTextToVideoApi: {\n displayPrice: (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'size'\n ) as IComboWidget\n\n if (!durationWidget || !resolutionWidget)\n return formatCreditsRangeLabel(0.05, 0.15, { suffix: '/second' })\n\n const seconds = parseFloat(String(durationWidget.value))\n const resolutionStr = String(resolutionWidget.value).toLowerCase()\n\n const resKey = resolutionStr.includes('1080')\n ? '1080p'\n : resolutionStr.includes('720')\n ? '720p'\n : resolutionStr.includes('480')\n ? '480p'\n : (resolutionStr.match(/^\\s*(\\d{3,4}p)/)?.[1] ?? '')\n\n const pricePerSecond: Record<string, number> = {\n '480p': 0.05,\n '720p': 0.1,\n '1080p': 0.15\n }\n\n const pps = pricePerSecond[resKey]\n if (isNaN(seconds) || !pps)\n return formatCreditsRangeLabel(0.05, 0.15, { suffix: '/second' })\n\n const cost = Number((pps * seconds).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n WanImageToVideoApi: {\n displayPrice: (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n\n if (!durationWidget || !resolutionWidget)\n return formatCreditsRangeLabel(0.05, 0.15, { suffix: '/second' })\n\n const seconds = parseFloat(String(durationWidget.value))\n const resolution = String(resolutionWidget.value).trim().toLowerCase()\n\n const pricePerSecond: Record<string, number> = {\n '480p': 0.05,\n '720p': 0.1,\n '1080p': 0.15\n }\n\n const pps = pricePerSecond[resolution]\n if (isNaN(seconds) || !pps)\n return formatCreditsRangeLabel(0.05, 0.15, { suffix: '/second' })\n\n const cost = Number((pps * seconds).toFixed(2))\n return formatCreditsLabel(cost)\n }\n },\n WanTextToImageApi: {\n displayPrice: formatCreditsLabel(0.03)\n },\n WanImageToImageApi: {\n displayPrice: formatCreditsLabel(0.03)\n },\n LtxvApiTextToVideo: {\n displayPrice: ltxvPricingCalculator\n },\n LtxvApiImageToVideo: {\n displayPrice: ltxvPricingCalculator\n },\n WanReferenceVideoApi: {\n displayPrice: (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const sizeWidget = node.widgets?.find(\n (w) => w.name === 'size'\n ) as IComboWidget\n\n if (!durationWidget || !sizeWidget) {\n return formatCreditsRangeLabel(0.7, 1.5, {\n note: '(varies with size & duration)'\n })\n }\n\n const seconds = parseFloat(String(durationWidget.value))\n const sizeStr = String(sizeWidget.value).toLowerCase()\n\n const rate = sizeStr.includes('1080p') ? 0.15 : 0.1\n const inputMin = 2 * rate\n const inputMax = 5 * rate\n const outputPrice = seconds * rate\n\n const minTotal = inputMin + outputPrice\n const maxTotal = inputMax + outputPrice\n\n return formatCreditsRangeLabel(minTotal, maxTotal)\n }\n },\n Vidu2TextToVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n\n if (!durationWidget || !resolutionWidget) {\n return formatCreditsRangeLabel(0.075, 0.6, {\n note: '(varies with duration & resolution)'\n })\n }\n\n const duration = parseFloat(String(durationWidget.value))\n const resolution = String(resolutionWidget.value).toLowerCase()\n\n // Text-to-Video uses Q2 model only\n // 720P: Starts at $0.075, +$0.025/sec\n // 1080P: Starts at $0.10, +$0.05/sec\n let basePrice: number\n let pricePerSecond: number\n\n if (resolution.includes('1080')) {\n basePrice = 0.1\n pricePerSecond = 0.05\n } else {\n // 720P default\n basePrice = 0.075\n pricePerSecond = 0.025\n }\n\n if (!Number.isFinite(duration) || duration <= 0) {\n return formatCreditsRangeLabel(0.075, 0.6, {\n note: '(varies with duration & resolution)'\n })\n }\n\n const cost = basePrice + pricePerSecond * (duration - 1)\n return formatCreditsLabel(cost)\n }\n },\n Vidu2ImageToVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n\n if (!modelWidget || !durationWidget || !resolutionWidget) {\n return formatCreditsRangeLabel(0.04, 1.0, {\n note: '(varies with model, duration & resolution)'\n })\n }\n\n const model = String(modelWidget.value).toLowerCase()\n const duration = parseFloat(String(durationWidget.value))\n const resolution = String(resolutionWidget.value).toLowerCase()\n const is1080p = resolution.includes('1080')\n\n let basePrice: number\n let pricePerSecond: number\n\n if (model.includes('q2-pro-fast')) {\n // Q2-pro-fast: 720P $0.04+$0.01/sec, 1080P $0.08+$0.02/sec\n basePrice = is1080p ? 0.08 : 0.04\n pricePerSecond = is1080p ? 0.02 : 0.01\n } else if (model.includes('q2-pro')) {\n // Q2-pro: 720P $0.075+$0.05/sec, 1080P $0.275+$0.075/sec\n basePrice = is1080p ? 0.275 : 0.075\n pricePerSecond = is1080p ? 0.075 : 0.05\n } else if (model.includes('q2-turbo')) {\n // Q2-turbo: 720P special pricing, 1080P $0.175+$0.05/sec\n if (is1080p) {\n basePrice = 0.175\n pricePerSecond = 0.05\n } else {\n // 720P: $0.04 at 1s, $0.05 at 2s, +$0.05/sec beyond 2s\n if (duration <= 1) {\n return formatCreditsLabel(0.04)\n }\n if (duration <= 2) {\n return formatCreditsLabel(0.05)\n }\n const cost = 0.05 + 0.05 * (duration - 2)\n return formatCreditsLabel(cost)\n }\n } else {\n return formatCreditsRangeLabel(0.04, 1.0, {\n note: '(varies with model, duration & resolution)'\n })\n }\n\n if (!Number.isFinite(duration) || duration <= 0) {\n return formatCreditsRangeLabel(0.04, 1.0, {\n note: '(varies with model, duration & resolution)'\n })\n }\n\n const cost = basePrice + pricePerSecond * (duration - 1)\n return formatCreditsLabel(cost)\n }\n },\n Vidu2ReferenceVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n const audioWidget = node.widgets?.find(\n (w) => w.name === 'audio'\n ) as IComboWidget\n\n if (!durationWidget) {\n return formatCreditsRangeLabel(0.125, 1.5, {\n note: '(varies with duration, resolution & audio)'\n })\n }\n\n const duration = parseFloat(String(durationWidget.value))\n const resolution = String(resolutionWidget?.value ?? '').toLowerCase()\n const is1080p = resolution.includes('1080')\n\n // Check if audio is enabled (adds $0.75)\n const audioValue = audioWidget?.value\n const hasAudio =\n audioValue !== undefined &&\n audioValue !== null &&\n String(audioValue).toLowerCase() !== 'false' &&\n String(audioValue).toLowerCase() !== 'none' &&\n audioValue !== ''\n\n // Reference-to-Video uses Q2 model\n // 720P: Starts at $0.125, +$0.025/sec\n // 1080P: Starts at $0.375, +$0.05/sec\n let basePrice: number\n let pricePerSecond: number\n\n if (is1080p) {\n basePrice = 0.375\n pricePerSecond = 0.05\n } else {\n // 720P default\n basePrice = 0.125\n pricePerSecond = 0.025\n }\n\n let cost = basePrice\n if (Number.isFinite(duration) && duration > 0) {\n cost = basePrice + pricePerSecond * (duration - 1)\n }\n\n // Audio adds $0.75 on top\n if (hasAudio) {\n cost += 0.075\n }\n\n return formatCreditsLabel(cost)\n }\n },\n Vidu2StartEndToVideoNode: {\n displayPrice: (node: LGraphNode): string => {\n const modelWidget = node.widgets?.find(\n (w) => w.name === 'model'\n ) as IComboWidget\n const durationWidget = node.widgets?.find(\n (w) => w.name === 'duration'\n ) as IComboWidget\n const resolutionWidget = node.widgets?.find(\n (w) => w.name === 'resolution'\n ) as IComboWidget\n\n if (!modelWidget || !durationWidget || !resolutionWidget) {\n return formatCreditsRangeLabel(0.04, 1.0, {\n note: '(varies with model, duration & resolution)'\n })\n }\n\n const model = String(modelWidget.value).toLowerCase()\n const duration = parseFloat(String(durationWidget.value))\n const resolution = String(resolutionWidget.value).toLowerCase()\n const is1080p = resolution.includes('1080')\n\n let basePrice: number\n let pricePerSecond: number\n\n if (model.includes('q2-pro-fast')) {\n // Q2-pro-fast: 720P $0.04+$0.01/sec, 1080P $0.08+$0.02/sec\n basePrice = is1080p ? 0.08 : 0.04\n pricePerSecond = is1080p ? 0.02 : 0.01\n } else if (model.includes('q2-pro')) {\n // Q2-pro: 720P $0.075+$0.05/sec, 1080P $0.275+$0.075/sec\n basePrice = is1080p ? 0.275 : 0.075\n pricePerSecond = is1080p ? 0.075 : 0.05\n } else if (model.includes('q2-turbo')) {\n // Q2-turbo: 720P special pricing, 1080P $0.175+$0.05/sec\n if (is1080p) {\n basePrice = 0.175\n pricePerSecond = 0.05\n } else {\n // 720P: $0.04 at 1s, $0.05 at 2s, +$0.05/sec beyond 2s\n if (!Number.isFinite(duration) || duration <= 1) {\n return formatCreditsLabel(0.04)\n }\n if (duration <= 2) {\n return formatCreditsLabel(0.05)\n }\n const cost = 0.05 + 0.05 * (duration - 2)\n return formatCreditsLabel(cost)\n }\n } else {\n return formatCreditsRangeLabel(0.04, 1.0, {\n note: '(varies with model, duration & resolution)'\n })\n }\n\n if (!Number.isFinite(duration) || duration <= 0) {\n return formatCreditsLabel(basePrice)\n }\n\n const cost = basePrice + pricePerSecond * (duration - 1)\n return formatCreditsLabel(cost)\n }\n }\n }\n\n/**\n * Composable to get node pricing information for API nodes\n */\nexport const useNodePricing = () => {\n /**\n * Get the price display for a node\n */\n const getNodeDisplayPrice = (node: LGraphNode): string => {\n if (!node.constructor?.nodeData?.api_node) return ''\n\n const nodeName = node.constructor.nodeData.name\n const priceConfig = apiNodeCosts[nodeName]\n\n if (!priceConfig) return ''\n\n // If it's a function, call it with the node to get dynamic pricing\n if (typeof priceConfig.displayPrice === 'function') {\n return safePricingExecution(priceConfig.displayPrice, node, '')\n }\n\n // Otherwise return the static price\n return priceConfig.displayPrice\n }\n\n const getNodePricingConfig = (node: LGraphNode) =>\n apiNodeCosts[node.constructor.nodeData?.name ?? '']\n\n const getRelevantWidgetNames = (nodeType: string): string[] => {\n const widgetMap: Record<string, string[]> = {\n KlingTextToVideoNode: ['mode', 'model_name', 'duration'],\n KlingImage2VideoNode: ['mode', 'model_name', 'duration'],\n KlingImageGenerationNode: ['modality', 'model_name', 'n'],\n KlingDualCharacterVideoEffectNode: ['mode', 'model_name', 'duration'],\n KlingSingleImageVideoEffectNode: ['effect_scene'],\n KlingStartEndFrameNode: ['mode', 'model_name', 'duration'],\n KlingTextToVideoWithAudio: ['duration', 'generate_audio'],\n KlingImageToVideoWithAudio: ['duration', 'generate_audio'],\n KlingOmniProTextToVideoNode: ['duration'],\n KlingOmniProFirstLastFrameNode: ['duration'],\n KlingOmniProImageToVideoNode: ['duration'],\n KlingOmniProVideoToVideoNode: ['duration'],\n KlingMotionControl: ['mode'],\n MinimaxHailuoVideoNode: ['resolution', 'duration'],\n OpenAIDalle3: ['size', 'quality'],\n OpenAIDalle2: ['size', 'n'],\n OpenAIVideoSora2: ['model', 'size', 'duration'],\n OpenAIGPTImage1: ['quality', 'n'],\n IdeogramV1: ['num_images', 'turbo'],\n IdeogramV2: ['num_images', 'turbo'],\n IdeogramV3: ['rendering_speed', 'num_images', 'character_image'],\n FluxProKontextProNode: [],\n FluxProKontextMaxNode: [],\n Flux2ProImageNode: ['width', 'height', 'images'],\n Flux2MaxImageNode: ['width', 'height', 'images'],\n VeoVideoGenerationNode: ['duration_seconds'],\n Veo3VideoGenerationNode: ['model', 'generate_audio'],\n Veo3FirstLastFrameNode: ['model', 'generate_audio', 'duration'],\n LumaVideoNode: ['model', 'resolution', 'duration'],\n LumaImageToVideoNode: ['model', 'resolution', 'duration'],\n LumaImageNode: ['model', 'aspect_ratio'],\n LumaImageModifyNode: ['model', 'aspect_ratio'],\n PixverseTextToVideoNode: ['duration_seconds', 'quality', 'motion_mode'],\n PixverseTransitionVideoNode: [\n 'duration_seconds',\n 'motion_mode',\n 'quality'\n ],\n PixverseImageToVideoNode: ['duration_seconds', 'quality', 'motion_mode'],\n StabilityStableImageSD_3_5Node: ['model'],\n RecraftTextToImageNode: ['n'],\n RecraftImageToImageNode: ['n'],\n RecraftImageInpaintingNode: ['n'],\n RecraftTextToVectorNode: ['n'],\n RecraftVectorizeImageNode: ['n'],\n RecraftGenerateColorFromImageNode: ['n'],\n RecraftGenerateImageNode: ['n'],\n RecraftGenerateVectorImageNode: ['n'],\n MoonvalleyTxt2VideoNode: ['length'],\n MoonvalleyImg2VideoNode: ['length'],\n MoonvalleyVideo2VideoNode: ['length'],\n // Runway nodes\n RunwayImageToVideoNodeGen3a: ['duration'],\n RunwayImageToVideoNodeGen4: ['duration'],\n RunwayFirstLastFrameNode: ['duration'],\n // Tripo nodes\n TripoTextToModelNode: [\n 'model_version',\n 'quad',\n 'style',\n 'texture',\n 'pbr',\n 'texture_quality',\n 'geometry_quality'\n ],\n TripoImageToModelNode: [\n 'model_version',\n 'quad',\n 'style',\n 'texture',\n 'pbr',\n 'texture_quality',\n 'geometry_quality'\n ],\n TripoMultiviewToModelNode: [\n 'model_version',\n 'quad',\n 'texture',\n 'pbr',\n 'texture_quality',\n 'geometry_quality'\n ],\n TripoConversionNode: [\n 'quad',\n 'face_limit',\n 'texture_size',\n 'texture_format',\n 'force_symmetry',\n 'flatten_bottom',\n 'flatten_bottom_threshold',\n 'pivot_to_center_bottom',\n 'scale_factor',\n 'with_animation',\n 'pack_uv',\n 'bake',\n 'part_names',\n 'fbx_preset',\n 'export_vertex_colors',\n 'export_orientation',\n 'animate_in_place'\n ],\n TripoTextureNode: ['texture_quality'],\n // Meshy nodes\n MeshyImageToModelNode: ['should_texture'],\n MeshyMultiImageToModelNode: ['should_texture'],\n // Google/Gemini nodes\n GeminiNode: ['model'],\n GeminiImage2Node: ['resolution'],\n // OpenAI nodes\n OpenAIChatNode: ['model'],\n // ByteDance\n ByteDanceImageNode: ['model'],\n ByteDanceImageEditNode: ['model'],\n ByteDanceSeedreamNode: [\n 'model',\n 'sequential_image_generation',\n 'max_images'\n ],\n ByteDanceTextToVideoNode: [\n 'model',\n 'duration',\n 'resolution',\n 'generate_audio'\n ],\n ByteDanceImageToVideoNode: [\n 'model',\n 'duration',\n 'resolution',\n 'generate_audio'\n ],\n ByteDanceFirstLastFrameNode: [\n 'model',\n 'duration',\n 'resolution',\n 'generate_audio'\n ],\n ByteDanceImageReferenceNode: ['model', 'duration', 'resolution'],\n WanTextToVideoApi: ['duration', 'size'],\n WanImageToVideoApi: ['duration', 'resolution'],\n WanReferenceVideoApi: ['duration', 'size'],\n LtxvApiTextToVideo: ['model', 'duration', 'resolution'],\n LtxvApiImageToVideo: ['model', 'duration', 'resolution'],\n Vidu2TextToVideoNode: ['model', 'duration', 'resolution'],\n Vidu2ImageToVideoNode: ['model', 'duration', 'resolution'],\n Vidu2ReferenceVideoNode: ['audio', 'duration', 'resolution'],\n Vidu2StartEndToVideoNode: ['model', 'duration', 'resolution']\n }\n return widgetMap[nodeType] || []\n }\n\n return {\n getNodeDisplayPrice,\n getNodePricingConfig,\n getRelevantWidgetNames\n }\n}\n","import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { LGraphBadge } from '@/lib/litegraph/src/litegraph'\n\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { adjustColor } from '@/utils/colorUtil'\n\nconst componentIconSvg = new Image()\ncomponentIconSvg.src =\n \"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='oklch(83.01%25 0.163 83.16)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0zm-13.239 0a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0zm6.619 6.619a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0zm0-13.238a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z'/%3E%3C/svg%3E\"\n\nexport const usePriceBadge = () => {\n function updateSubgraphCredits(node: LGraphNode) {\n if (!node.isSubgraphNode()) return\n node.badges = node.badges.filter((b) => !isCreditsBadge(b))\n const newBadges = collectCreditsBadges(node.subgraph)\n if (newBadges.length > 1) {\n node.badges.push(getCreditsBadge('Partner Nodes x ' + newBadges.length))\n } else {\n node.badges.push(...newBadges)\n }\n }\n function collectCreditsBadges(\n graph: LGraph,\n visited: Set<string> = new Set()\n ): (LGraphBadge | (() => LGraphBadge))[] {\n if (visited.has(graph.id)) return []\n visited.add(graph.id)\n const badges = []\n for (const node of graph.nodes) {\n badges.push(\n ...(node.isSubgraphNode()\n ? collectCreditsBadges(node.subgraph, visited)\n : node.badges.filter((b) => isCreditsBadge(b)))\n )\n }\n return badges\n }\n\n function isCreditsBadge(badge: LGraphBadge | (() => LGraphBadge)): boolean {\n const badgeInstance = typeof badge === 'function' ? badge() : badge\n return badgeInstance.icon?.image === componentIconSvg\n }\n\n const colorPaletteStore = useColorPaletteStore()\n function getCreditsBadge(price: string): LGraphBadge {\n const isLightTheme = colorPaletteStore.completedActivePalette.light_theme\n\n return new LGraphBadge({\n text: price,\n iconOptions: {\n image: componentIconSvg,\n size: 8\n },\n fgColor:\n colorPaletteStore.completedActivePalette.colors.litegraph_base\n .BADGE_FG_COLOR,\n bgColor: isLightTheme\n ? adjustColor('#8D6932', { lightness: 0.5 })\n : '#8D6932'\n })\n }\n return {\n getCreditsBadge,\n updateSubgraphCredits\n }\n}\n","import { computedWithControl } from '@vueuse/core'\nimport { ref } from 'vue'\nimport type { ComputedRef } from 'vue'\n\nimport { useChainCallback } from '@/composables/functional/useChainCallback'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\n\ninterface UseComputedWithWidgetWatchOptions {\n /**\n * Names of widgets to observe for changes.\n * If not provided, all widgets will be observed.\n */\n widgetNames?: string[]\n\n /**\n * Whether to trigger a canvas redraw when widget values change.\n * @default false\n */\n triggerCanvasRedraw?: boolean\n}\n\n/**\n * A composable that creates a computed that has a node's widget values as a dependencies.\n * Essentially `computedWithControl` (https://vueuse.org/shared/computedWithControl/) where\n * the explicitly defined extra dependencies are LGraphNode widgets.\n *\n * @param node - The LGraphNode whose widget values are to be watched\n * @param options - Configuration options for the watcher\n * @returns A function to create computed that responds to widget changes\n *\n * @example\n * ```ts\n * const computedWithWidgetWatch = useComputedWithWidgetWatch(node, {\n * widgetNames: ['width', 'height'],\n * triggerCanvasRedraw: true\n * })\n *\n * const dynamicPrice = computedWithWidgetWatch(() => {\n * return calculatePrice(node)\n * })\n * ```\n */\nexport const useComputedWithWidgetWatch = (\n node: LGraphNode,\n options: UseComputedWithWidgetWatchOptions = {}\n) => {\n const { widgetNames, triggerCanvasRedraw = false } = options\n\n // Create a reactive trigger based on widget values\n const widgetValues = ref<Record<string, any>>({})\n\n // Initialize widget observers\n if (node.widgets) {\n const widgetsToObserve = widgetNames\n ? node.widgets.filter((widget) => widgetNames.includes(widget.name))\n : node.widgets\n\n // Initialize current values\n const currentValues: Record<string, any> = {}\n widgetsToObserve.forEach((widget) => {\n currentValues[widget.name] = widget.value\n })\n widgetValues.value = currentValues\n\n widgetsToObserve.forEach((widget) => {\n widget.callback = useChainCallback(widget.callback, () => {\n // Update the reactive widget values\n widgetValues.value = {\n ...widgetValues.value,\n [widget.name]: widget.value\n }\n\n // Optionally trigger a canvas redraw\n if (triggerCanvasRedraw) {\n node.graph?.setDirtyCanvas(true, true)\n }\n })\n })\n if (widgetNames && widgetNames.length > widgetsToObserve.length) {\n //Inputs have been included\n const indexesToObserve = widgetNames\n .map((name) =>\n widgetsToObserve.some((w) => w.name == name)\n ? -1\n : node.inputs.findIndex((i) => i.name == name)\n )\n .filter((i) => i >= 0)\n node.onConnectionsChange = useChainCallback(\n node.onConnectionsChange,\n (_type: unknown, index: number, isConnected: boolean) => {\n if (!indexesToObserve.includes(index)) return\n widgetValues.value = {\n ...widgetValues.value,\n [indexesToObserve[index]]: isConnected\n }\n if (triggerCanvasRedraw) {\n node.graph?.setDirtyCanvas(true, true)\n }\n }\n )\n }\n }\n\n // Returns a function that creates a computed that responds to widget changes.\n // The computed will be re-evaluated whenever any observed widget changes.\n return <T>(computeFn: () => T): ComputedRef<T> => {\n return computedWithControl(widgetValues, computeFn)\n }\n}\n","import _ from 'es-toolkit/compat'\nimport { computed, onMounted, watch } from 'vue'\n\nimport { useNodePricing } from '@/composables/node/useNodePricing'\nimport { usePriceBadge } from '@/composables/node/usePriceBadge'\nimport { useComputedWithWidgetWatch } from '@/composables/node/useWatchWidget'\nimport { BadgePosition, LGraphBadge } from '@/lib/litegraph/src/litegraph'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { app } from '@/scripts/app'\nimport { useExtensionStore } from '@/stores/extensionStore'\nimport type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'\nimport { useNodeDefStore } from '@/stores/nodeDefStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { NodeBadgeMode } from '@/types/nodeSource'\n\n/**\n * Add LGraphBadge to LGraphNode based on settings.\n *\n * Following badges are added:\n * - Node ID badge\n * - Node source badge\n * - Node life cycle badge\n * - API node credits badge\n */\nexport const useNodeBadge = () => {\n const settingStore = useSettingStore()\n const extensionStore = useExtensionStore()\n const colorPaletteStore = useColorPaletteStore()\n const priceBadge = usePriceBadge()\n\n const nodeSourceBadgeMode = computed(\n () =>\n settingStore.get('Comfy.NodeBadge.NodeSourceBadgeMode') as NodeBadgeMode\n )\n const nodeIdBadgeMode = computed(\n () => settingStore.get('Comfy.NodeBadge.NodeIdBadgeMode') as NodeBadgeMode\n )\n const nodeLifeCycleBadgeMode = computed(\n () =>\n settingStore.get(\n 'Comfy.NodeBadge.NodeLifeCycleBadgeMode'\n ) as NodeBadgeMode\n )\n\n const showApiPricingBadge = computed(() =>\n settingStore.get('Comfy.NodeBadge.ShowApiPricing')\n )\n\n watch(\n [\n nodeSourceBadgeMode,\n nodeIdBadgeMode,\n nodeLifeCycleBadgeMode,\n showApiPricingBadge\n ],\n () => {\n app.canvas?.setDirty(true, true)\n }\n )\n\n const nodeDefStore = useNodeDefStore()\n function badgeTextVisible(\n nodeDef: ComfyNodeDefImpl | null,\n badgeMode: NodeBadgeMode\n ): boolean {\n return !(\n badgeMode === NodeBadgeMode.None ||\n (nodeDef?.isCoreNode && badgeMode === NodeBadgeMode.HideBuiltIn)\n )\n }\n\n onMounted(() => {\n const nodePricing = useNodePricing()\n\n extensionStore.registerExtension({\n name: 'Comfy.NodeBadge',\n nodeCreated(node: LGraphNode) {\n node.badgePosition = BadgePosition.TopRight\n\n const badge = computed(() => {\n const nodeDef = nodeDefStore.fromLGraphNode(node)\n return new LGraphBadge({\n text: _.truncate(\n [\n badgeTextVisible(nodeDef, nodeIdBadgeMode.value)\n ? `#${node.id}`\n : '',\n badgeTextVisible(nodeDef, nodeLifeCycleBadgeMode.value)\n ? (nodeDef?.nodeLifeCycleBadgeText ?? '')\n : '',\n badgeTextVisible(nodeDef, nodeSourceBadgeMode.value)\n ? (nodeDef?.nodeSource?.badgeText ?? '')\n : ''\n ]\n .filter((s) => s.length > 0)\n .join(' '),\n {\n length: 31\n }\n ),\n fgColor:\n colorPaletteStore.completedActivePalette.colors.litegraph_base\n .BADGE_FG_COLOR,\n bgColor:\n colorPaletteStore.completedActivePalette.colors.litegraph_base\n .BADGE_BG_COLOR\n })\n })\n\n node.badges.push(() => badge.value)\n\n if (node.constructor.nodeData?.api_node && showApiPricingBadge.value) {\n // Get the pricing function to determine if this node has dynamic pricing\n const pricingConfig = nodePricing.getNodePricingConfig(node)\n const hasDynamicPricing =\n typeof pricingConfig?.displayPrice === 'function'\n\n let creditsBadge\n const createBadge = () => {\n const price = nodePricing.getNodeDisplayPrice(node)\n return priceBadge.getCreditsBadge(price)\n }\n\n if (hasDynamicPricing) {\n // For dynamic pricing nodes, use computed that watches widget changes\n const relevantWidgetNames = nodePricing.getRelevantWidgetNames(\n node.constructor.nodeData?.name\n )\n\n const computedWithWidgetWatch = useComputedWithWidgetWatch(node, {\n widgetNames: relevantWidgetNames,\n triggerCanvasRedraw: true\n })\n\n creditsBadge = computedWithWidgetWatch(createBadge)\n } else {\n // For static pricing nodes, use regular computed\n creditsBadge = computed(createBadge)\n }\n\n node.badges.push(() => creditsBadge.value)\n }\n },\n init() {\n app.canvas.canvas.addEventListener<'litegraph:set-graph'>(\n 'litegraph:set-graph',\n () => {\n for (const node of app.canvas.graph?.nodes ?? [])\n priceBadge.updateSubgraphCredits(node)\n }\n )\n app.canvas.canvas.addEventListener<'subgraph-converted'>(\n 'subgraph-converted',\n (e) => priceBadge.updateSubgraphCredits(e.detail.subgraphNode)\n )\n },\n afterConfigureGraph() {\n for (const node of app.canvas.graph?.nodes ?? [])\n priceBadge.updateSubgraphCredits(node)\n }\n })\n })\n}\n","import type { Ref } from 'vue'\n\nimport { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'\nimport { usePragmaticDroppable } from '@/composables/usePragmaticDragAndDrop'\nimport type { LGraphNode } from '@/lib/litegraph/src/litegraph'\nimport { LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'\nimport { app as comfyApp } from '@/scripts/app'\nimport { useLitegraphService } from '@/services/litegraphService'\nimport { ComfyModelDef } from '@/stores/modelStore'\nimport type { ModelNodeProvider } from '@/stores/modelToNodeStore'\nimport { useModelToNodeStore } from '@/stores/modelToNodeStore'\nimport { ComfyNodeDefImpl } from '@/stores/nodeDefStore'\nimport type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'\n\nexport const useCanvasDrop = (canvasRef: Ref<HTMLCanvasElement | null>) => {\n const modelToNodeStore = useModelToNodeStore()\n const litegraphService = useLitegraphService()\n const workflowService = useWorkflowService()\n\n usePragmaticDroppable(() => canvasRef.value, {\n getDropEffect: (args): Exclude<DataTransfer['dropEffect'], 'none'> =>\n args.source.data.type === 'tree-explorer-node' ? 'copy' : 'move',\n onDrop: async (event) => {\n const loc = event.location.current.input\n const dndData = event.source.data\n\n if (dndData.type === 'tree-explorer-node') {\n const node = dndData.data as RenderedTreeExplorerNode\n const conv = useSharedCanvasPositionConversion()\n const basePos = conv.clientPosToCanvasPos([loc.clientX, loc.clientY])\n\n if (node.data instanceof ComfyNodeDefImpl) {\n const nodeDef = node.data\n const pos = [...basePos]\n // Add an offset on y to make sure after adding the node, the cursor\n // is on the node (top left corner)\n pos[1] += LiteGraph.NODE_TITLE_HEIGHT\n litegraphService.addNodeOnGraph(nodeDef, { pos })\n } else if (node.data instanceof ComfyModelDef) {\n const model = node.data\n const pos = basePos\n const nodeAtPos = comfyApp.canvas.graph?.getNodeOnPos(pos[0], pos[1])\n let targetProvider: ModelNodeProvider | null = null\n let targetGraphNode: LGraphNode | null = null\n if (nodeAtPos) {\n const providers = modelToNodeStore.getAllNodeProviders(\n model.directory\n )\n for (const provider of providers) {\n if (provider.nodeDef.name === nodeAtPos.comfyClass) {\n targetGraphNode = nodeAtPos\n targetProvider = provider\n }\n }\n }\n if (!targetGraphNode) {\n const provider = modelToNodeStore.getNodeProvider(model.directory)\n if (provider) {\n targetGraphNode = litegraphService.addNodeOnGraph(\n provider.nodeDef,\n {\n pos\n }\n )\n targetProvider = provider\n }\n }\n if (targetGraphNode) {\n const widget = targetGraphNode.widgets?.find(\n (widget) => widget.name === targetProvider?.key\n )\n if (widget) {\n widget.value = model.file_name\n }\n }\n } else if (node.data instanceof ComfyWorkflow) {\n const workflow = node.data\n await workflowService.insertWorkflow(workflow, { position: basePos })\n }\n }\n }\n })\n}\n","import { st, te } from '@/i18n'\nimport { legacyMenuCompat } from '@/lib/litegraph/src/contextMenuCompat'\nimport type {\n IContextMenuOptions,\n IContextMenuValue,\n INodeInputSlot,\n IWidget\n} from '@/lib/litegraph/src/litegraph'\nimport { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport { app } from '@/scripts/app'\nimport { normalizeI18nKey } from '@/utils/formatUtil'\n\n/**\n * Add translation for litegraph context menu.\n */\nexport const useContextMenuTranslation = () => {\n // Install compatibility layer BEFORE any extensions load\n legacyMenuCompat.install(LGraphCanvas.prototype, 'getCanvasMenuOptions')\n\n const { getCanvasMenuOptions } = LGraphCanvas.prototype\n const getCanvasCenterMenuOptions = function (\n this: LGraphCanvas,\n ...args: Parameters<typeof getCanvasMenuOptions>\n ) {\n const res: (IContextMenuValue | null)[] = getCanvasMenuOptions.apply(\n this,\n args\n )\n\n // Add items from new extension API\n const newApiItems = app.collectCanvasMenuItems(this)\n for (const item of newApiItems) {\n res.push(item)\n }\n\n // Add legacy monkey-patched items\n const legacyItems = legacyMenuCompat.extractLegacyItems(\n 'getCanvasMenuOptions',\n this,\n ...args\n )\n for (const item of legacyItems) {\n res.push(item)\n }\n\n // Translate all items\n for (const item of res) {\n if (item?.content) {\n item.content = st(`contextMenu.${item.content}`, item.content)\n }\n }\n return res\n }\n\n LGraphCanvas.prototype.getCanvasMenuOptions = getCanvasCenterMenuOptions\n\n legacyMenuCompat.registerWrapper(\n 'getCanvasMenuOptions',\n getCanvasCenterMenuOptions,\n getCanvasMenuOptions,\n LGraphCanvas.prototype\n )\n\n // Install compatibility layer for getNodeMenuOptions\n legacyMenuCompat.install(LGraphCanvas.prototype, 'getNodeMenuOptions')\n\n // Wrap getNodeMenuOptions to add new API items\n const nodeMenuFn = LGraphCanvas.prototype.getNodeMenuOptions\n const getNodeMenuOptionsWithExtensions = function (\n this: LGraphCanvas,\n ...args: Parameters<typeof nodeMenuFn>\n ) {\n const res = nodeMenuFn.apply(this, args) as (IContextMenuValue | null)[]\n\n // Add items from new extension API\n const node = args[0]\n const newApiItems = app.collectNodeMenuItems(node)\n for (const item of newApiItems) {\n res.push(item)\n }\n\n // Add legacy monkey-patched items\n const legacyItems = legacyMenuCompat.extractLegacyItems(\n 'getNodeMenuOptions',\n this,\n ...args\n )\n for (const item of legacyItems) {\n res.push(item)\n }\n\n return res\n }\n\n LGraphCanvas.prototype.getNodeMenuOptions = getNodeMenuOptionsWithExtensions\n\n legacyMenuCompat.registerWrapper(\n 'getNodeMenuOptions',\n getNodeMenuOptionsWithExtensions,\n nodeMenuFn,\n LGraphCanvas.prototype\n )\n\n function translateMenus(\n values: readonly (IContextMenuValue | string | null)[] | undefined,\n options: IContextMenuOptions\n ) {\n if (!values) return\n const reInput = /Convert (.*) to input/\n const reWidget = /Convert (.*) to widget/\n const cvt = st('contextMenu.Convert ', 'Convert ')\n const tinp = st('contextMenu. to input', ' to input')\n const twgt = st('contextMenu. to widget', ' to widget')\n for (const value of values) {\n if (typeof value === 'string') continue\n\n translateMenus(value?.submenu?.options, options)\n if (!value?.content) {\n continue\n }\n if (te(`contextMenu.${value.content}`)) {\n value.content = st(`contextMenu.${value.content}`, value.content)\n }\n\n // for capture translation text of input and widget\n const extraInfo: any = options.extra || options.parentMenu?.options?.extra\n // widgets and inputs\n const matchInput = value.content?.match(reInput)\n if (matchInput) {\n let match = matchInput[1]\n extraInfo?.inputs?.find((i: INodeInputSlot) => {\n if (i.name != match) return false\n match = i.label ? i.label : i.name\n })\n extraInfo?.widgets?.find((i: IWidget) => {\n if (i.name != match) return false\n match = i.label ? i.label : i.name\n })\n value.content = cvt + match + tinp\n continue\n }\n const matchWidget = value.content?.match(reWidget)\n if (matchWidget) {\n let match = matchWidget[1]\n extraInfo?.inputs?.find((i: INodeInputSlot) => {\n if (i.name != match) return false\n match = i.label ? i.label : i.name\n })\n extraInfo?.widgets?.find((i: IWidget) => {\n if (i.name != match) return false\n match = i.label ? i.label : i.name\n })\n value.content = cvt + match + twgt\n continue\n }\n }\n }\n\n const OriginalContextMenu = LiteGraph.ContextMenu\n function ContextMenu(\n values: (IContextMenuValue | string)[],\n options: IContextMenuOptions\n ) {\n if (options.title) {\n options.title = st(\n `nodeDefs.${normalizeI18nKey(options.title)}.display_name`,\n options.title\n )\n }\n translateMenus(values, options)\n const ctx = new OriginalContextMenu(values, options)\n return ctx\n }\n\n LiteGraph.ContextMenu = ContextMenu as unknown as typeof LiteGraph.ContextMenu\n LiteGraph.ContextMenu.prototype = OriginalContextMenu.prototype\n}\n","import { useEventListener } from '@vueuse/core'\n\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { shouldIgnoreCopyPaste } from '@/workbench/eventHelpers'\n\nconst clipboardHTMLWrapper = [\n '<meta charset=\"utf-8\"><div><span data-metadata=\"',\n '\"></span></div><span style=\"white-space:pre-wrap;\">Text</span>'\n]\n\n/**\n * Adds a handler on copy that serializes selected nodes to JSON\n */\nexport const useCopy = () => {\n const canvasStore = useCanvasStore()\n\n useEventListener(document, 'copy', (e) => {\n if (shouldIgnoreCopyPaste(e.target)) {\n // Default system copy\n return\n }\n // copy nodes and clear clipboard\n const canvas = canvasStore.canvas\n if (canvas?.selectedItems) {\n const serializedData = canvas.copyToClipboard()\n // Use TextEncoder to handle Unicode characters properly\n const base64Data = btoa(\n String.fromCharCode(\n ...Array.from(new TextEncoder().encode(serializedData))\n )\n )\n // clearData doesn't remove images from clipboard\n e.clipboardData?.setData(\n 'text/html',\n clipboardHTMLWrapper.join(base64Data)\n )\n e.preventDefault()\n e.stopImmediatePropagation()\n return false\n }\n })\n}\n","import {\n ContextMenu,\n DragAndScale,\n LGraph,\n LGraphBadge,\n LGraphCanvas,\n LGraphGroup,\n LGraphNode,\n LLink,\n LiteGraph\n} from '@/lib/litegraph/src/litegraph'\n\n/**\n * Assign all properties of LiteGraph to window to make it backward compatible.\n */\nexport const useGlobalLitegraph = () => {\n // @ts-expect-error fixme ts strict error\n window['LiteGraph'] = LiteGraph\n // @ts-expect-error fixme ts strict error\n window['LGraph'] = LGraph\n // @ts-expect-error fixme ts strict error\n window['LLink'] = LLink\n // @ts-expect-error fixme ts strict error\n window['LGraphNode'] = LGraphNode\n // @ts-expect-error fixme ts strict error\n window['LGraphGroup'] = LGraphGroup\n // @ts-expect-error fixme ts strict error\n window['DragAndScale'] = DragAndScale\n // @ts-expect-error fixme ts strict error\n window['LGraphCanvas'] = LGraphCanvas\n // @ts-expect-error fixme ts strict error\n window['ContextMenu'] = ContextMenu\n // @ts-expect-error fixme ts strict error\n window['LGraphBadge'] = LGraphBadge\n}\n","import { watchEffect } from 'vue'\n\nimport {\n CanvasPointer,\n LGraphNode,\n LiteGraph\n} from '@/lib/litegraph/src/litegraph'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\n/**\n * Watch for changes in the setting store and update the LiteGraph settings accordingly.\n */\nexport const useLitegraphSettings = () => {\n const settingStore = useSettingStore()\n const canvasStore = useCanvasStore()\n\n watchEffect(() => {\n const canvasInfoEnabled = settingStore.get('Comfy.Graph.CanvasInfo')\n if (canvasStore.canvas) {\n canvasStore.canvas.show_info = canvasInfoEnabled\n canvasStore.canvas.draw(false, true)\n }\n })\n\n watchEffect(() => {\n const zoomSpeed = settingStore.get('Comfy.Graph.ZoomSpeed')\n if (canvasStore.canvas) {\n canvasStore.canvas.zoom_speed = zoomSpeed\n }\n })\n\n watchEffect(() => {\n LiteGraph.snaps_for_comfy = settingStore.get(\n 'Comfy.Node.AutoSnapLinkToSlot'\n )\n })\n\n watchEffect(() => {\n LiteGraph.snap_highlights_node = settingStore.get(\n 'Comfy.Node.SnapHighlightsNode'\n )\n })\n\n watchEffect(() => {\n LGraphNode.keepAllLinksOnBypass = settingStore.get(\n 'Comfy.Node.BypassAllLinksOnDelete'\n )\n })\n\n watchEffect(() => {\n LiteGraph.middle_click_slot_add_default_node = settingStore.get(\n 'Comfy.Node.MiddleClickRerouteNode'\n )\n })\n\n watchEffect(() => {\n const linkRenderMode = settingStore.get('Comfy.LinkRenderMode')\n if (canvasStore.canvas) {\n canvasStore.canvas.links_render_mode = linkRenderMode\n canvasStore.canvas.setDirty(/* fg */ false, /* bg */ true)\n }\n })\n\n watchEffect(() => {\n const minFontSizeForLOD = settingStore.get(\n 'LiteGraph.Canvas.MinFontSizeForLOD'\n )\n if (canvasStore.canvas) {\n canvasStore.canvas.min_font_size_for_lod = minFontSizeForLOD\n canvasStore.canvas.setDirty(/* fg */ true, /* bg */ true)\n }\n })\n\n watchEffect(() => {\n const linkMarkerShape = settingStore.get('Comfy.Graph.LinkMarkers')\n const { canvas } = canvasStore\n if (canvas) {\n canvas.linkMarkerShape = linkMarkerShape\n canvas.setDirty(false, true)\n }\n })\n\n watchEffect(() => {\n const maximumFps = settingStore.get('LiteGraph.Canvas.MaximumFps')\n const { canvas } = canvasStore\n if (canvas) canvas.maximumFps = maximumFps\n })\n\n watchEffect(() => {\n const dragZoomEnabled = settingStore.get('Comfy.Graph.CtrlShiftZoom')\n const { canvas } = canvasStore\n if (canvas) canvas.dragZoomEnabled = dragZoomEnabled\n })\n\n watchEffect(() => {\n const liveSelection = settingStore.get('Comfy.Graph.LiveSelection')\n const { canvas } = canvasStore\n if (canvas) canvas.liveSelection = liveSelection\n })\n\n watchEffect(() => {\n CanvasPointer.doubleClickTime = settingStore.get(\n 'Comfy.Pointer.DoubleClickTime'\n )\n })\n\n watchEffect(() => {\n CanvasPointer.bufferTime = settingStore.get('Comfy.Pointer.ClickBufferTime')\n })\n\n watchEffect(() => {\n CanvasPointer.maxClickDrift = settingStore.get('Comfy.Pointer.ClickDrift')\n })\n\n watchEffect(() => {\n LiteGraph.CANVAS_GRID_SIZE = settingStore.get('Comfy.SnapToGrid.GridSize')\n })\n\n watchEffect(() => {\n LiteGraph.alwaysSnapToGrid = settingStore.get('pysssss.SnapToGrid')\n })\n\n watchEffect(() => {\n LiteGraph.context_menu_scaling = settingStore.get(\n 'LiteGraph.ContextMenu.Scaling'\n )\n })\n\n watchEffect(() => {\n LiteGraph.Reroute.maxSplineOffset = settingStore.get(\n 'LiteGraph.Reroute.SplineOffset'\n )\n })\n\n watchEffect(() => {\n const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode') as\n | 'standard'\n | 'legacy'\n | 'custom'\n\n LiteGraph.canvasNavigationMode = navigationMode\n LiteGraph.macTrackpadGestures = navigationMode === 'standard'\n })\n\n watchEffect(() => {\n const leftMouseBehavior = settingStore.get(\n 'Comfy.Canvas.LeftMouseClickBehavior'\n ) as 'panning' | 'select'\n LiteGraph.leftMouseClickBehavior = leftMouseBehavior\n })\n\n watchEffect(() => {\n const mouseWheelScroll = settingStore.get(\n 'Comfy.Canvas.MouseWheelScroll'\n ) as 'panning' | 'zoom'\n LiteGraph.mouseWheelScroll = mouseWheelScroll\n })\n\n watchEffect(() => {\n LiteGraph.saveViewportWithGraph = settingStore.get(\n 'Comfy.EnableWorkflowViewRestore'\n )\n })\n}\n","import { LinkMarkerShape, LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport { isCloud } from '@/platform/distribution/types'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport type { SettingParams } from '@/platform/settings/types'\nimport type { ColorPalettes } from '@/schemas/colorPaletteSchema'\nimport type { Keybinding } from '@/schemas/keyBindingSchema'\nimport { NodeBadgeMode } from '@/types/nodeSource'\nimport { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'\nimport { breakpointsTailwind } from '@vueuse/core'\n\n/**\n * Core settings are essential configuration parameters required for ComfyUI's basic functionality.\n * These settings must be present in the settings store and cannot be omitted.\n *\n * IMPORTANT: To prevent ID conflicts, settings should be marked as deprecated rather than removed\n * when they are no longer needed.\n */\nexport const CORE_SETTINGS: SettingParams[] = [\n {\n id: 'Comfy.Memory.AllowManualUnload',\n name: 'Allow manual unload of models and execution cache via user command',\n type: 'hidden',\n defaultValue: isCloud ? false : true,\n versionAdded: '1.18.0'\n },\n {\n id: 'Comfy.Validation.Workflows',\n name: 'Validate workflows',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.NodeSearchBoxImpl',\n category: ['Comfy', 'Node Search Box', 'Implementation'],\n experimental: true,\n name: 'Node search box implementation',\n type: 'combo',\n options: ['default', 'litegraph (legacy)'],\n defaultValue: 'default'\n },\n {\n id: 'Comfy.LinkRelease.Action',\n category: ['LiteGraph', 'LinkRelease', 'Action'],\n name: 'Action on link release (No modifier)',\n type: 'combo',\n options: Object.values(LinkReleaseTriggerAction),\n defaultValue: LinkReleaseTriggerAction.CONTEXT_MENU,\n defaultsByInstallVersion: {\n '1.24.1': LinkReleaseTriggerAction.SEARCH_BOX\n }\n },\n {\n id: 'Comfy.LinkRelease.ActionShift',\n category: ['LiteGraph', 'LinkRelease', 'ActionShift'],\n name: 'Action on link release (Shift)',\n type: 'combo',\n options: Object.values(LinkReleaseTriggerAction),\n defaultValue: LinkReleaseTriggerAction.SEARCH_BOX,\n defaultsByInstallVersion: {\n '1.24.1': LinkReleaseTriggerAction.CONTEXT_MENU\n }\n },\n {\n id: 'Comfy.NodeSearchBoxImpl.NodePreview',\n category: ['Comfy', 'Node Search Box', 'NodePreview'],\n name: 'Node preview',\n tooltip: 'Only applies to the default implementation',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.NodeSearchBoxImpl.ShowCategory',\n category: ['Comfy', 'Node Search Box', 'ShowCategory'],\n name: 'Show node category in search results',\n tooltip: 'Only applies to the default implementation',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.NodeSearchBoxImpl.ShowIdName',\n category: ['Comfy', 'Node Search Box', 'ShowIdName'],\n name: 'Show node id name in search results',\n tooltip: 'Only applies to the default implementation',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.NodeSearchBoxImpl.ShowNodeFrequency',\n category: ['Comfy', 'Node Search Box', 'ShowNodeFrequency'],\n name: 'Show node frequency in search results',\n tooltip: 'Only applies to the default implementation',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.Sidebar.Location',\n category: ['Appearance', 'Sidebar', 'Location'],\n name: 'Sidebar location',\n type: 'combo',\n options: ['left', 'right'],\n defaultValue: 'left'\n },\n {\n id: 'Comfy.Sidebar.Size',\n category: ['Appearance', 'Sidebar', 'Size'],\n name: 'Sidebar size',\n type: 'combo',\n options: ['normal', 'small'],\n // Default to small if the window is less than 1536px(2xl) wide.\n defaultValue: () => (window.innerWidth < 1536 ? 'small' : 'normal')\n },\n {\n id: 'Comfy.Sidebar.UnifiedWidth',\n category: ['Appearance', 'Sidebar', 'UnifiedWidth'],\n name: 'Unified sidebar width',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.18.1'\n },\n {\n id: 'Comfy.Sidebar.Style',\n category: ['Appearance', 'Sidebar', 'Style'],\n name: 'Sidebar style',\n type: 'combo',\n options: ['floating', 'connected'],\n defaultValue: 'connected'\n },\n {\n id: 'Comfy.TextareaWidget.FontSize',\n category: ['Appearance', 'Node Widget', 'TextareaWidget', 'FontSize'],\n name: 'Textarea widget font size',\n type: 'slider',\n defaultValue: 10,\n attrs: {\n min: 8,\n max: 24\n }\n },\n {\n id: 'Comfy.TextareaWidget.Spellcheck',\n category: ['Comfy', 'Node Widget', 'TextareaWidget', 'Spellcheck'],\n name: 'Textarea widget spellcheck',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.Workflow.SortNodeIdOnSave',\n name: 'Sort node IDs when saving workflow',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.Canvas.NavigationMode',\n category: ['LiteGraph', 'Canvas Navigation', 'NavigationMode'],\n name: 'Navigation Mode',\n defaultValue: 'legacy',\n type: 'combo',\n sortOrder: 100,\n options: [\n { value: 'standard', text: 'Standard (New)' },\n { value: 'legacy', text: 'Drag Navigation' },\n { value: 'custom', text: 'Custom' }\n ],\n versionAdded: '1.25.0',\n defaultsByInstallVersion: {\n '1.25.0': 'legacy'\n },\n onChange: async (newValue: string, oldValue?: string) => {\n if (!oldValue) return\n const settingStore = useSettingStore()\n\n if (newValue === 'standard') {\n // Update related settings to match standard mode - select + panning\n await settingStore.set('Comfy.Canvas.LeftMouseClickBehavior', 'select')\n await settingStore.set('Comfy.Canvas.MouseWheelScroll', 'panning')\n } else if (newValue === 'legacy') {\n // Update related settings to match legacy mode - panning + zoom\n await settingStore.set('Comfy.Canvas.LeftMouseClickBehavior', 'panning')\n await settingStore.set('Comfy.Canvas.MouseWheelScroll', 'zoom')\n }\n }\n },\n {\n id: 'Comfy.Canvas.LeftMouseClickBehavior',\n category: ['LiteGraph', 'Canvas Navigation', 'LeftMouseClickBehavior'],\n name: 'Left Mouse Click Behavior',\n defaultValue: 'panning',\n type: 'radio',\n sortOrder: 50,\n options: [\n { value: 'panning', text: 'Panning' },\n { value: 'select', text: 'Select' }\n ],\n versionAdded: '1.27.4',\n onChange: async (newValue: string) => {\n const settingStore = useSettingStore()\n\n const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode')\n\n if (navigationMode !== 'custom') {\n if (\n (newValue === 'select' && navigationMode === 'standard') ||\n (newValue === 'panning' && navigationMode === 'legacy')\n ) {\n return\n }\n\n // only set to custom if it doesn't match the preset modes\n await settingStore.set('Comfy.Canvas.NavigationMode', 'custom')\n }\n }\n },\n {\n id: 'Comfy.Canvas.MouseWheelScroll',\n category: ['LiteGraph', 'Canvas Navigation', 'MouseWheelScroll'],\n name: 'Mouse Wheel Scroll',\n defaultValue: 'zoom',\n type: 'radio',\n options: [\n { value: 'panning', text: 'Panning' },\n { value: 'zoom', text: 'Zoom in/out' }\n ],\n versionAdded: '1.27.4',\n onChange: async (newValue: string) => {\n const settingStore = useSettingStore()\n\n const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode')\n\n if (navigationMode !== 'custom') {\n if (\n (newValue === 'panning' && navigationMode === 'standard') ||\n (newValue === 'zoom' && navigationMode === 'legacy')\n ) {\n return\n }\n\n // only set to custom if it doesn't match the preset modes\n await settingStore.set('Comfy.Canvas.NavigationMode', 'custom')\n }\n }\n },\n {\n id: 'Comfy.Graph.CanvasInfo',\n category: ['LiteGraph', 'Canvas', 'CanvasInfo'],\n name: 'Show canvas info on bottom left corner (fps, etc.)',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Node.ShowDeprecated',\n name: 'Show deprecated nodes in search',\n tooltip:\n 'Deprecated nodes are hidden by default in the UI, but remain functional in existing workflows that use them.',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.Node.ShowExperimental',\n name: 'Show experimental nodes in search',\n tooltip:\n 'Experimental nodes are marked as such in the UI and may be subject to significant changes or removal in future versions. Use with caution in production workflows',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Node.Opacity',\n category: ['Appearance', 'Node', 'Opacity'],\n name: 'Node opacity',\n type: 'slider',\n defaultValue: 1,\n attrs: {\n min: 0.01,\n max: 1,\n step: 0.01\n }\n },\n {\n id: 'Comfy.Workflow.ShowMissingNodesWarning',\n name: 'Show missing nodes warning',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Workflow.ShowMissingModelsWarning',\n name: 'Show missing models warning',\n type: isCloud ? 'hidden' : 'boolean',\n defaultValue: isCloud ? false : true,\n experimental: true\n },\n {\n id: 'Comfy.Workflow.WarnBlueprintOverwrite',\n name: 'Require confirmation to overwrite an existing subgraph blueprint',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Graph.ZoomSpeed',\n category: ['LiteGraph', 'Canvas', 'ZoomSpeed'],\n name: 'Canvas zoom speed',\n type: 'slider',\n defaultValue: 1.1,\n attrs: {\n min: 1.01,\n max: 2.5,\n step: 0.01\n }\n },\n // Bookmarks are stored in the settings store.\n // Bookmarks are in format of category/display_name. e.g. \"conditioning/CLIPTextEncode\"\n {\n id: 'Comfy.NodeLibrary.Bookmarks',\n name: 'Node library bookmarks with display name (deprecated)',\n type: 'hidden',\n defaultValue: [],\n deprecated: true\n },\n {\n id: 'Comfy.NodeLibrary.Bookmarks.V2',\n name: 'Node library bookmarks v2 with unique name',\n type: 'hidden',\n defaultValue: []\n },\n // Stores mapping from bookmark folder name to its customization.\n {\n id: 'Comfy.NodeLibrary.BookmarksCustomization',\n name: 'Node library bookmarks customization',\n type: 'hidden',\n defaultValue: {}\n },\n {\n id: 'Comfy.GroupSelectedNodes.Padding',\n category: ['LiteGraph', 'Group', 'Padding'],\n name: 'Group selected nodes padding',\n type: 'slider',\n defaultValue: 10,\n attrs: {\n min: 0,\n max: 100\n }\n },\n {\n id: 'Comfy.Node.DoubleClickTitleToEdit',\n category: ['LiteGraph', 'Node', 'DoubleClickTitleToEdit'],\n name: 'Double click node title to edit',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Node.AllowImageSizeDraw',\n category: ['LiteGraph', 'Node Widget', 'AllowImageSizeDraw'],\n name: 'Show width × height below the image preview',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Group.DoubleClickTitleToEdit',\n category: ['LiteGraph', 'Group', 'DoubleClickTitleToEdit'],\n name: 'Double click group title to edit',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Window.UnloadConfirmation',\n name: 'Show confirmation when closing window',\n type: 'boolean',\n defaultValue: true,\n versionModified: '1.7.12'\n },\n {\n id: 'Comfy.TreeExplorer.ItemPadding',\n category: ['Appearance', 'Tree Explorer', 'ItemPadding'],\n name: 'Tree explorer item padding',\n type: 'slider',\n defaultValue: 2,\n attrs: {\n min: 0,\n max: 8,\n step: 1\n }\n },\n {\n id: 'Comfy.ModelLibrary.AutoLoadAll',\n name: 'Automatically load all model folders',\n tooltip:\n 'If true, all folders will load as soon as you open the model library (this may cause delays while it loads). If false, root level model folders will only load once you click on them.',\n type: isCloud ? 'hidden' : 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.ModelLibrary.NameFormat',\n name: 'What name to display in the model library tree view',\n tooltip:\n 'Select \"filename\" to render a simplified view of the raw filename (without directory or \".safetensors\" extension) in the model list. Select \"title\" to display the configurable model metadata title.',\n type: 'combo',\n options: ['filename', 'title'],\n defaultValue: 'title'\n },\n {\n id: 'Comfy.Locale',\n name: 'Language',\n type: 'combo',\n options: [\n { value: 'en', text: 'English' },\n { value: 'zh', text: '中文' },\n { value: 'zh-TW', text: '繁體中文' },\n { value: 'ru', text: 'Русский' },\n { value: 'ja', text: '日本語' },\n { value: 'ko', text: '한국어' },\n { value: 'fr', text: 'Français' },\n { value: 'es', text: 'Español' },\n { value: 'ar', text: 'عربي' },\n { value: 'tr', text: 'Türkçe' },\n { value: 'pt-BR', text: 'Português (BR)' },\n { value: 'fa', text: 'فارسی' }\n ],\n defaultValue: () => navigator.language.split('-')[0] || 'en'\n },\n {\n id: 'Comfy.NodeBadge.NodeSourceBadgeMode',\n category: ['LiteGraph', 'Node', 'NodeSourceBadgeMode'],\n name: 'Node source badge mode',\n type: 'combo',\n options: Object.values(NodeBadgeMode),\n defaultValue: NodeBadgeMode.HideBuiltIn\n },\n {\n id: 'Comfy.NodeBadge.NodeIdBadgeMode',\n category: ['LiteGraph', 'Node', 'NodeIdBadgeMode'],\n name: 'Node ID badge mode',\n type: 'combo',\n options: [NodeBadgeMode.None, NodeBadgeMode.ShowAll],\n defaultValue: NodeBadgeMode.None\n },\n {\n id: 'Comfy.NodeBadge.NodeLifeCycleBadgeMode',\n category: ['LiteGraph', 'Node', 'NodeLifeCycleBadgeMode'],\n name: 'Node life cycle badge mode',\n type: 'combo',\n options: [NodeBadgeMode.None, NodeBadgeMode.ShowAll],\n defaultValue: NodeBadgeMode.ShowAll\n },\n {\n id: 'Comfy.NodeBadge.ShowApiPricing',\n category: ['Comfy', 'API Nodes'],\n name: 'Show API node pricing badge',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.20.3'\n },\n {\n id: 'Comfy.Notification.ShowVersionUpdates',\n category: ['Comfy', 'Notification Preferences'],\n name: 'Show version updates',\n tooltip: 'Show updates for new models, and major new features.',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.ConfirmClear',\n category: ['Comfy', 'Workflow', 'ConfirmClear'],\n name: 'Require confirmation when clearing workflow',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.PromptFilename',\n category: ['Comfy', 'Workflow', 'PromptFilename'],\n name: 'Prompt for filename when saving workflow',\n type: 'boolean',\n defaultValue: true\n },\n /**\n * file format for preview\n *\n * format;quality\n *\n * ex)\n * webp;50 -> webp, quality 50\n * jpeg;80 -> rgb, jpeg, quality 80\n *\n * @type {string}\n */\n {\n id: 'Comfy.PreviewFormat',\n category: ['LiteGraph', 'Node Widget', 'PreviewFormat'],\n name: 'Preview image format',\n tooltip:\n 'When displaying a preview in the image widget, convert it to a lightweight image, e.g. webp, jpeg, webp;50, etc.',\n type: 'text',\n defaultValue: ''\n },\n {\n id: 'Comfy.DisableSliders',\n category: ['LiteGraph', 'Node Widget', 'DisableSliders'],\n name: 'Disable node widget sliders',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.DisableFloatRounding',\n category: ['LiteGraph', 'Node Widget', 'DisableFloatRounding'],\n name: 'Disable default float widget rounding.',\n tooltip:\n '(requires page reload) Cannot disable round when round is set by the node in the backend.',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'Comfy.FloatRoundingPrecision',\n category: ['LiteGraph', 'Node Widget', 'FloatRoundingPrecision'],\n name: 'Float widget rounding decimal places [0 = auto].',\n tooltip: '(requires page reload)',\n type: 'slider',\n attrs: {\n min: 0,\n max: 6,\n step: 1\n },\n defaultValue: 0\n },\n {\n id: 'LiteGraph.Node.TooltipDelay',\n name: 'Tooltip Delay',\n type: 'number',\n attrs: {\n min: 100,\n max: 3000,\n step: 50\n },\n defaultValue: 500,\n versionAdded: '1.9.0'\n },\n {\n id: 'Comfy.EnableTooltips',\n category: ['LiteGraph', 'Node', 'EnableTooltips'],\n name: 'Enable Tooltips',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.DevMode',\n name: 'Enable dev mode options (API save, etc.)',\n type: 'boolean',\n defaultValue: false,\n onChange: (value) => {\n const element = document.getElementById('comfy-dev-save-api-button')\n if (element) {\n element.style.display = value ? 'flex' : 'none'\n }\n }\n },\n {\n id: 'Comfy.UI.TabBarLayout',\n category: ['Appearance', 'General'],\n name: 'Tab Bar Layout',\n type: 'combo',\n options: ['Default', 'Integrated'],\n tooltip:\n 'Controls the layout of the tab bar. \"Integrated\" moves Help and User controls into the tab bar area.',\n defaultValue: 'Default'\n },\n {\n id: 'Comfy.UseNewMenu',\n category: ['Comfy', 'Menu', 'UseNewMenu'],\n defaultValue: 'Top',\n name: 'Use new menu',\n type: 'combo',\n options: ['Disabled', 'Top'],\n tooltip: 'Enable the redesigned top menu bar.',\n migrateDeprecatedValue: (value: string) => {\n // Floating is now supported by dragging the docked actionbar off.\n if (value === 'Floating') {\n return 'Top'\n } else if (value === 'Bottom') {\n return 'Top'\n }\n return value\n }\n },\n {\n id: 'Comfy.Workflow.WorkflowTabsPosition',\n name: 'Opened workflows position',\n type: 'combo',\n options: ['Sidebar', 'Topbar'],\n defaultValue: 'Topbar',\n migrateDeprecatedValue: (value: string) => {\n if (value === 'Topbar (2nd-row)') {\n return 'Topbar'\n }\n return value\n }\n },\n {\n id: 'Comfy.Graph.CanvasMenu',\n category: ['LiteGraph', 'Canvas', 'CanvasMenu'],\n name: 'Show graph canvas menu',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.QueueButton.BatchCountLimit',\n name: 'Batch count limit',\n tooltip:\n 'The maximum number of tasks added to the queue at one button click',\n type: 'number',\n defaultValue: isCloud ? 4 : 100,\n versionAdded: '1.3.5'\n },\n {\n id: 'Comfy.Keybinding.UnsetBindings',\n name: 'Keybindings unset by the user',\n type: 'hidden',\n defaultValue: [] as Keybinding[],\n versionAdded: '1.3.7',\n versionModified: '1.7.3',\n migrateDeprecatedValue: (\n value: (Keybinding & { targetSelector?: string })[]\n ) => {\n return value.map((keybinding) => {\n if (keybinding.targetSelector === '#graph-canvas') {\n keybinding.targetElementId = 'graph-canvas-container'\n }\n return keybinding\n })\n }\n },\n {\n id: 'Comfy.Keybinding.NewBindings',\n name: 'Keybindings set by the user',\n type: 'hidden',\n defaultValue: [] as Keybinding[],\n versionAdded: '1.3.7'\n },\n {\n id: 'Comfy.Extension.Disabled',\n name: 'Disabled extension names',\n type: 'hidden',\n defaultValue: [] as string[],\n versionAdded: '1.3.11'\n },\n {\n id: 'Comfy.LinkRenderMode',\n category: ['LiteGraph', 'Graph', 'LinkRenderMode'],\n name: 'Link Render Mode',\n defaultValue: 2,\n type: 'combo',\n options: [\n { value: LiteGraph.STRAIGHT_LINK, text: 'Straight' },\n { value: LiteGraph.LINEAR_LINK, text: 'Linear' },\n { value: LiteGraph.SPLINE_LINK, text: 'Spline' },\n { value: LiteGraph.HIDDEN_LINK, text: 'Hidden' }\n ]\n },\n {\n id: 'Comfy.Node.AutoSnapLinkToSlot',\n category: ['LiteGraph', 'Node', 'AutoSnapLinkToSlot'],\n name: 'Auto snap link to node slot',\n tooltip:\n 'When dragging a link over a node, the link automatically snap to a viable input slot on the node',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.3.29'\n },\n {\n id: 'Comfy.Node.SnapHighlightsNode',\n category: ['LiteGraph', 'Node', 'SnapHighlightsNode'],\n name: 'Snap highlights node',\n tooltip:\n 'When dragging a link over a node with viable input slot, highlight the node',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.3.29'\n },\n {\n id: 'Comfy.Node.BypassAllLinksOnDelete',\n category: ['LiteGraph', 'Node', 'BypassAllLinksOnDelete'],\n name: 'Keep all links when deleting nodes',\n tooltip:\n 'When deleting a node, attempt to reconnect all of its input and output links (bypassing the deleted node)',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.3.40'\n },\n {\n id: 'Comfy.Node.MiddleClickRerouteNode',\n category: ['LiteGraph', 'Node', 'MiddleClickRerouteNode'],\n name: 'Middle-click creates a new Reroute node',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.3.42'\n },\n {\n id: 'Comfy.Graph.LinkMarkers',\n category: ['LiteGraph', 'Link', 'LinkMarkers'],\n name: 'Link midpoint markers',\n defaultValue: LinkMarkerShape.Circle,\n type: 'combo',\n options: [\n { value: LinkMarkerShape.None, text: 'None' },\n { value: LinkMarkerShape.Circle, text: 'Circle' },\n { value: LinkMarkerShape.Arrow, text: 'Arrow' }\n ],\n versionAdded: '1.3.42'\n },\n {\n id: 'Comfy.DOMClippingEnabled',\n category: ['LiteGraph', 'Node', 'DOMClippingEnabled'],\n name: 'Enable DOM element clipping (enabling may reduce performance)',\n type: 'boolean',\n defaultValue: true\n },\n {\n id: 'Comfy.Graph.CtrlShiftZoom',\n category: ['LiteGraph', 'Canvas', 'CtrlShiftZoom'],\n name: 'Enable fast-zoom shortcut (Ctrl + Shift + Drag)',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.4.0'\n },\n {\n id: 'Comfy.Graph.LiveSelection',\n category: ['LiteGraph', 'Canvas', 'LiveSelection'],\n name: 'Live selection',\n tooltip:\n 'When enabled, nodes are selected/deselected in real-time as you drag the selection rectangle, similar to other design tools.',\n type: 'boolean',\n defaultValue: false,\n versionAdded: '1.36.1'\n },\n {\n id: 'Comfy.Pointer.ClickDrift',\n category: ['LiteGraph', 'Pointer', 'ClickDrift'],\n name: 'Pointer click drift (maximum distance)',\n tooltip:\n 'If the pointer moves more than this distance while holding a button down, it is considered dragging (rather than clicking).\\n\\nHelps prevent objects from being unintentionally nudged if the pointer is moved whilst clicking.',\n experimental: true,\n type: 'slider',\n attrs: {\n min: 0,\n max: 20,\n step: 1\n },\n defaultValue: 6,\n versionAdded: '1.4.3'\n },\n {\n id: 'Comfy.Pointer.ClickBufferTime',\n category: ['LiteGraph', 'Pointer', 'ClickBufferTime'],\n name: 'Pointer click drift delay',\n tooltip:\n 'After pressing a pointer button down, this is the maximum time (in milliseconds) that pointer movement can be ignored for.\\n\\nHelps prevent objects from being unintentionally nudged if the pointer is moved whilst clicking.',\n experimental: true,\n type: 'slider',\n attrs: {\n min: 0,\n max: 1000,\n step: 25\n },\n defaultValue: 150,\n versionAdded: '1.4.3'\n },\n {\n id: 'Comfy.Pointer.DoubleClickTime',\n category: ['LiteGraph', 'Pointer', 'DoubleClickTime'],\n name: 'Double click interval (maximum)',\n tooltip:\n 'The maximum time in milliseconds between the two clicks of a double-click. Increasing this value may assist if double-clicks are sometimes not registered.',\n type: 'slider',\n attrs: {\n min: 100,\n max: 1000,\n step: 50\n },\n defaultValue: 300,\n versionAdded: '1.4.3'\n },\n {\n id: 'Comfy.SnapToGrid.GridSize',\n category: ['LiteGraph', 'Canvas', 'GridSize'],\n name: 'Snap to grid size',\n type: 'slider',\n attrs: {\n min: 1,\n max: 500\n },\n tooltip:\n 'When dragging and resizing nodes while holding shift they will be aligned to the grid, this controls the size of that grid.',\n defaultValue: LiteGraph.CANVAS_GRID_SIZE\n },\n // Keep the 'pysssss.SnapToGrid' setting id so we don't need to migrate setting values.\n // Using a new setting id can cause existing users to lose their existing settings.\n {\n id: 'pysssss.SnapToGrid',\n category: ['LiteGraph', 'Canvas', 'AlwaysSnapToGrid'],\n name: 'Always snap to grid',\n type: 'boolean',\n defaultValue: false,\n versionAdded: '1.3.13'\n },\n {\n id: 'Comfy.Server.ServerConfigValues',\n name: 'Server config values for frontend display',\n tooltip: 'Server config values used for frontend display only',\n type: 'hidden',\n // Mapping from server config id to value.\n defaultValue: {} as Record<string, unknown>,\n versionAdded: '1.4.8'\n },\n {\n id: 'Comfy.Server.LaunchArgs',\n name: 'Server launch arguments',\n tooltip:\n 'These are the actual arguments that are passed to the server when it is launched.',\n type: 'hidden',\n defaultValue: {} as Record<string, string>,\n versionAdded: '1.4.8'\n },\n {\n id: 'Comfy.Queue.MaxHistoryItems',\n name: 'Queue history size',\n tooltip: 'The maximum number of tasks that show in the queue history.',\n type: 'slider',\n attrs: {\n min: 2,\n max: 256,\n step: 2\n },\n defaultValue: 64,\n versionAdded: '1.4.12'\n },\n {\n id: 'Comfy.Queue.History.Expanded',\n name: 'Queue history expanded',\n type: 'hidden',\n defaultValue: false,\n versionAdded: '1.37.0'\n },\n {\n id: 'Comfy.Execution.PreviewMethod',\n category: ['Comfy', 'Execution', 'PreviewMethod'],\n name: 'Live preview method',\n tooltip:\n 'Live preview method during image generation. \"default\" uses the server CLI setting.',\n type: 'combo',\n options: ['default', 'none', 'auto', 'latent2rgb', 'taesd'],\n defaultValue: 'default',\n versionAdded: '1.36.0'\n },\n {\n id: 'LiteGraph.Canvas.MaximumFps',\n name: 'Maximum FPS',\n tooltip:\n 'The maximum frames per second that the canvas is allowed to render. Caps GPU usage at the cost of smoothness. If 0, the screen refresh rate is used. Default: 0',\n type: 'slider',\n attrs: {\n min: 0,\n max: 120\n },\n defaultValue: 0,\n versionAdded: '1.5.1'\n },\n {\n id: 'Comfy.EnableWorkflowViewRestore',\n category: ['Comfy', 'Workflow', 'EnableWorkflowViewRestore'],\n name: 'Save and restore canvas position and zoom level in workflows',\n type: 'boolean',\n defaultValue: true,\n versionModified: '1.5.4'\n },\n {\n id: 'Comfy.Workflow.ConfirmDelete',\n name: 'Show confirmation when deleting workflows',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.5.6'\n },\n {\n id: 'Comfy.ColorPalette',\n name: 'The active color palette id',\n type: 'hidden',\n defaultValue: 'dark',\n versionModified: '1.6.7',\n migrateDeprecatedValue(value: string) {\n // Legacy custom palettes were prefixed with 'custom_'\n return value.startsWith('custom_') ? value.replace('custom_', '') : value\n }\n },\n {\n id: 'Comfy.CustomColorPalettes',\n name: 'Custom color palettes',\n type: 'hidden',\n defaultValue: {} as ColorPalettes,\n versionModified: '1.6.7'\n },\n {\n id: 'Comfy.WidgetControlMode',\n category: ['Comfy', 'Node Widget', 'WidgetControlMode'],\n name: 'Widget control mode',\n tooltip:\n 'Controls when widget values are updated (randomize/increment/decrement), either before the prompt is queued or after.',\n type: 'combo',\n defaultValue: 'after',\n options: ['before', 'after'],\n versionModified: '1.6.10'\n },\n {\n id: 'Comfy.TutorialCompleted',\n name: 'Tutorial completed',\n type: 'hidden',\n defaultValue: false,\n versionAdded: '1.8.7'\n },\n {\n id: 'Comfy.InstalledVersion',\n name: 'The frontend version that was running when the user first installed ComfyUI',\n type: 'hidden',\n defaultValue: null,\n versionAdded: '1.24.0'\n },\n {\n id: 'LiteGraph.ContextMenu.Scaling',\n name: 'Scale node combo widget menus (lists) when zoomed in',\n defaultValue: false,\n type: 'boolean',\n versionAdded: '1.8.8'\n },\n\n {\n id: 'LiteGraph.Canvas.LowQualityRenderingZoomThreshold',\n type: 'hidden',\n deprecated: true,\n name: 'Low quality rendering zoom threshold (deprecated)',\n tooltip:\n 'Zoom level threshold for performance mode. Lower values (0.1) = quality at all zoom levels. Higher values (1.0) = performance mode even when zoomed in. Performance mode simplifies rendering by hiding text labels, shadows, and details.',\n attrs: {\n min: 0.1,\n max: 1,\n step: 0.01\n },\n defaultValue: 0.6,\n versionAdded: '1.9.1',\n versionModified: '1.26.7'\n },\n {\n id: 'LiteGraph.Canvas.MinFontSizeForLOD',\n name: 'Zoom Node Level of Detail - font size threshold',\n tooltip:\n 'Controls when the nodes switch to low quality LOD rendering. Uses font size in pixels to determine when to switch. Set to 0 to disable. Values 1-24 set the minimum font size threshold for LOD - higher values (24px) = switch nodes to simplified rendering sooner when zooming out, lower values (1px) = maintain full node quality longer.',\n type: 'slider',\n attrs: {\n min: 0,\n max: 24,\n step: 1\n },\n defaultValue: 8,\n versionAdded: '1.26.7',\n hideInVueNodes: true\n },\n {\n id: 'Comfy.Canvas.SelectionToolbox',\n category: ['LiteGraph', 'Canvas', 'SelectionToolbox'],\n name: 'Show selection toolbox',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.10.5'\n },\n {\n id: 'LiteGraph.Reroute.SplineOffset',\n name: 'Reroute spline offset',\n tooltip: 'The bezier control point offset from the reroute centre point',\n type: 'slider',\n defaultValue: 20,\n attrs: {\n min: 0,\n max: 400\n },\n versionAdded: '1.15.7'\n },\n {\n id: 'Comfy.Toast.DisableReconnectingToast',\n name: 'Disable toasts when reconnecting or reconnected',\n type: 'hidden',\n defaultValue: false,\n versionAdded: '1.15.12'\n },\n {\n id: 'Comfy.Minimap.Visible',\n name: 'Display minimap on canvas',\n type: 'hidden',\n defaultValue: window.innerWidth >= breakpointsTailwind.lg,\n versionAdded: '1.25.0'\n },\n {\n id: 'Comfy.Minimap.NodeColors',\n name: 'Display node with its original color on minimap',\n type: 'hidden',\n defaultValue: false,\n versionAdded: '1.26.0'\n },\n {\n id: 'Comfy.Minimap.ShowLinks',\n name: 'Display links on minimap',\n type: 'hidden',\n defaultValue: true,\n versionAdded: '1.26.0'\n },\n {\n id: 'Comfy.Minimap.ShowGroups',\n name: 'Display node groups on minimap',\n type: 'hidden',\n defaultValue: true,\n versionAdded: '1.26.0'\n },\n {\n id: 'Comfy.Minimap.RenderBypassState',\n name: 'Render bypass state on minimap',\n type: 'hidden',\n defaultValue: true,\n versionAdded: '1.26.0'\n },\n {\n id: 'Comfy.Minimap.RenderErrorState',\n name: 'Render error state on minimap',\n type: 'hidden',\n defaultValue: true,\n versionAdded: '1.26.0'\n },\n {\n id: 'Comfy.Workflow.AutoSaveDelay',\n name: 'Auto Save Delay (ms)',\n defaultValue: 1000,\n type: 'number',\n tooltip: 'Only applies if Auto Save is set to \"after delay\".',\n versionAdded: '1.16.0'\n },\n {\n id: 'Comfy.Workflow.AutoSave',\n name: 'Auto Save',\n type: 'combo',\n options: ['off', 'after delay'], // Room for other options like on focus change, tab change, window change\n defaultValue: 'off', // Popular request by users (https://github.com/Comfy-Org/ComfyUI_frontend/issues/1584#issuecomment-2536610154)\n versionAdded: '1.16.0'\n },\n {\n id: 'Comfy.Workflow.Persist',\n name: 'Persist workflow state and restore on page (re)load',\n type: 'boolean',\n defaultValue: true,\n versionAdded: '1.16.1'\n },\n {\n id: 'LiteGraph.Node.DefaultPadding',\n name: 'Always shrink new nodes',\n tooltip:\n 'Resize nodes to the smallest possible size when created. When disabled, a newly added node will be widened slightly to show widget values.',\n type: 'boolean',\n defaultValue: false,\n versionAdded: '1.18.0'\n },\n {\n id: 'Comfy.Canvas.BackgroundImage',\n category: ['Appearance', 'Canvas', 'Background'],\n name: 'Canvas background image',\n type: 'backgroundImage',\n tooltip:\n 'Image URL for the canvas background. You can right-click an image in the outputs panel and select \"Set as Background\" to use it, or upload your own image using the upload button.',\n defaultValue: '',\n versionAdded: '1.20.4',\n versionModified: '1.20.5'\n },\n // Release data stored in settings\n {\n id: 'Comfy.Release.Version',\n name: 'Last seen release version',\n type: 'hidden',\n defaultValue: ''\n },\n {\n id: 'Comfy.Release.Status',\n name: 'Release status',\n type: 'hidden',\n defaultValue: 'skipped'\n },\n {\n id: 'Comfy.Release.Timestamp',\n name: 'Release seen timestamp',\n type: 'hidden',\n defaultValue: 0\n },\n\n /**\n * Template Library Filter Settings\n */\n {\n id: 'Comfy.Templates.SelectedModels',\n name: 'Template library - Selected model filters',\n type: 'hidden',\n defaultValue: []\n },\n {\n id: 'Comfy.Templates.SelectedUseCases',\n name: 'Template library - Selected use case filters',\n type: 'hidden',\n defaultValue: []\n },\n {\n id: 'Comfy.Templates.SelectedRunsOn',\n name: 'Template library - Selected runs on filters',\n type: 'hidden',\n defaultValue: []\n },\n {\n id: 'Comfy.Templates.SortBy',\n name: 'Template library - Sort preference',\n type: 'hidden',\n defaultValue: 'default'\n },\n\n /**\n * Nodes 2.0 Settings\n */\n {\n id: 'Comfy.VueNodes.Enabled',\n category: ['Comfy', 'Nodes 2.0', 'VueNodesEnabled'],\n name: 'Modern Node Design (Nodes 2.0)',\n type: 'boolean',\n tooltip:\n 'Modern: DOM-based rendering with enhanced interactivity, native browser features, and updated visual design. Classic: Traditional canvas rendering.',\n defaultValue: false,\n sortOrder: 100,\n experimental: true,\n versionAdded: '1.27.1'\n },\n {\n id: 'Comfy.VueNodes.AutoScaleLayout',\n category: ['Comfy', 'Nodes 2.0', 'AutoScaleLayout'],\n name: 'Auto-scale layout (Nodes 2.0)',\n tooltip:\n 'Automatically scale node positions when switching to Nodes 2.0 rendering to prevent overlap',\n type: 'boolean',\n sortOrder: 50,\n experimental: true,\n defaultValue: true,\n versionAdded: '1.30.3'\n },\n {\n id: 'Comfy.Assets.UseAssetAPI',\n name: 'Use Asset API for model library',\n type: 'hidden',\n tooltip: 'Use new Asset API for model browsing',\n defaultValue: isCloud ? true : false,\n experimental: true\n },\n {\n id: 'Comfy.VersionCompatibility.DisableWarnings',\n name: 'Disable version compatibility warnings',\n type: 'hidden',\n defaultValue: false,\n versionAdded: '1.34.1'\n },\n {\n id: 'Comfy.Queue.QPOV2',\n name: 'Queue Panel V2',\n type: 'hidden',\n tooltip: 'Enable the new Assets Panel design with list/grid view toggle',\n defaultValue: false,\n experimental: true\n }\n]\n","import { computed, onUnmounted, watch } from 'vue'\n\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { api } from '@/scripts/api'\n\nexport function useWorkflowAutoSave() {\n const workflowStore = useWorkflowStore()\n const settingStore = useSettingStore()\n const workflowService = useWorkflowService()\n\n // Use computed refs to cache autosave settings\n const autoSaveSetting = computed(() =>\n settingStore.get('Comfy.Workflow.AutoSave')\n )\n const autoSaveDelay = computed(() =>\n settingStore.get('Comfy.Workflow.AutoSaveDelay')\n )\n\n let autoSaveTimeout: NodeJS.Timeout | null = null\n let isSaving = false\n let needsAutoSave = false\n\n const scheduleAutoSave = () => {\n // Clear any existing timeout\n if (autoSaveTimeout) {\n clearTimeout(autoSaveTimeout)\n autoSaveTimeout = null\n }\n\n // If autosave is enabled\n if (autoSaveSetting.value === 'after delay') {\n // If a save is in progress, mark that we need an autosave after saving\n if (isSaving) {\n needsAutoSave = true\n return\n }\n const delay = autoSaveDelay.value\n autoSaveTimeout = setTimeout(async () => {\n const activeWorkflow = workflowStore.activeWorkflow\n if (activeWorkflow?.isModified && activeWorkflow.isPersisted) {\n try {\n isSaving = true\n await workflowService.saveWorkflow(activeWorkflow)\n } catch (err) {\n console.error('Auto save failed:', err)\n } finally {\n isSaving = false\n if (needsAutoSave) {\n needsAutoSave = false\n scheduleAutoSave()\n }\n }\n }\n }, delay)\n }\n }\n\n // Watch for autosave setting changes\n watch(\n autoSaveSetting,\n (newSetting) => {\n // Clear any existing timeout when settings change\n if (autoSaveTimeout) {\n clearTimeout(autoSaveTimeout)\n autoSaveTimeout = null\n }\n\n // If there's an active modified workflow and autosave is enabled, schedule a save\n if (\n newSetting === 'after delay' &&\n workflowStore.activeWorkflow?.isModified\n ) {\n scheduleAutoSave()\n }\n },\n { immediate: true }\n )\n\n // Listen for graph changes and schedule autosave when they occur\n const onGraphChanged = () => {\n scheduleAutoSave()\n }\n\n api.addEventListener('graphChanged', onGraphChanged)\n\n onUnmounted(() => {\n if (autoSaveTimeout) {\n clearTimeout(autoSaveTimeout)\n autoSaveTimeout = null\n }\n api.removeEventListener('graphChanged', onGraphChanged)\n })\n}\n","import { useToast } from 'primevue/usetoast'\nimport { useI18n } from 'vue-i18n'\nimport { useRoute, useRouter } from 'vue-router'\n\nimport { clearPreservedQuery } from '@/platform/navigation/preservedQueryManager'\nimport { PRESERVED_QUERY_NAMESPACES } from '@/platform/navigation/preservedQueryNamespaces'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\nimport { useTemplateWorkflows } from './useTemplateWorkflows'\n\n/**\n * Composable for loading templates from URL query parameters\n *\n * Supports URLs like:\n * - /?template=flux_simple (loads with default source)\n * - /?template=flux_simple&source=custom (loads from custom source)\n * - /?template=flux_simple&mode=linear (loads template in linear mode)\n *\n * Input validation:\n * - Template, source, and mode parameters must match: ^[a-zA-Z0-9_-]+$\n * - Invalid formats are rejected with console warnings\n */\nexport function useTemplateUrlLoader() {\n const route = useRoute()\n const router = useRouter()\n const { t } = useI18n()\n const toast = useToast()\n const templateWorkflows = useTemplateWorkflows()\n const canvasStore = useCanvasStore()\n const TEMPLATE_NAMESPACE = PRESERVED_QUERY_NAMESPACES.TEMPLATE\n const SUPPORTED_MODES = ['linear'] as const\n type SupportedMode = (typeof SUPPORTED_MODES)[number]\n\n /**\n * Validates parameter format to prevent path traversal and injection attacks\n * Allows: letters, numbers, underscores, hyphens, and dots (for version numbers)\n * Blocks: path separators (/, \\), special chars that could enable injection\n */\n const isValidParameter = (param: string): boolean => {\n return /^[a-zA-Z0-9_.-]+$/.test(param)\n }\n\n /**\n * Type guard to check if a value is a supported mode\n */\n const isSupportedMode = (mode: string): mode is SupportedMode => {\n return SUPPORTED_MODES.includes(mode as SupportedMode)\n }\n\n /**\n * Removes template, source, and mode parameters from URL\n */\n const cleanupUrlParams = () => {\n const newQuery = { ...route.query }\n delete newQuery.template\n delete newQuery.source\n delete newQuery.mode\n void router.replace({ query: newQuery })\n }\n\n /**\n * Loads template from URL query parameters if present\n * Handles errors internally and shows appropriate user feedback\n */\n const loadTemplateFromUrl = async () => {\n const templateParam = route.query.template\n\n if (!templateParam || typeof templateParam !== 'string') {\n return\n }\n\n if (!isValidParameter(templateParam)) {\n console.warn(\n `[useTemplateUrlLoader] Invalid template parameter format: ${templateParam}`\n )\n return\n }\n\n const sourceParam = (route.query.source as string | undefined) || 'default'\n\n if (!isValidParameter(sourceParam)) {\n console.warn(\n `[useTemplateUrlLoader] Invalid source parameter format: ${sourceParam}`\n )\n return\n }\n\n const modeParam = route.query.mode as string | undefined\n\n if (\n modeParam &&\n (typeof modeParam !== 'string' || !isValidParameter(modeParam))\n ) {\n console.warn(\n `[useTemplateUrlLoader] Invalid mode parameter format: ${modeParam}`\n )\n return\n }\n\n if (modeParam && !isSupportedMode(modeParam)) {\n console.warn(\n `[useTemplateUrlLoader] Unsupported mode parameter: ${modeParam}. Supported modes: ${SUPPORTED_MODES.join(', ')}`\n )\n }\n\n try {\n await templateWorkflows.loadTemplates()\n\n const success = await templateWorkflows.loadWorkflowTemplate(\n templateParam,\n sourceParam\n )\n\n if (!success) {\n toast.add({\n severity: 'error',\n summary: t('g.error'),\n detail: t('templateWorkflows.error.templateNotFound', {\n templateName: templateParam\n }),\n life: 3000\n })\n } else if (modeParam === 'linear') {\n // Set linear mode after successful template load\n canvasStore.linearMode = true\n }\n } catch (error) {\n console.error(\n '[useTemplateUrlLoader] Failed to load template from URL:',\n error\n )\n toast.add({\n severity: 'error',\n summary: t('g.error'),\n detail: t('g.errorLoadingTemplate'),\n life: 3000\n })\n } finally {\n cleanupUrlParams()\n clearPreservedQuery(TEMPLATE_NAMESPACE)\n }\n }\n\n return {\n loadTemplateFromUrl\n }\n}\n","import { tryOnScopeDispose } from '@vueuse/core'\nimport { computed, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\n\nimport {\n hydratePreservedQuery,\n mergePreservedQueryIntoQuery\n} from '@/platform/navigation/preservedQueryManager'\nimport { PRESERVED_QUERY_NAMESPACES } from '@/platform/navigation/preservedQueryNamespaces'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useTemplateUrlLoader } from '@/platform/workflow/templates/composables/useTemplateUrlLoader'\nimport { api } from '@/scripts/api'\nimport { app as comfyApp } from '@/scripts/app'\nimport { getStorageValue, setStorageValue } from '@/scripts/utils'\nimport { useCommandStore } from '@/stores/commandStore'\n\nexport function useWorkflowPersistence() {\n const workflowStore = useWorkflowStore()\n const settingStore = useSettingStore()\n const route = useRoute()\n const router = useRouter()\n const templateUrlLoader = useTemplateUrlLoader()\n const TEMPLATE_NAMESPACE = PRESERVED_QUERY_NAMESPACES.TEMPLATE\n\n const ensureTemplateQueryFromIntent = async () => {\n hydratePreservedQuery(TEMPLATE_NAMESPACE)\n const mergedQuery = mergePreservedQueryIntoQuery(\n TEMPLATE_NAMESPACE,\n route.query\n )\n\n if (mergedQuery) {\n await router.replace({ query: mergedQuery })\n }\n\n return mergedQuery ?? route.query\n }\n\n const workflowPersistenceEnabled = computed(() =>\n settingStore.get('Comfy.Workflow.Persist')\n )\n\n const persistCurrentWorkflow = () => {\n if (!workflowPersistenceEnabled.value) return\n const workflow = JSON.stringify(comfyApp.rootGraph.serialize())\n\n try {\n localStorage.setItem('workflow', workflow)\n if (api.clientId) {\n sessionStorage.setItem(`workflow:${api.clientId}`, workflow)\n }\n } catch (error) {\n // Only log our own keys and aggregate stats\n const ourKeys = Object.keys(sessionStorage).filter(\n (key) => key.startsWith('workflow:') || key === 'workflow'\n )\n console.error('QuotaExceededError details:', {\n workflowSizeKB: Math.round(workflow.length / 1024),\n totalStorageItems: Object.keys(sessionStorage).length,\n ourWorkflowKeys: ourKeys.length,\n ourWorkflowSizes: ourKeys.map((key) => ({\n key,\n sizeKB: Math.round(sessionStorage[key].length / 1024)\n })),\n error: error instanceof Error ? error.message : String(error)\n })\n throw error\n }\n }\n\n const loadWorkflowFromStorage = async (\n json: string | null,\n workflowName: string | null\n ) => {\n if (!json) return false\n const workflow = JSON.parse(json)\n await comfyApp.loadGraphData(workflow, true, true, workflowName)\n return true\n }\n\n const loadPreviousWorkflowFromStorage = async () => {\n const workflowName = getStorageValue('Comfy.PreviousWorkflow')\n const clientId = api.initialClientId ?? api.clientId\n\n // Try loading from session storage first\n if (clientId) {\n const sessionWorkflow = sessionStorage.getItem(`workflow:${clientId}`)\n if (await loadWorkflowFromStorage(sessionWorkflow, workflowName)) {\n return true\n }\n }\n\n // Fall back to local storage\n const localWorkflow = localStorage.getItem('workflow')\n return await loadWorkflowFromStorage(localWorkflow, workflowName)\n }\n\n const loadDefaultWorkflow = async () => {\n if (!settingStore.get('Comfy.TutorialCompleted')) {\n await settingStore.set('Comfy.TutorialCompleted', true)\n await useWorkflowService().loadBlankWorkflow()\n await useCommandStore().execute('Comfy.BrowseTemplates')\n } else {\n await comfyApp.loadGraphData()\n }\n }\n\n const initializeWorkflow = async () => {\n if (!workflowPersistenceEnabled.value) return\n\n try {\n const restored = await loadPreviousWorkflowFromStorage()\n if (!restored) {\n await loadDefaultWorkflow()\n }\n } catch (err) {\n console.error('Error loading previous workflow', err)\n await loadDefaultWorkflow()\n }\n }\n\n const loadTemplateFromUrlIfPresent = async () => {\n const query = await ensureTemplateQueryFromIntent()\n const hasTemplateUrl = query.template && typeof query.template === 'string'\n\n if (hasTemplateUrl) {\n await templateUrlLoader.loadTemplateFromUrl()\n }\n }\n\n // Setup watchers\n watch(\n () => workflowStore.activeWorkflow?.key,\n (activeWorkflowKey) => {\n if (!activeWorkflowKey) return\n setStorageValue('Comfy.PreviousWorkflow', activeWorkflowKey)\n // When the activeWorkflow changes, the graph has already been loaded.\n // Saving the current state of the graph to the localStorage.\n persistCurrentWorkflow()\n }\n )\n api.addEventListener('graphChanged', persistCurrentWorkflow)\n\n // Clean up event listener when component unmounts\n tryOnScopeDispose(() => {\n api.removeEventListener('graphChanged', persistCurrentWorkflow)\n })\n\n // Restore workflow tabs states\n const openWorkflows = computed(() => workflowStore.openWorkflows)\n const activeWorkflow = computed(() => workflowStore.activeWorkflow)\n const restoreState = computed<{ paths: string[]; activeIndex: number }>(\n () => {\n if (!openWorkflows.value || !activeWorkflow.value) {\n return { paths: [], activeIndex: -1 }\n }\n\n const paths = openWorkflows.value\n .filter((workflow) => workflow?.isPersisted)\n .map((workflow) => workflow.path)\n const activeIndex = openWorkflows.value.findIndex(\n (workflow) => workflow.path === activeWorkflow.value?.path\n )\n\n return { paths, activeIndex }\n }\n )\n\n // Get storage values before setting watchers\n const storedWorkflows = JSON.parse(\n getStorageValue('Comfy.OpenWorkflowsPaths') || '[]'\n )\n const storedActiveIndex = JSON.parse(\n getStorageValue('Comfy.ActiveWorkflowIndex') || '-1'\n )\n\n watch(restoreState, ({ paths, activeIndex }) => {\n if (workflowPersistenceEnabled.value) {\n setStorageValue('Comfy.OpenWorkflowsPaths', JSON.stringify(paths))\n setStorageValue('Comfy.ActiveWorkflowIndex', JSON.stringify(activeIndex))\n }\n })\n\n const restoreWorkflowTabsState = () => {\n if (!workflowPersistenceEnabled.value) return\n const isRestorable = storedWorkflows?.length > 0 && storedActiveIndex >= 0\n if (isRestorable) {\n workflowStore.openWorkflowsInBackground({\n left: storedWorkflows.slice(0, storedActiveIndex),\n right: storedWorkflows.slice(storedActiveIndex)\n })\n }\n }\n\n return {\n initializeWorkflow,\n loadTemplateFromUrlIfPresent,\n restoreWorkflowTabsState\n }\n}\n","import { useDebounceFn, useEventListener } from '@vueuse/core'\nimport { ref } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\ninterface TransformSettlingOptions {\n /**\n * Delay in ms before transform is considered \"settled\" after last interaction\n * @default 200\n */\n settleDelay?: number\n /**\n * Whether to use passive event listeners (better performance but can't preventDefault)\n * @default true\n */\n passive?: boolean\n}\n\n/**\n * Tracks when canvas zoom transforms are actively changing vs settled.\n *\n * This composable helps optimize rendering quality during zoom transformations.\n * When the user is actively zooming, we can reduce rendering quality\n * for better performance. Once the transform \"settles\" (stops changing), we can\n * trigger high-quality re-rasterization.\n *\n * The settling concept prevents constant quality switching during interactions\n * by waiting for a period of inactivity before considering the transform complete.\n *\n * Uses VueUse's useEventListener for automatic cleanup and useDebounceFn for\n * efficient settle detection.\n *\n * @example\n * ```ts\n * const { isTransforming } = useTransformSettling(canvasRef, {\n * settleDelay: 200\n * })\n *\n * // Use in CSS classes or rendering logic\n * const cssClass = computed(() => ({\n * 'low-quality': isTransforming.value,\n * 'high-quality': !isTransforming.value\n * }))\n * ```\n */\nexport function useTransformSettling(\n target: MaybeRefOrGetter<HTMLElement | null | undefined>,\n options: TransformSettlingOptions = {}\n) {\n const { settleDelay = 256, passive = true } = options\n\n const isTransforming = ref(false)\n\n /**\n * Mark transform as active\n */\n const markTransformActive = () => {\n isTransforming.value = true\n }\n\n /**\n * Mark transform as settled (debounced)\n */\n const markTransformSettled = useDebounceFn(() => {\n isTransforming.value = false\n }, settleDelay)\n\n /**\n * Handle zoom transform event - mark active then queue settle\n */\n const handleWheel = () => {\n markTransformActive()\n void markTransformSettled()\n }\n\n // Register wheel event listener with auto-cleanup\n useEventListener(target, 'wheel', handleWheel, {\n capture: true,\n passive\n })\n\n return {\n isTransforming\n }\n}\n","/**\n * Composable for managing transform state synchronized with LiteGraph canvas\n *\n * This composable is a critical part of the hybrid rendering architecture that\n * allows Vue components to render in perfect alignment with LiteGraph's canvas.\n *\n * ## Core Concept\n *\n * LiteGraph uses a canvas for rendering connections, grid, and handling interactions.\n * Vue components need to render nodes on top of this canvas. The challenge is\n * synchronizing the coordinate systems:\n *\n * - LiteGraph: Uses canvas coordinates with its own transform matrix\n * - Vue/DOM: Uses screen coordinates with CSS transforms\n *\n * ## Solution: Transform Container Pattern\n *\n * Instead of transforming individual nodes (O(n) complexity), we:\n * 1. Mirror LiteGraph's transform matrix to a single CSS container\n * 2. Place all Vue nodes as children with simple absolute positioning\n * 3. Achieve O(1) transform updates regardless of node count\n *\n * ## Coordinate Systems\n *\n * - **Canvas coordinates**: LiteGraph's internal coordinate system\n * - **Screen coordinates**: Browser's viewport coordinate system\n * - **Transform sync**: camera.x/y/z mirrors canvas.ds.offset/scale\n *\n * ## Performance Benefits\n *\n * - GPU acceleration via CSS transforms\n * - No layout thrashing (only transform changes)\n * - Efficient viewport culling calculations\n * - Scales to 1000+ nodes while maintaining 60 FPS\n *\n * @example\n * ```typescript\n * const { camera, transformStyle, canvasToScreen } = useTransformState()\n *\n * // In template\n * <div :style=\"transformStyle\">\n * <NodeComponent\n * v-for=\"node in nodes\"\n * :style=\"{ left: node.x + 'px', top: node.y + 'px' }\"\n * />\n * </div>\n *\n * // Convert coordinates\n * const screenPos = canvasToScreen({ x: nodeX, y: nodeY })\n * ```\n */\nimport { computed, reactive, readonly } from 'vue'\n\nimport type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'\nimport { createSharedComposable } from '@vueuse/core'\n\ninterface Point {\n x: number\n y: number\n}\n\ninterface Camera {\n x: number\n y: number\n z: number // scale/zoom\n}\n\nfunction useTransformStateIndividual() {\n // Reactive state mirroring LiteGraph's canvas transform\n const camera = reactive<Camera>({\n x: 0,\n y: 0,\n z: 1\n })\n\n // Computed transform string for CSS\n const transformStyle = computed(() => ({\n // Match LiteGraph DragAndScale.toCanvasContext():\n // ctx.scale(scale); ctx.translate(offset)\n // CSS applies right-to-left, so \"scale() translate()\" -> translate first, then scale\n // Effective mapping: screen = (canvas + offset) * scale\n transform: `scale(${camera.z}) translate(${camera.x}px, ${camera.y}px)`,\n transformOrigin: '0 0'\n }))\n\n /**\n * Synchronizes Vue's reactive camera state with LiteGraph's canvas transform\n *\n * Called every frame via RAF to ensure Vue components stay aligned with canvas.\n * This is the heart of the hybrid rendering system - it bridges the gap between\n * LiteGraph's canvas transforms and Vue's reactive system.\n *\n * @param canvas - LiteGraph canvas instance with DragAndScale (ds) transform state\n */\n function syncWithCanvas(canvas: LGraphCanvas) {\n if (!canvas || !canvas.ds) return\n\n // Mirror LiteGraph's transform state to Vue's reactive state\n // ds.offset = pan offset, ds.scale = zoom level\n camera.x = canvas.ds.offset[0]\n camera.y = canvas.ds.offset[1]\n camera.z = canvas.ds.scale || 1\n }\n\n /**\n * Converts canvas coordinates to screen coordinates\n *\n * Applies the same transform that LiteGraph uses for rendering.\n * Essential for positioning Vue components to align with canvas elements.\n *\n * Formula: screen = (canvas + offset) * scale\n *\n * @param point - Point in canvas coordinate system\n * @returns Point in screen coordinate system\n */\n function canvasToScreen(point: Point): Point {\n return {\n x: (point.x + camera.x) * camera.z,\n y: (point.y + camera.y) * camera.z\n }\n }\n\n /**\n * Converts screen coordinates to canvas coordinates\n *\n * Inverse of canvasToScreen. Useful for hit testing and converting\n * mouse events back to canvas space.\n *\n * Formula: canvas = screen / scale - offset\n *\n * @param point - Point in screen coordinate system\n * @returns Point in canvas coordinate system\n */\n const screenToCanvas = (point: Point): Point => {\n return {\n x: point.x / camera.z - camera.x,\n y: point.y / camera.z - camera.y\n }\n }\n\n // Get node's screen bounds for culling\n function getNodeScreenBounds(\n pos: [number, number],\n size: [number, number]\n ): DOMRect {\n const topLeft = canvasToScreen({ x: pos[0], y: pos[1] })\n const width = size[0] * camera.z\n const height = size[1] * camera.z\n\n return new DOMRect(topLeft.x, topLeft.y, width, height)\n }\n\n // Helper: Calculate zoom-adjusted margin for viewport culling\n function calculateAdjustedMargin(baseMargin: number): number {\n if (camera.z < 0.1) return Math.min(baseMargin * 5, 2.0)\n if (camera.z > 3.0) return Math.max(baseMargin * 0.5, 0.05)\n return baseMargin\n }\n\n // Helper: Check if node is too small to be visible at current zoom\n function isNodeTooSmall(nodeSize: [number, number]): boolean {\n const nodeScreenSize = Math.max(nodeSize[0], nodeSize[1]) * camera.z\n return nodeScreenSize < 4\n }\n\n // Helper: Calculate expanded viewport bounds with margin\n function getExpandedViewportBounds(\n viewport: { width: number; height: number },\n margin: number\n ) {\n const marginX = viewport.width * margin\n const marginY = viewport.height * margin\n return {\n left: -marginX,\n right: viewport.width + marginX,\n top: -marginY,\n bottom: viewport.height + marginY\n }\n }\n\n // Helper: Test if node intersects with viewport bounds\n function testViewportIntersection(\n screenPos: { x: number; y: number },\n nodeSize: [number, number],\n bounds: { left: number; right: number; top: number; bottom: number }\n ): boolean {\n const nodeRight = screenPos.x + nodeSize[0] * camera.z\n const nodeBottom = screenPos.y + nodeSize[1] * camera.z\n\n return !(\n nodeRight < bounds.left ||\n screenPos.x > bounds.right ||\n nodeBottom < bounds.top ||\n screenPos.y > bounds.bottom\n )\n }\n\n // Check if node is within viewport with frustum and size-based culling\n function isNodeInViewport(\n nodePos: [number, number],\n nodeSize: [number, number],\n viewport: { width: number; height: number },\n margin: number = 0.2\n ): boolean {\n // Early exit for tiny nodes\n if (isNodeTooSmall(nodeSize)) return false\n\n const screenPos = canvasToScreen({ x: nodePos[0], y: nodePos[1] })\n const adjustedMargin = calculateAdjustedMargin(margin)\n const bounds = getExpandedViewportBounds(viewport, adjustedMargin)\n\n return testViewportIntersection(screenPos, nodeSize, bounds)\n }\n\n // Get viewport bounds in canvas coordinates (for spatial index queries)\n function getViewportBounds(\n viewport: { width: number; height: number },\n margin: number = 0.2\n ) {\n const marginX = viewport.width * margin\n const marginY = viewport.height * margin\n\n const topLeft = screenToCanvas({ x: -marginX, y: -marginY })\n const bottomRight = screenToCanvas({\n x: viewport.width + marginX,\n y: viewport.height + marginY\n })\n\n return {\n x: topLeft.x,\n y: topLeft.y,\n width: bottomRight.x - topLeft.x,\n height: bottomRight.y - topLeft.y\n }\n }\n\n return {\n camera: readonly(camera),\n transformStyle,\n syncWithCanvas,\n canvasToScreen,\n screenToCanvas,\n getNodeScreenBounds,\n isNodeInViewport,\n getViewportBounds\n }\n}\n\nexport const useTransformState = createSharedComposable(\n useTransformStateIndividual\n)\n","<template>\n <div\n data-testid=\"transform-pane\"\n :class=\"\n cn(\n 'absolute inset-0 w-full h-full pointer-events-none',\n isInteracting ? 'transform-pane--interacting' : 'will-change-auto'\n )\n \"\n :style=\"transformStyle\"\n >\n <!-- Vue nodes will be rendered here -->\n <slot />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useRafFn } from '@vueuse/core'\nimport { computed } from 'vue'\n\nimport type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'\nimport { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'\nimport { useTransformState } from '@/renderer/core/layout/transform/useTransformState'\nimport { cn } from '@/utils/tailwindUtil'\n\ninterface TransformPaneProps {\n canvas?: LGraphCanvas\n}\n\nconst props = defineProps<TransformPaneProps>()\n\nconst { transformStyle, syncWithCanvas } = useTransformState()\n\nconst canvasElement = computed(() => props.canvas?.canvas)\nconst { isTransforming: isInteracting } = useTransformSettling(canvasElement, {\n settleDelay: 16\n})\n\nuseRafFn(\n () => {\n if (!props.canvas) {\n return\n }\n syncWithCanvas(props.canvas)\n },\n { immediate: true }\n)\n</script>\n\n<style scoped>\n.transform-pane--interacting {\n will-change: transform;\n}\n</style>\n","<template>\n <div\n v-if=\"visible && initialized\"\n ref=\"minimapRef\"\n class=\"minimap-main-container absolute right-0 bottom-[54px] z-1000 flex\"\n >\n <MiniMapPanel\n v-if=\"showOptionsPanel\"\n :panel-styles=\"panelStyles\"\n :node-colors=\"nodeColors\"\n :show-links=\"showLinks\"\n :show-groups=\"showGroups\"\n :render-bypass=\"renderBypass\"\n :render-error=\"renderError\"\n @update-option=\"updateOption\"\n />\n\n <div\n ref=\"containerRef\"\n class=\"litegraph-minimap relative border border-interface-stroke bg-comfy-menu-bg shadow-interface\"\n :style=\"containerStyles\"\n >\n <Button\n class=\"absolute top-0 left-0 z-10\"\n size=\"icon\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('g.settings')\"\n @click.stop=\"toggleOptionsPanel\"\n >\n <i class=\"icon-[lucide--settings-2]\" />\n </Button>\n <Button\n class=\"absolute top-0 right-0 z-10\"\n size=\"icon\"\n variant=\"muted-textonly\"\n :aria-label=\"$t('g.close')\"\n data-testid=\"close-minmap-button\"\n @click.stop=\"() => commandStore.execute('Comfy.Canvas.ToggleMinimap')\"\n >\n <i class=\"icon-[lucide--x]\" />\n </Button>\n\n <hr\n class=\"absolute top-6 h-px border-0 bg-node-component-border\"\n :style=\"{\n width: containerStyles.width\n }\"\n />\n\n <canvas\n ref=\"canvasRef\"\n :width=\"width\"\n :height=\"height\"\n class=\"minimap-canvas\"\n />\n\n <div class=\"minimap-viewport\" :style=\"viewportStyles\" />\n\n <div\n class=\"absolute inset-0 touch-none\"\n @pointerdown=\"handlePointerDown\"\n @pointermove=\"handlePointerMove\"\n @pointerup=\"handlePointerUp\"\n @pointerleave=\"handlePointerUp\"\n @pointercancel=\"handlePointerCancel\"\n @wheel=\"handleWheel\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, ref, useTemplateRef } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap'\nimport { useCommandStore } from '@/stores/commandStore'\n\nimport MiniMapPanel from './MiniMapPanel.vue'\n\nconst commandStore = useCommandStore()\nconst minimapRef = ref<HTMLDivElement>()\nconst containerRef = useTemplateRef<HTMLDivElement>('containerRef')\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef')\n\nconst {\n initialized,\n visible,\n containerStyles,\n viewportStyles,\n width,\n height,\n panelStyles,\n nodeColors,\n showLinks,\n showGroups,\n renderBypass,\n renderError,\n updateOption,\n destroy,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n handlePointerCancel,\n handleWheel,\n setMinimapRef\n} = useMinimap({\n containerRefMaybe: containerRef,\n canvasRefMaybe: canvasRef\n})\n\nconst showOptionsPanel = ref(false)\n\nconst toggleOptionsPanel = () => {\n showOptionsPanel.value = !showOptionsPanel.value\n}\n\nonMounted(() => {\n if (minimapRef.value) {\n setMinimapRef(minimapRef.value)\n }\n})\n\nonUnmounted(() => {\n destroy()\n})\n</script>\n\n<style scoped>\n.litegraph-minimap {\n overflow: hidden;\n}\n\n.minimap-canvas {\n display: block;\n width: 100%;\n height: 100%;\n pointer-events: none;\n}\n\n.minimap-viewport {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n}\n</style>\n","/**\n * Checks if a pointer/mouse event has multi-select modifier keys pressed.\n * Multi-select keys are: Ctrl (Windows/Linux), Cmd (Mac), or Shift\n *\n * @param event - The pointer or mouse event to check\n * @returns true if any multi-select modifier key is pressed\n */\nexport function isMultiSelectKey(event: PointerEvent | MouseEvent): boolean {\n return event.ctrlKey || event.metaKey || event.shiftKey\n}\n","/**\n * Node Event Handlers Composable\n *\n * Handles all Vue node interaction events including:\n * - Node selection with multi-select support\n * - Node collapse/expand state management\n * - Node title editing and updates\n * - Layout mutations for visual feedback\n * - Integration with LiteGraph canvas selection system\n */\nimport { createSharedComposable } from '@vueuse/core'\n\nimport { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'\nimport { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'\nimport { isMultiSelectKey } from '@/renderer/extensions/vueNodes/utils/selectionUtils'\nimport type { NodeId } from '@/renderer/core/layout/types'\n\nfunction useNodeEventHandlersIndividual() {\n const canvasStore = useCanvasStore()\n const { nodeManager } = useVueNodeLifecycle()\n const { bringNodeToFront } = useNodeZIndex()\n const { shouldHandleNodePointerEvents } = useCanvasInteractions()\n\n /**\n * Handle node selection events\n * Supports single selection and multi-select with Ctrl/Cmd\n */\n function handleNodeSelect(event: PointerEvent, nodeId: NodeId) {\n if (!shouldHandleNodePointerEvents.value) return\n\n if (!canvasStore.canvas || !nodeManager.value) return\n\n const node = nodeManager.value.getNode(nodeId)\n if (!node) return\n\n const multiSelect = isMultiSelectKey(event)\n const selectedItemsCount = canvasStore.selectedItems.length\n const preserveExistingSelection =\n !multiSelect && node.selected && selectedItemsCount > 1\n\n if (multiSelect) {\n if (!node.selected) {\n canvasStore.canvas.select(node)\n }\n } else if (!preserveExistingSelection) {\n // Regular click -> single select\n canvasStore.canvas.deselectAll()\n canvasStore.canvas.select(node)\n }\n\n // Bring node to front when clicked (similar to LiteGraph behavior)\n // Skip if node is pinned to avoid unwanted movement\n if (!node.flags?.pinned) {\n bringNodeToFront(nodeId)\n }\n\n // Update canvas selection tracking\n canvasStore.updateSelectedItems()\n }\n\n /**\n * Handle node collapse/expand state changes\n * Uses LiteGraph's native collapse method for proper state management\n */\n function handleNodeCollapse(nodeId: NodeId, collapsed: boolean) {\n if (!shouldHandleNodePointerEvents.value) return\n\n if (!nodeManager.value) return\n\n const node = nodeManager.value.getNode(nodeId)\n if (!node) return\n\n // Use LiteGraph's collapse method if the state needs to change\n const currentCollapsed = node.flags?.collapsed ?? false\n if (currentCollapsed !== collapsed) {\n node.collapse()\n }\n }\n\n /**\n * Handle node title updates\n * Updates the title in LiteGraph for persistence across sessions\n */\n function handleNodeTitleUpdate(nodeId: NodeId, newTitle: string) {\n if (!shouldHandleNodePointerEvents.value) return\n\n if (!nodeManager.value) return\n\n const node = nodeManager.value.getNode(nodeId)\n if (!node) return\n\n // Update the node title in LiteGraph for persistence\n node.title = newTitle\n }\n\n /**\n * Handle node right-click context menu events\n * Integrates with LiteGraph's context menu system\n */\n function handleNodeRightClick(event: PointerEvent, nodeId: NodeId) {\n if (!shouldHandleNodePointerEvents.value) return\n\n if (!canvasStore.canvas || !nodeManager.value) return\n\n const node = nodeManager.value.getNode(nodeId)\n if (!node) return\n\n // Prevent default context menu\n event.preventDefault()\n\n // Select the node if not already selected\n if (!node.selected) {\n handleNodeSelect(event, nodeId)\n }\n\n // Let LiteGraph handle the context menu\n // The canvas will handle showing the appropriate context menu\n }\n\n function toggleNodeSelectionAfterPointerUp(\n nodeId: NodeId,\n multiSelect: boolean\n ) {\n if (!shouldHandleNodePointerEvents.value) return\n\n if (!canvasStore.canvas || !nodeManager.value) return\n\n const node = nodeManager.value.getNode(nodeId)\n if (!node) return\n\n if (!multiSelect) {\n canvasStore.canvas.deselectAll()\n canvasStore.canvas.select(node)\n canvasStore.updateSelectedItems()\n // Bring node to front when selected (unless pinned)\n if (!node.flags?.pinned) {\n bringNodeToFront(nodeId)\n }\n return\n }\n\n if (node.selected) {\n canvasStore.canvas.deselect(node)\n } else {\n canvasStore.canvas.select(node)\n // Bring node to front when selected (unless pinned)\n if (!node.flags?.pinned) {\n bringNodeToFront(nodeId)\n }\n }\n\n canvasStore.updateSelectedItems()\n }\n\n return {\n // Core event handlers\n handleNodeSelect,\n handleNodeCollapse,\n handleNodeTitleUpdate,\n handleNodeRightClick,\n\n // Batch operations\n toggleNodeSelectionAfterPointerUp\n }\n}\n\nexport const useNodeEventHandlers = createSharedComposable(\n useNodeEventHandlersIndividual\n)\n","import { computed } from 'vue'\n\nimport { snapPoint } from '@/lib/litegraph/src/measure'\nimport { useSettingStore } from '@/platform/settings/settingStore'\n\n/**\n * Composable for node snap-to-grid functionality\n *\n * Provides reactive access to snap settings and utilities for applying\n * snap-to-grid behavior to Vue nodes during drag and resize operations.\n */\nexport function useNodeSnap() {\n const settingStore = useSettingStore()\n\n // Reactive snap settings\n const gridSize = computed(() => settingStore.get('Comfy.SnapToGrid.GridSize'))\n const alwaysSnap = computed(() => settingStore.get('pysssss.SnapToGrid'))\n\n /**\n * Determines if snap-to-grid should be applied based on shift key and settings\n * @param event - The pointer event to check for shift key\n * @returns true if snapping should be applied\n */\n function shouldSnap(event: PointerEvent): boolean {\n return event.shiftKey || alwaysSnap.value\n }\n\n /**\n * Applies snap-to-grid to a position\n * @param position - Position object with x, y coordinates\n * @returns The snapped position as a new object\n */\n function applySnapToPosition(position: { x: number; y: number }): {\n x: number\n y: number\n } {\n const size = gridSize.value\n if (!size) return { ...position }\n\n const posArray: [number, number] = [position.x, position.y]\n if (snapPoint(posArray, size)) {\n return { x: posArray[0], y: posArray[1] }\n }\n return { ...position }\n }\n\n /**\n * Applies snap-to-grid to a size (width/height)\n * @param size - Size object with width, height\n * @returns The snapped size as a new object\n */\n function applySnapToSize(size: { width: number; height: number }): {\n width: number\n height: number\n } {\n const gridSizeValue = gridSize.value\n if (!gridSizeValue) return { ...size }\n\n const sizeArray: [number, number] = [size.width, size.height]\n if (snapPoint(sizeArray, gridSizeValue)) {\n return { width: sizeArray[0], height: sizeArray[1] }\n }\n return { ...size }\n }\n\n return {\n gridSize,\n alwaysSnap,\n shouldSnap,\n applySnapToPosition,\n applySnapToSize\n }\n}\n","import { tryOnScopeDispose, useEventListener } from '@vueuse/core'\nimport { shallowRef } from 'vue'\n\nimport { app } from '@/scripts/app'\n\n/**\n * Composable for synchronizing shift key state from Vue nodes to LiteGraph canvas.\n *\n * Enables snap-to-grid preview rendering in LiteGraph during Vue node drag/resize operations\n * by dispatching synthetic keyboard events to the canvas element.\n *\n * @returns Object containing trackShiftKey function for shift state synchronization lifecycle\n *\n * @example\n * ```ts\n * const { trackShiftKey } = useShiftKeySync()\n *\n * function startDrag(event: PointerEvent) {\n * const stopTracking = trackShiftKey(event)\n * // ... drag logic\n * // Call stopTracking() on pointerup to cleanup listeners\n * }\n * ```\n */\nexport function useShiftKeySync() {\n const shiftKeyState = shallowRef(false)\n let canvasEl: HTMLCanvasElement | null = null\n\n /**\n * Synchronizes shift key state to LiteGraph canvas by dispatching synthetic keyboard events.\n *\n * Only dispatches events when shift state actually changes to minimize overhead.\n * Canvas reference is lazily initialized on first sync.\n *\n * @param isShiftPressed - Current shift key state to synchronize\n */\n function syncShiftState(isShiftPressed: boolean) {\n if (isShiftPressed === shiftKeyState.value) return\n\n // Lazy-initialize canvas reference on first use\n if (!canvasEl) {\n canvasEl = app.canvas?.canvas ?? null\n if (!canvasEl) return // Canvas not ready yet\n }\n\n shiftKeyState.value = isShiftPressed\n canvasEl.dispatchEvent(\n new KeyboardEvent(isShiftPressed ? 'keydown' : 'keyup', {\n key: 'Shift',\n shiftKey: isShiftPressed,\n bubbles: true\n })\n )\n }\n\n /**\n * Tracks shift key state during drag/resize operations and synchronizes to canvas.\n *\n * Attaches window-level keyboard event listeners for the duration of the operation.\n * Listeners are automatically cleaned up when the returned function is called.\n *\n * @param initialEvent - Initial pointer event containing shift key state at drag/resize start\n * @returns Cleanup function that removes event listeners - must be called when operation ends\n *\n * @example\n * ```ts\n * function startDrag(event: PointerEvent) {\n * const stopTracking = trackShiftKey(event)\n *\n * const handlePointerUp = () => {\n * stopTracking() // Cleanup listeners\n * }\n * }\n * ```\n */\n function trackShiftKey(initialEvent: PointerEvent): () => void {\n // Sync initial shift state\n syncShiftState(initialEvent.shiftKey)\n\n // Listen for shift key press/release during the operation\n const handleKeyEvent = (e: KeyboardEvent) => {\n if (e.key !== 'Shift') return\n syncShiftState(e.shiftKey)\n }\n\n const stopKeydown = useEventListener(window, 'keydown', handleKeyEvent, {\n passive: true\n })\n const stopKeyup = useEventListener(window, 'keyup', handleKeyEvent, {\n passive: true\n })\n\n // Return cleanup function that stops both listeners\n return () => {\n stopKeydown()\n stopKeyup()\n }\n }\n\n // Cleanup on component unmount\n tryOnScopeDispose(() => {\n shiftKeyState.value = false\n canvasEl = null\n })\n\n return { trackShiftKey }\n}\n","import { storeToRefs } from 'pinia'\nimport { toValue } from 'vue'\n\nimport type { LGraphGroup } from '@/lib/litegraph/src/LGraphGroup'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { LayoutSource } from '@/renderer/core/layout/types'\nimport type {\n NodeBoundsUpdate,\n NodeId,\n Point\n} from '@/renderer/core/layout/types'\nimport { useNodeSnap } from '@/renderer/extensions/vueNodes/composables/useNodeSnap'\nimport { useShiftKeySync } from '@/renderer/extensions/vueNodes/composables/useShiftKeySync'\nimport { useTransformState } from '@/renderer/core/layout/transform/useTransformState'\nimport { isLGraphGroup } from '@/utils/litegraphUtil'\nimport { createSharedComposable } from '@vueuse/core'\n\nexport const useNodeDrag = createSharedComposable(useNodeDragIndividual)\n\nfunction useNodeDragIndividual() {\n const mutations = useLayoutMutations()\n const { selectedNodeIds, selectedItems } = storeToRefs(useCanvasStore())\n\n // Get transform utilities from TransformPane if available\n const transformState = useTransformState()\n\n // Snap-to-grid functionality\n const { shouldSnap, applySnapToPosition } = useNodeSnap()\n\n // Shift key sync for LiteGraph canvas preview\n const { trackShiftKey } = useShiftKeySync()\n\n // Drag state\n let dragStartPos: Point | null = null\n let dragStartMouse: Point | null = null\n let otherSelectedNodesStartPositions: Map<string, Point> | null = null\n let rafId: number | null = null\n let stopShiftSync: (() => void) | null = null\n\n // For groups: track the last applied canvas delta to compute frame delta\n let lastCanvasDelta: Point | null = null\n let selectedGroups: LGraphGroup[] | null = null\n\n function startDrag(event: PointerEvent, nodeId: NodeId) {\n const layout = toValue(layoutStore.getNodeLayoutRef(nodeId))\n if (!layout) return\n const position = layout.position ?? { x: 0, y: 0 }\n\n // Track shift key state and sync to canvas for snap preview\n stopShiftSync = trackShiftKey(event)\n\n dragStartPos = { ...position }\n dragStartMouse = { x: event.clientX, y: event.clientY }\n\n const selectedNodes = toValue(selectedNodeIds)\n\n // capture the starting positions of all other selected nodes\n // Only move other selected items if the dragged node is part of the selection\n const isDraggedNodeInSelection = selectedNodes?.has(nodeId)\n\n if (isDraggedNodeInSelection && selectedNodes.size > 1) {\n otherSelectedNodesStartPositions = new Map()\n\n for (const id of selectedNodes) {\n // Skip the current node being dragged\n if (id === nodeId) continue\n\n const nodeLayout = layoutStore.getNodeLayoutRef(id).value\n if (nodeLayout) {\n otherSelectedNodesStartPositions.set(id, { ...nodeLayout.position })\n }\n }\n } else {\n otherSelectedNodesStartPositions = null\n }\n\n // Capture selected groups only if the dragged node is part of the selection\n // This prevents groups from moving when dragging an unrelated node\n if (isDraggedNodeInSelection) {\n selectedGroups = toValue(selectedItems).filter(isLGraphGroup)\n lastCanvasDelta = { x: 0, y: 0 }\n } else {\n selectedGroups = null\n lastCanvasDelta = null\n }\n\n mutations.setSource(LayoutSource.Vue)\n }\n\n function handleDrag(event: PointerEvent, nodeId: NodeId) {\n if (!dragStartPos || !dragStartMouse) {\n return\n }\n\n // Throttle position updates using requestAnimationFrame for better performance\n if (rafId !== null) return // Skip if frame already scheduled\n\n const { target, pointerId } = event\n if (target instanceof HTMLElement && !target.hasPointerCapture(pointerId)) {\n // Delay capture to drag to allow for the Node cloning\n target.setPointerCapture(pointerId)\n }\n rafId = requestAnimationFrame(() => {\n rafId = null\n\n if (!dragStartPos || !dragStartMouse) return\n\n // Calculate mouse delta in screen coordinates\n const mouseDelta = {\n x: event.clientX - dragStartMouse.x,\n y: event.clientY - dragStartMouse.y\n }\n\n // Convert to canvas coordinates\n const canvasOrigin = transformState.screenToCanvas({ x: 0, y: 0 })\n const canvasWithDelta = transformState.screenToCanvas(mouseDelta)\n const canvasDelta = {\n x: canvasWithDelta.x - canvasOrigin.x,\n y: canvasWithDelta.y - canvasOrigin.y\n }\n\n // Calculate new position for the current node\n const newPosition = {\n x: dragStartPos.x + canvasDelta.x,\n y: dragStartPos.y + canvasDelta.y\n }\n\n // Apply mutation through the layout system (Vue batches DOM updates automatically)\n mutations.moveNode(nodeId, newPosition)\n\n // If we're dragging multiple selected nodes, move them all together\n if (\n otherSelectedNodesStartPositions &&\n otherSelectedNodesStartPositions.size > 0\n ) {\n for (const [\n otherNodeId,\n startPos\n ] of otherSelectedNodesStartPositions) {\n const newOtherPosition = {\n x: startPos.x + canvasDelta.x,\n y: startPos.y + canvasDelta.y\n }\n mutations.moveNode(otherNodeId, newOtherPosition)\n }\n }\n\n // Move selected groups using frame delta (difference from last frame)\n // This matches LiteGraph's behavior which uses delta-based movement\n if (selectedGroups && selectedGroups.length > 0 && lastCanvasDelta) {\n const frameDelta = {\n x: canvasDelta.x - lastCanvasDelta.x,\n y: canvasDelta.y - lastCanvasDelta.y\n }\n\n for (const group of selectedGroups) {\n group.move(frameDelta.x, frameDelta.y, true)\n }\n }\n\n lastCanvasDelta = canvasDelta\n })\n }\n\n function endDrag(event: PointerEvent, nodeId: NodeId | undefined) {\n // Apply snap to final position if snap was active (matches LiteGraph behavior)\n if (shouldSnap(event) && nodeId) {\n const boundsUpdates: NodeBoundsUpdate[] = []\n\n // Snap main node\n const currentLayout = toValue(layoutStore.getNodeLayoutRef(nodeId))\n if (currentLayout) {\n const currentPos = currentLayout.position\n const snappedPos = applySnapToPosition({ ...currentPos })\n\n // Only add update if position actually changed\n if (snappedPos.x !== currentPos.x || snappedPos.y !== currentPos.y) {\n boundsUpdates.push({\n nodeId,\n bounds: {\n x: snappedPos.x,\n y: snappedPos.y,\n width: currentLayout.size.width,\n height: currentLayout.size.height\n }\n })\n }\n }\n\n // Also snap other selected nodes\n // Capture all positions at the start to ensure consistent state\n if (\n otherSelectedNodesStartPositions &&\n otherSelectedNodesStartPositions.size > 0\n ) {\n for (const otherNodeId of otherSelectedNodesStartPositions.keys()) {\n const nodeLayout = layoutStore.getNodeLayoutRef(otherNodeId).value\n if (nodeLayout) {\n const currentPos = { ...nodeLayout.position }\n const snappedPos = applySnapToPosition(currentPos)\n\n // Only add update if position actually changed\n if (\n snappedPos.x !== currentPos.x ||\n snappedPos.y !== currentPos.y\n ) {\n boundsUpdates.push({\n nodeId: otherNodeId,\n bounds: {\n x: snappedPos.x,\n y: snappedPos.y,\n width: nodeLayout.size.width,\n height: nodeLayout.size.height\n }\n })\n }\n }\n }\n }\n\n // Apply all snap updates in a single batched transaction\n if (boundsUpdates.length > 0) {\n layoutStore.batchUpdateNodeBounds(boundsUpdates)\n }\n }\n\n dragStartPos = null\n dragStartMouse = null\n otherSelectedNodesStartPositions = null\n selectedGroups = null\n lastCanvasDelta = null\n\n // Stop tracking shift key state\n stopShiftSync?.()\n stopShiftSync = null\n\n // Cancel any pending animation frame\n if (rafId !== null) {\n cancelAnimationFrame(rafId)\n rafId = null\n }\n }\n\n return {\n startDrag,\n handleDrag,\n endDrag\n }\n}\n","import { onScopeDispose, ref, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport { isMiddlePointerInput } from '@/base/pointerUtils'\nimport { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'\nimport { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'\nimport { isMultiSelectKey } from '@/renderer/extensions/vueNodes/utils/selectionUtils'\nimport { useNodeDrag } from '@/renderer/extensions/vueNodes/layout/useNodeDrag'\n\nexport function useNodePointerInteractions(\n nodeIdRef: MaybeRefOrGetter<string>\n) {\n const { startDrag, endDrag, handleDrag } = useNodeDrag()\n // Use canvas interactions for proper wheel event handling and pointer event capture control\n const { forwardEventToCanvas, shouldHandleNodePointerEvents } =\n useCanvasInteractions()\n const { handleNodeSelect, toggleNodeSelectionAfterPointerUp } =\n useNodeEventHandlers()\n const { nodeManager } = useVueNodeLifecycle()\n\n const forwardMiddlePointerIfNeeded = (event: PointerEvent) => {\n if (!isMiddlePointerInput(event)) return false\n forwardEventToCanvas(event)\n return true\n }\n\n let hasDraggingStarted = false\n\n const startPosition = ref({ x: 0, y: 0 })\n\n const DRAG_THRESHOLD = 3 // pixels\n\n function onPointerdown(event: PointerEvent) {\n if (forwardMiddlePointerIfNeeded(event)) return\n\n // Only start drag on left-click (button 0)\n if (event.button !== 0) return\n\n // Don't handle pointer events when canvas is in panning mode - forward to canvas instead\n if (!shouldHandleNodePointerEvents.value) {\n forwardEventToCanvas(event)\n return\n }\n\n const nodeId = toValue(nodeIdRef)\n if (!nodeId) {\n console.warn(\n 'LGraphNode: nodeData is null/undefined in handlePointerDown'\n )\n return\n }\n\n // IMPORTANT: Read from actual LGraphNode to get correct state\n if (nodeManager.value?.getNode(nodeId)?.flags?.pinned) {\n return\n }\n\n startPosition.value = { x: event.clientX, y: event.clientY }\n\n safeDragStart(event, nodeId)\n }\n\n function onPointermove(event: PointerEvent) {\n if (forwardMiddlePointerIfNeeded(event)) return\n\n // Don't activate drag while resizing\n if (layoutStore.isResizingVueNodes.value) return\n\n const nodeId = toValue(nodeIdRef)\n\n if (nodeManager.value?.getNode(nodeId)?.flags?.pinned) {\n return\n }\n\n const multiSelect = isMultiSelectKey(event)\n\n const lmbDown = event.buttons & 1\n if (lmbDown && multiSelect && !layoutStore.isDraggingVueNodes.value) {\n layoutStore.isDraggingVueNodes.value = true\n handleNodeSelect(event, nodeId)\n safeDragStart(event, nodeId)\n return\n }\n // Check if we should start dragging (pointer moved beyond threshold)\n if (lmbDown && !layoutStore.isDraggingVueNodes.value) {\n const dx = event.clientX - startPosition.value.x\n const dy = event.clientY - startPosition.value.y\n const distance = Math.sqrt(dx * dx + dy * dy)\n\n if (distance > DRAG_THRESHOLD) {\n layoutStore.isDraggingVueNodes.value = true\n handleNodeSelect(event, nodeId)\n }\n }\n\n if (layoutStore.isDraggingVueNodes.value) {\n handleDrag(event, nodeId)\n }\n }\n\n function cleanupDragState() {\n layoutStore.isDraggingVueNodes.value = false\n }\n\n function safeDragStart(event: PointerEvent, nodeId: string) {\n try {\n startDrag(event, nodeId)\n } finally {\n hasDraggingStarted = true\n }\n }\n\n function safeDragEnd(event: PointerEvent) {\n try {\n const nodeId = toValue(nodeIdRef)\n endDrag(event, nodeId)\n } catch (error) {\n console.error('Error during endDrag:', error)\n } finally {\n hasDraggingStarted = false\n cleanupDragState()\n }\n }\n\n function onPointerup(event: PointerEvent) {\n if (forwardMiddlePointerIfNeeded(event)) return\n // Don't handle pointer events when canvas is in panning mode - forward to canvas instead\n const canHandlePointer = shouldHandleNodePointerEvents.value\n if (!canHandlePointer) {\n forwardEventToCanvas(event)\n return\n }\n const wasDragging = layoutStore.isDraggingVueNodes.value\n\n if (hasDraggingStarted || wasDragging) {\n safeDragEnd(event)\n\n if (wasDragging) {\n return\n }\n }\n\n // Skip selection handling for right-click (button 2) - context menu handles its own selection\n if (event.button === 2) return\n\n const multiSelect = isMultiSelectKey(event)\n\n const nodeId = toValue(nodeIdRef)\n if (nodeId) {\n toggleNodeSelectionAfterPointerUp(nodeId, multiSelect)\n }\n }\n\n function onPointercancel(event: PointerEvent) {\n if (!layoutStore.isDraggingVueNodes.value) return\n safeDragEnd(event)\n }\n\n /**\n * Handles right-click during drag operations\n * Cancels the current drag to prevent context menu from appearing while dragging\n */\n function onContextmenu(event: MouseEvent) {\n if (!layoutStore.isDraggingVueNodes.value) return\n\n event.preventDefault()\n // Simply cleanup state without calling endDrag to avoid synthetic event creation\n cleanupDragState()\n }\n\n // Cleanup on unmount to prevent resource leaks\n onScopeDispose(() => {\n cleanupDragState()\n })\n\n const pointerHandlers = {\n onPointerdown,\n onPointermove,\n onPointerup,\n onPointercancel,\n onContextmenu\n } as const\n\n return {\n pointerHandlers\n }\n}\n","/**\n * Generic Vue Element Tracking System\n *\n * Automatically tracks DOM size and position changes for Vue-rendered elements\n * and syncs them to the layout store. Uses a single shared ResizeObserver for\n * performance, with elements identified by configurable data attributes.\n *\n * Supports different element types (nodes, slots, widgets, etc.) with\n * customizable data attributes and update handlers.\n */\nimport { getCurrentInstance, onMounted, onUnmounted, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport { useSharedCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'\nimport { LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport type { Bounds, NodeId } from '@/renderer/core/layout/types'\nimport { LayoutSource } from '@/renderer/core/layout/types'\n\nimport { syncNodeSlotLayoutsFromDOM } from './useSlotElementTracking'\n\n/**\n * Generic update item for element bounds tracking\n */\ninterface ElementBoundsUpdate {\n /** Element identifier (could be nodeId, widgetId, slotId, etc.) */\n id: string\n /** Updated bounds */\n bounds: Bounds\n}\n\n/**\n * Configuration for different types of tracked elements\n */\ninterface ElementTrackingConfig {\n /** Data attribute name (e.g., 'nodeId') */\n dataAttribute: string\n /** Handler for processing bounds updates */\n updateHandler: (updates: ElementBoundsUpdate[]) => void\n}\n\n/**\n * Registry of tracking configurations by element type\n */\nconst trackingConfigs: Map<string, ElementTrackingConfig> = new Map([\n [\n 'node',\n {\n dataAttribute: 'nodeId',\n updateHandler: (updates) => {\n const nodeUpdates = updates.map(({ id, bounds }) => ({\n nodeId: id as NodeId,\n bounds\n }))\n layoutStore.batchUpdateNodeBounds(nodeUpdates)\n }\n }\n ]\n])\n\n// Single ResizeObserver instance for all Vue elements\nconst resizeObserver = new ResizeObserver((entries) => {\n if (useCanvasStore().linearMode) return\n // Canvas is ready when this code runs; no defensive guards needed.\n const conv = useSharedCanvasPositionConversion()\n // Group updates by type, then flush via each config's handler\n const updatesByType = new Map<string, ElementBoundsUpdate[]>()\n // Track nodes whose slots should be resynced after node size changes\n const nodesNeedingSlotResync = new Set<string>()\n\n for (const entry of entries) {\n if (!(entry.target instanceof HTMLElement)) continue\n const element = entry.target\n\n // Find which type this element belongs to\n let elementType: string | undefined\n let elementId: string | undefined\n\n for (const [type, config] of trackingConfigs) {\n const id = element.dataset[config.dataAttribute]\n if (id) {\n elementType = type\n elementId = id\n break\n }\n }\n\n if (!elementType || !elementId) continue\n\n // Use borderBoxSize when available; fall back to contentRect for older engines/tests\n // Border box is the border included FULL wxh DOM value.\n const borderBox = Array.isArray(entry.borderBoxSize)\n ? entry.borderBoxSize[0]\n : {\n inlineSize: entry.contentRect.width,\n blockSize: entry.contentRect.height\n }\n const width = borderBox.inlineSize\n const height = borderBox.blockSize\n\n // Screen-space rect\n const rect = element.getBoundingClientRect()\n const [cx, cy] = conv.clientPosToCanvasPos([rect.left, rect.top])\n const topLeftCanvas = { x: cx, y: cy }\n const bounds: Bounds = {\n x: topLeftCanvas.x,\n y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT,\n width: Math.max(0, width),\n height: Math.max(0, height)\n }\n\n let updates = updatesByType.get(elementType)\n if (!updates) {\n updates = []\n updatesByType.set(elementType, updates)\n }\n updates.push({ id: elementId, bounds })\n\n // If this entry is a node, mark it for slot layout resync\n if (elementType === 'node' && elementId) {\n nodesNeedingSlotResync.add(elementId)\n }\n }\n\n layoutStore.setSource(LayoutSource.DOM)\n\n // Flush per-type\n for (const [type, updates] of updatesByType) {\n const config = trackingConfigs.get(type)\n if (config && updates.length) config.updateHandler(updates)\n }\n\n // After node bounds are updated, refresh slot cached offsets and layouts\n if (nodesNeedingSlotResync.size > 0) {\n for (const nodeId of nodesNeedingSlotResync) {\n syncNodeSlotLayoutsFromDOM(nodeId)\n }\n }\n})\n\n/**\n * Tracks DOM element size/position changes for a Vue component and syncs to layout store\n *\n * Sets up automatic ResizeObserver tracking when the component mounts and cleans up\n * when unmounted. The tracked element is identified by a data attribute set on the\n * component's root DOM element.\n *\n * @param appIdentifier - Application-level identifier for this tracked element (not a DOM ID)\n * Example: node ID like 'node-123', widget ID like 'widget-456'\n * @param trackingType - Type of element being tracked, determines which tracking config to use\n * Example: 'node' for Vue nodes, 'widget' for UI widgets\n *\n * @example\n * ```ts\n * // Track a Vue node component with ID 'my-node-123'\n * useVueElementTracking('my-node-123', 'node')\n *\n * // Would set data-node-id=\"my-node-123\" on the component's root element\n * // and sync size changes to layoutStore.batchUpdateNodeBounds()\n * ```\n */\nexport function useVueElementTracking(\n appIdentifierMaybe: MaybeRefOrGetter<string>,\n trackingType: string\n) {\n const appIdentifier = toValue(appIdentifierMaybe)\n onMounted(() => {\n const element = getCurrentInstance()?.proxy?.$el\n if (!(element instanceof HTMLElement) || !appIdentifier) return\n\n const config = trackingConfigs.get(trackingType)\n if (!config) return\n\n // Set the data attribute expected by the RO pipeline for this type\n element.dataset[config.dataAttribute] = appIdentifier\n resizeObserver.observe(element)\n })\n\n onUnmounted(() => {\n const element = getCurrentInstance()?.proxy?.$el\n if (!(element instanceof HTMLElement)) return\n\n const config = trackingConfigs.get(trackingType)\n if (!config) return\n\n // Remove the data attribute and observer\n delete element.dataset[config.dataAttribute]\n resizeObserver.unobserve(element)\n })\n}\n","import { storeToRefs } from 'pinia'\nimport { computed, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport { useExecutionStore } from '@/stores/executionStore'\n\n/**\n * Composable for managing execution state of Vue-based nodes\n *\n * Provides reactive access to execution state and progress for a specific node\n * by injecting execution data from the parent GraphCanvas provider.\n *\n * @param nodeLocatorIdMaybe - Locator ID (root or subgraph scoped) of the node to track\n * @returns Object containing reactive execution state and progress\n */\nexport const useNodeExecutionState = (\n nodeLocatorIdMaybe: MaybeRefOrGetter<string | undefined>\n) => {\n const locatorId = computed(() => toValue(nodeLocatorIdMaybe) ?? '')\n const { nodeLocationProgressStates, isIdle } =\n storeToRefs(useExecutionStore())\n\n const progressState = computed(() => {\n const id = locatorId.value\n return id ? nodeLocationProgressStates.value[id] : undefined\n })\n\n const executing = computed(\n () => !isIdle.value && progressState.value?.state === 'running'\n )\n\n const progress = computed(() => {\n const state = progressState.value\n return state && state.max > 0 ? state.value / state.max : undefined\n })\n\n const progressPercentage = computed(() => {\n const prog = progress.value\n return prog !== undefined ? Math.round(prog * 100) : undefined\n })\n\n const executionState = computed(() => {\n const state = progressState.value\n if (!state) return 'idle'\n return state.state\n })\n\n return {\n executing,\n progress,\n progressPercentage,\n progressState,\n executionState\n }\n}\n","import { computed, onUnmounted, toValue } from 'vue'\nimport type { MaybeRefOrGetter } from 'vue'\n\nimport { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { LayoutSource } from '@/renderer/core/layout/types'\nimport type { Point } from '@/renderer/core/layout/types'\n\n/**\n * Composable for individual Vue node components\n * Uses customRef for shared write access with Canvas renderer\n */\nexport function useNodeLayout(nodeIdMaybe: MaybeRefOrGetter<string>) {\n const nodeId = toValue(nodeIdMaybe)\n const mutations = useLayoutMutations()\n\n // Get the customRef for this node (shared write access)\n const layoutRef = layoutStore.getNodeLayoutRef(nodeId)\n\n // Clean up refs and triggers when Vue component unmounts\n onUnmounted(() => {\n layoutStore.cleanupNodeRef(nodeId)\n })\n\n // Computed properties for easy access\n const position = computed(() => {\n const layout = layoutRef.value\n const pos = layout?.position ?? { x: 0, y: 0 }\n return pos\n })\n const size = computed(\n () => layoutRef.value?.size ?? { width: 200, height: 100 }\n )\n\n const zIndex = computed(() => layoutRef.value?.zIndex ?? 0)\n\n /**\n * Update node position directly (without drag)\n */\n function moveNodeTo(position: Point) {\n mutations.setSource(LayoutSource.Vue)\n mutations.moveNode(nodeId, position)\n }\n\n return {\n // Reactive state (via customRef)\n position,\n size,\n zIndex,\n\n // Mutations\n moveNodeTo\n }\n}\n","import { storeToRefs } from 'pinia'\nimport { computed, toValue } from 'vue'\nimport type { MaybeRefOrGetter, Ref } from 'vue'\n\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useNodeOutputStore } from '@/stores/imagePreviewStore'\n\nexport const useNodePreviewState = (\n nodeIdMaybe: MaybeRefOrGetter<string>,\n options?: {\n isCollapsed?: Ref<boolean>\n }\n) => {\n const nodeId = toValue(nodeIdMaybe)\n const workflowStore = useWorkflowStore()\n const { nodePreviewImages } = storeToRefs(useNodeOutputStore())\n\n const locatorId = computed(() => workflowStore.nodeIdToNodeLocatorId(nodeId))\n\n const previewUrls = computed(() => {\n const key = locatorId.value\n if (!key) return undefined\n const urls = nodePreviewImages.value[key]\n return urls?.length ? urls : undefined\n })\n\n const hasPreview = computed(() => !!previewUrls.value?.length)\n\n const latestPreviewUrl = computed(() => {\n const urls = previewUrls.value\n return urls?.length ? urls.at(-1) : ''\n })\n\n const shouldShowPreviewImg = computed(() => {\n if (!options?.isCollapsed) {\n return hasPreview.value\n }\n return !options.isCollapsed.value && hasPreview.value\n })\n\n return {\n locatorId,\n previewUrls,\n hasPreview,\n latestPreviewUrl,\n shouldShowPreviewImg\n }\n}\n","import { useEventListener } from '@vueuse/core'\nimport { ref } from 'vue'\n\nimport type { Point, Size } from '@/renderer/core/layout/types'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport { useNodeSnap } from '@/renderer/extensions/vueNodes/composables/useNodeSnap'\nimport { useShiftKeySync } from '@/renderer/extensions/vueNodes/composables/useShiftKeySync'\nimport { useTransformState } from '@/renderer/core/layout/transform/useTransformState'\n\ninterface ResizeCallbackPayload {\n size: Size\n}\n\n/**\n * Composable for node resizing functionality (bottom-right corner only)\n *\n * Provides resize handle interaction that integrates with the layout system.\n * Handles pointer capture, coordinate calculations, and size constraints.\n */\nexport function useNodeResize(\n resizeCallback: (payload: ResizeCallbackPayload, element: HTMLElement) => void\n) {\n const transformState = useTransformState()\n\n const isResizing = ref(false)\n const resizeStartPointer = ref<Point | null>(null)\n const resizeStartSize = ref<Size | null>(null)\n\n // Snap-to-grid functionality\n const { shouldSnap, applySnapToSize } = useNodeSnap()\n\n // Shift key sync for LiteGraph canvas preview\n const { trackShiftKey } = useShiftKeySync()\n\n const startResize = (event: PointerEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n const target = event.currentTarget\n if (!(target instanceof HTMLElement)) return\n\n const nodeElement = target.closest('[data-node-id]')\n if (!(nodeElement instanceof HTMLElement)) return\n\n const rect = nodeElement.getBoundingClientRect()\n const scale = transformState.camera.z\n\n const startSize: Size = {\n width: rect.width / scale,\n height: rect.height / scale\n }\n\n // Track shift key state and sync to canvas for snap preview\n const stopShiftSync = trackShiftKey(event)\n\n // Capture pointer to ensure we get all move/up events\n target.setPointerCapture(event.pointerId)\n\n // Mark as resizing to prevent drag from activating\n layoutStore.isResizingVueNodes.value = true\n isResizing.value = true\n resizeStartPointer.value = { x: event.clientX, y: event.clientY }\n resizeStartSize.value = startSize\n\n const handlePointerMove = (moveEvent: PointerEvent) => {\n if (\n !isResizing.value ||\n !resizeStartPointer.value ||\n !resizeStartSize.value\n ) {\n return\n }\n\n const scale = transformState.camera.z\n const deltaX =\n (moveEvent.clientX - resizeStartPointer.value.x) / (scale || 1)\n const deltaY =\n (moveEvent.clientY - resizeStartPointer.value.y) / (scale || 1)\n\n let newSize: Size = {\n width: resizeStartSize.value.width + deltaX,\n height: resizeStartSize.value.height + deltaY\n }\n\n // Apply snap if shift is held\n if (shouldSnap(moveEvent)) {\n newSize = applySnapToSize(newSize)\n }\n\n const nodeElement = target.closest('[data-node-id]')\n if (nodeElement instanceof HTMLElement) {\n resizeCallback({ size: newSize }, nodeElement)\n }\n }\n\n const handlePointerUp = (upEvent: PointerEvent) => {\n if (isResizing.value) {\n isResizing.value = false\n layoutStore.isResizingVueNodes.value = false\n resizeStartPointer.value = null\n resizeStartSize.value = null\n\n // Stop tracking shift key state\n stopShiftSync()\n\n target.releasePointerCapture(upEvent.pointerId)\n stopMoveListen()\n stopUpListen()\n }\n }\n\n const stopMoveListen = useEventListener('pointermove', handlePointerMove)\n const stopUpListen = useEventListener('pointerup', handlePointerUp)\n }\n\n return {\n startResize,\n isResizing\n }\n}\n","<template>\n <div v-if=\"imageUrl\" class=\"flex h-full min-h-16 w-full min-w-16 flex-col\">\n <!-- Image Container -->\n <div\n class=\"relative h-88 w-full grow overflow-hidden rounded-[5px] bg-node-component-surface\"\n >\n <!-- Error State -->\n <div\n v-if=\"imageError\"\n class=\"text-pure-white flex h-full w-full flex-col items-center justify-center text-center\"\n >\n <i-lucide:image-off class=\"mb-1 size-8 text-smoke-500\" />\n <p class=\"text-xs text-smoke-400\">{{ $t('g.imageFailedToLoad') }}</p>\n </div>\n\n <!-- Main Image -->\n <img\n v-else\n :src=\"imageUrl\"\n :alt=\"$t('g.liveSamplingPreview')\"\n class=\"pointer-events-none h-full w-full object-contain object-center\"\n @load=\"handleImageLoad\"\n @error=\"handleImageError\"\n />\n </div>\n\n <!-- Image Dimensions -->\n <div class=\"text-node-component-header-text mt-1 text-center text-xs\">\n {{\n imageError\n ? $t('g.errorLoadingImage')\n : actualDimensions || $t('g.calculatingDimensions')\n }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\n\ninterface LivePreviewProps {\n /** Image URL to display */\n imageUrl: string | null\n}\n\nconst props = defineProps<LivePreviewProps>()\n\nconst actualDimensions = ref<string | null>(null)\nconst imageError = ref(false)\n\nwatch(\n () => props.imageUrl,\n () => {\n // Reset states when URL changes\n actualDimensions.value = null\n imageError.value = false\n }\n)\n\nconst handleImageLoad = (event: Event) => {\n if (!event.target || !(event.target instanceof HTMLImageElement)) return\n const img = event.target\n imageError.value = false\n if (img.naturalWidth && img.naturalHeight) {\n actualDimensions.value = `${img.naturalWidth} x ${img.naturalHeight}`\n }\n}\n\nconst handleImageError = () => {\n imageError.value = true\n actualDimensions.value = null\n}\n</script>\n","<template>\n <div\n v-if=\"imageUrls.length > 0\"\n class=\"video-preview group relative flex size-full min-h-16 min-w-16 flex-col px-2\"\n @keydown=\"handleKeyDown\"\n >\n <!-- Video Wrapper -->\n <div\n ref=\"videoWrapperEl\"\n class=\"relative h-full w-full grow overflow-hidden rounded-[5px] bg-node-component-surface\"\n tabindex=\"0\"\n role=\"region\"\n :aria-label=\"$t('g.videoPreview')\"\n :aria-busy=\"showLoader\"\n @mouseenter=\"handleMouseEnter\"\n @mouseleave=\"handleMouseLeave\"\n @focusin=\"handleFocusIn\"\n @focusout=\"handleFocusOut\"\n >\n <!-- Error State -->\n <div\n v-if=\"videoError\"\n role=\"alert\"\n class=\"flex size-full flex-col items-center justify-center bg-muted-background text-center text-base-foreground py-8\"\n >\n <i\n class=\"mb-2 icon-[lucide--video-off] h-12 w-12 text-base-foreground\"\n />\n <p class=\"text-sm text-base-foreground\">\n {{ $t('g.videoFailedToLoad') }}\n </p>\n <p class=\"mt-1 text-xs text-base-foreground\">\n {{ getVideoFilename(currentVideoUrl) }}\n </p>\n </div>\n\n <!-- Loading State -->\n <Skeleton\n v-if=\"showLoader && !videoError\"\n class=\"absolute inset-0 size-full\"\n border-radius=\"5px\"\n width=\"100%\"\n height=\"100%\"\n />\n\n <!-- Main Video -->\n <video\n v-if=\"!videoError\"\n :src=\"currentVideoUrl\"\n :class=\"cn('block size-full object-contain', showLoader && 'invisible')\"\n controls\n loop\n playsinline\n @loadeddata=\"handleVideoLoad\"\n @error=\"handleVideoError\"\n />\n\n <!-- Floating Action Buttons (appear on hover) -->\n <div\n v-if=\"isHovered || isFocused\"\n class=\"actions absolute top-2 right-2 flex gap-2.5\"\n >\n <!-- Download Button -->\n <button\n :class=\"actionButtonClass\"\n :title=\"$t('g.downloadVideo')\"\n :aria-label=\"$t('g.downloadVideo')\"\n @click=\"handleDownload\"\n >\n <i class=\"icon-[lucide--download] h-4 w-4\" />\n </button>\n\n <!-- Close Button -->\n <button\n :class=\"actionButtonClass\"\n :title=\"$t('g.removeVideo')\"\n :aria-label=\"$t('g.removeVideo')\"\n @click=\"handleRemove\"\n >\n <i class=\"icon-[lucide--x] h-4 w-4\" />\n </button>\n </div>\n\n <!-- Multiple Videos Navigation -->\n <div\n v-if=\"hasMultipleVideos\"\n class=\"absolute right-2 bottom-2 left-2 flex justify-center gap-1\"\n >\n <button\n v-for=\"(_, index) in imageUrls\"\n :key=\"index\"\n :class=\"getNavigationDotClass(index)\"\n :aria-label=\"\n $t('g.viewVideoOfTotal', {\n index: index + 1,\n total: imageUrls.length\n })\n \"\n @click=\"setCurrentIndex(index)\"\n />\n </div>\n </div>\n\n <!-- Video Dimensions -->\n <div class=\"mt-2 text-center text-xs text-muted-foreground\">\n <span v-if=\"videoError\" class=\"text-red-400\">\n {{ $t('g.errorLoadingVideo') }}\n </span>\n <span v-else-if=\"showLoader\" class=\"text-smoke-400\">\n {{ $t('g.loading') }}...\n </span>\n <span v-else>\n {{ actualDimensions || $t('g.calculatingDimensions') }}\n </span>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useToast } from 'primevue'\nimport Skeleton from 'primevue/skeleton'\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { downloadFile } from '@/base/common/downloadUtil'\nimport { useNodeOutputStore } from '@/stores/imagePreviewStore'\nimport { cn } from '@/utils/tailwindUtil'\n\ninterface VideoPreviewProps {\n /** Array of video URLs to display */\n readonly imageUrls: readonly string[] // Named imageUrls for consistency with parent components\n /** Optional node ID for context-aware actions */\n readonly nodeId?: string\n}\n\nconst props = defineProps<VideoPreviewProps>()\n\nconst { t } = useI18n()\nconst nodeOutputStore = useNodeOutputStore()\n\nconst actionButtonClass =\n 'flex h-8 min-h-8 items-center justify-center gap-2.5 rounded-lg border-0 bg-button-surface px-2 py-2 text-button-surface-contrast shadow-sm transition-colors duration-200 hover:bg-button-hover-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-button-surface-contrast focus-visible:ring-offset-2 focus-visible:ring-offset-transparent cursor-pointer'\n\n// Component state\nconst currentIndex = ref(0)\nconst isHovered = ref(false)\nconst isFocused = ref(false)\nconst actualDimensions = ref<string | null>(null)\nconst videoError = ref(false)\nconst showLoader = ref(false)\n\nconst videoWrapperEl = ref<HTMLDivElement>()\n\n// Computed values\nconst currentVideoUrl = computed(() => props.imageUrls[currentIndex.value])\nconst hasMultipleVideos = computed(() => props.imageUrls.length > 1)\n\n// Watch for URL changes and reset state\nwatch(\n () => props.imageUrls,\n (newUrls) => {\n // Reset current index if it's out of bounds\n if (currentIndex.value >= newUrls.length) {\n currentIndex.value = 0\n }\n\n // Reset loading and error states when URLs change\n actualDimensions.value = null\n videoError.value = false\n showLoader.value = newUrls.length > 0\n },\n { deep: true, immediate: true }\n)\n\n// Event handlers\nconst handleVideoLoad = (event: Event) => {\n if (!event.target || !(event.target instanceof HTMLVideoElement)) return\n const video = event.target\n showLoader.value = false\n videoError.value = false\n if (video.videoWidth && video.videoHeight) {\n actualDimensions.value = `${video.videoWidth} x ${video.videoHeight}`\n }\n}\n\nconst handleVideoError = () => {\n showLoader.value = false\n videoError.value = true\n actualDimensions.value = null\n}\n\nconst handleDownload = () => {\n try {\n downloadFile(currentVideoUrl.value)\n } catch (error) {\n useToast().add({\n severity: 'error',\n summary: 'Error',\n detail: t('g.failedToDownloadVideo'),\n life: 3000,\n group: 'video-preview'\n })\n }\n}\n\nconst handleRemove = () => {\n if (!props.nodeId) return\n nodeOutputStore.removeNodeOutputs(props.nodeId)\n}\n\nconst setCurrentIndex = (index: number) => {\n if (index >= 0 && index < props.imageUrls.length) {\n currentIndex.value = index\n actualDimensions.value = null\n showLoader.value = true\n videoError.value = false\n }\n}\n\nconst handleMouseEnter = () => {\n isHovered.value = true\n}\n\nconst handleMouseLeave = () => {\n isHovered.value = false\n}\n\nconst handleFocusIn = () => {\n isFocused.value = true\n}\n\nconst handleFocusOut = (event: FocusEvent) => {\n if (!videoWrapperEl.value?.contains(event.relatedTarget as Node)) {\n isFocused.value = false\n }\n}\n\nconst getNavigationDotClass = (index: number) => {\n return [\n 'w-2 h-2 rounded-full transition-all duration-200 border-0 cursor-pointer',\n index === currentIndex.value ? 'bg-white' : 'bg-white/50 hover:bg-white/80'\n ]\n}\n\nconst handleKeyDown = (event: KeyboardEvent) => {\n if (props.imageUrls.length <= 1) return\n\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n setCurrentIndex(\n currentIndex.value > 0\n ? currentIndex.value - 1\n : props.imageUrls.length - 1\n )\n break\n case 'ArrowRight':\n event.preventDefault()\n setCurrentIndex(\n currentIndex.value < props.imageUrls.length - 1\n ? currentIndex.value + 1\n : 0\n )\n break\n case 'Home':\n event.preventDefault()\n setCurrentIndex(0)\n break\n case 'End':\n event.preventDefault()\n setCurrentIndex(props.imageUrls.length - 1)\n break\n }\n}\n\nconst getVideoFilename = (url: string): string => {\n try {\n return new URL(url).searchParams.get('filename') || 'Unknown file'\n } catch {\n return 'Invalid URL'\n }\n}\n</script>\n","<template>\n <div\n v-if=\"imageUrls.length > 0\"\n class=\"image-preview group relative flex size-full min-h-16 min-w-16 flex-col px-2 justify-center\"\n @keydown=\"handleKeyDown\"\n >\n <!-- Image Wrapper -->\n <div\n ref=\"imageWrapperEl\"\n class=\"h-full w-full overflow-hidden rounded-[5px] bg-muted-background relative\"\n tabindex=\"0\"\n role=\"img\"\n :aria-label=\"$t('g.imagePreview')\"\n :aria-busy=\"showLoader\"\n @mouseenter=\"handleMouseEnter\"\n @mouseleave=\"handleMouseLeave\"\n @focusin=\"handleFocusIn\"\n @focusout=\"handleFocusOut\"\n >\n <!-- Error State -->\n <div\n v-if=\"imageError\"\n role=\"alert\"\n class=\"flex size-full flex-col items-center justify-center bg-muted-background text-center text-base-foreground py-8\"\n >\n <i\n class=\"mb-2 icon-[lucide--image-off] h-12 w-12 text-base-foreground\"\n />\n <p class=\"text-sm text-base-foreground\">\n {{ $t('g.imageFailedToLoad') }}\n </p>\n <p class=\"mt-1 text-xs text-base-foreground\">\n {{ getImageFilename(currentImageUrl) }}\n </p>\n </div>\n <!-- Loading State -->\n <div v-if=\"showLoader && !imageError\" class=\"size-full\">\n <Skeleton border-radius=\"5px\" width=\"100%\" height=\"100%\" />\n </div>\n <!-- Main Image -->\n <img\n v-if=\"!imageError\"\n ref=\"currentImageEl\"\n :src=\"currentImageUrl\"\n :alt=\"imageAltText\"\n class=\"block size-full object-contain pointer-events-none\"\n @load=\"handleImageLoad\"\n @error=\"handleImageError\"\n />\n\n <!-- Floating Action Buttons (appear on hover and focus) -->\n <div\n v-if=\"isHovered || isFocused\"\n class=\"actions absolute top-2 right-2 flex gap-2.5\"\n >\n <!-- Mask/Edit Button -->\n <button\n v-if=\"!hasMultipleImages\"\n :class=\"actionButtonClass\"\n :title=\"$t('g.editOrMaskImage')\"\n :aria-label=\"$t('g.editOrMaskImage')\"\n @click=\"handleEditMask\"\n >\n <i-comfy:mask class=\"h-4 w-4\" />\n </button>\n\n <!-- Download Button -->\n <button\n :class=\"actionButtonClass\"\n :title=\"$t('g.downloadImage')\"\n :aria-label=\"$t('g.downloadImage')\"\n @click=\"handleDownload\"\n >\n <i class=\"icon-[lucide--download] h-4 w-4\" />\n </button>\n\n <!-- Close Button -->\n <button\n :class=\"actionButtonClass\"\n :title=\"$t('g.removeImage')\"\n :aria-label=\"$t('g.removeImage')\"\n @click=\"handleRemove\"\n >\n <i class=\"icon-[lucide--x] h-4 w-4\" />\n </button>\n </div>\n </div>\n\n <!-- Image Dimensions -->\n <div class=\"pt-2 text-center text-xs text-base-foreground\">\n <span v-if=\"imageError\" class=\"text-red-400\">\n {{ $t('g.errorLoadingImage') }}\n </span>\n <span v-else-if=\"showLoader\" class=\"text-base-foreground\">\n {{ $t('g.loading') }}...\n </span>\n <span v-else>\n {{ actualDimensions || $t('g.calculatingDimensions') }}\n </span>\n </div>\n <!-- Multiple Images Navigation -->\n <div\n v-if=\"hasMultipleImages\"\n class=\"flex flex-wrap justify-center gap-1 pt-4\"\n >\n <button\n v-for=\"(_, index) in imageUrls\"\n :key=\"index\"\n :class=\"getNavigationDotClass(index)\"\n :aria-current=\"index === currentIndex ? 'true' : undefined\"\n :aria-label=\"\n $t('g.viewImageOfTotal', {\n index: index + 1,\n total: imageUrls.length\n })\n \"\n @click=\"setCurrentIndex(index)\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { useTimeoutFn } from '@vueuse/core'\nimport { useToast } from 'primevue'\nimport Skeleton from 'primevue/skeleton'\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { downloadFile } from '@/base/common/downloadUtil'\nimport { app } from '@/scripts/app'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useNodeOutputStore } from '@/stores/imagePreviewStore'\n\ninterface ImagePreviewProps {\n /** Array of image URLs to display */\n readonly imageUrls: readonly string[]\n /** Optional node ID for context-aware actions */\n readonly nodeId?: string\n}\n\nconst props = defineProps<ImagePreviewProps>()\n\nconst { t } = useI18n()\nconst commandStore = useCommandStore()\nconst nodeOutputStore = useNodeOutputStore()\n\nconst actionButtonClass =\n 'flex h-8 min-h-8 items-center justify-center gap-2.5 rounded-lg border-0 bg-button-surface px-2 py-2 text-button-surface-contrast shadow-sm transition-colors duration-200 hover:bg-button-hover-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-button-surface-contrast focus-visible:ring-offset-2 focus-visible:ring-offset-transparent cursor-pointer'\n\n// Component state\nconst currentIndex = ref(0)\nconst isHovered = ref(false)\nconst isFocused = ref(false)\nconst actualDimensions = ref<string | null>(null)\nconst imageError = ref(false)\nconst showLoader = ref(false)\n\nconst currentImageEl = ref<HTMLImageElement>()\nconst imageWrapperEl = ref<HTMLDivElement>()\n\nconst { start: startDelayedLoader, stop: stopDelayedLoader } = useTimeoutFn(\n () => {\n showLoader.value = true\n },\n 250,\n // Make sure it doesnt run on component mount\n { immediate: false }\n)\n\n// Computed values\nconst currentImageUrl = computed(() => props.imageUrls[currentIndex.value])\nconst hasMultipleImages = computed(() => props.imageUrls.length > 1)\nconst imageAltText = computed(() => `Node output ${currentIndex.value + 1}`)\n\n// Watch for URL changes and reset state\nwatch(\n () => props.imageUrls,\n (newUrls) => {\n // Reset current index if it's out of bounds\n if (currentIndex.value >= newUrls.length) {\n currentIndex.value = 0\n }\n\n // Reset loading and error states when URLs change\n actualDimensions.value = null\n\n imageError.value = false\n if (newUrls.length > 0) startDelayedLoader()\n },\n { deep: true, immediate: true }\n)\n\n// Event handlers\nconst handleImageLoad = (event: Event) => {\n if (!event.target || !(event.target instanceof HTMLImageElement)) return\n const img = event.target\n stopDelayedLoader()\n showLoader.value = false\n imageError.value = false\n if (img.naturalWidth && img.naturalHeight) {\n actualDimensions.value = `${img.naturalWidth} x ${img.naturalHeight}`\n }\n}\n\nconst handleImageError = () => {\n stopDelayedLoader()\n showLoader.value = false\n imageError.value = true\n actualDimensions.value = null\n}\n\n// In vueNodes mode, we need to set them manually before opening the mask editor.\nconst setupNodeForMaskEditor = () => {\n if (!props.nodeId || !currentImageEl.value) return\n const node = app.rootGraph?.getNodeById(props.nodeId)\n if (!node) return\n node.imageIndex = currentIndex.value\n node.imgs = [currentImageEl.value]\n app.canvas?.select(node)\n}\n\nconst handleEditMask = () => {\n setupNodeForMaskEditor()\n void commandStore.execute('Comfy.MaskEditor.OpenMaskEditor')\n}\n\nconst handleDownload = () => {\n try {\n downloadFile(currentImageUrl.value)\n } catch (error) {\n useToast().add({\n severity: 'error',\n summary: 'Error',\n detail: t('g.failedToDownloadImage'),\n life: 3000,\n group: 'image-preview'\n })\n }\n}\n\nconst handleRemove = () => {\n if (!props.nodeId) return\n nodeOutputStore.removeNodeOutputs(props.nodeId)\n}\n\nconst setCurrentIndex = (index: number) => {\n if (currentIndex.value === index) return\n if (index >= 0 && index < props.imageUrls.length) {\n currentIndex.value = index\n startDelayedLoader()\n imageError.value = false\n }\n}\n\nconst handleMouseEnter = () => {\n isHovered.value = true\n}\n\nconst handleMouseLeave = () => {\n isHovered.value = false\n}\n\nconst handleFocusIn = () => {\n isFocused.value = true\n}\n\nconst handleFocusOut = (event: FocusEvent) => {\n // Only unfocus if focus is leaving the wrapper entirely\n if (!imageWrapperEl.value?.contains(event.relatedTarget as Node)) {\n isFocused.value = false\n }\n}\n\nconst getNavigationDotClass = (index: number) => {\n return [\n 'w-2 h-2 rounded-full transition-all duration-200 border-0 cursor-pointer p-0',\n index === currentIndex.value\n ? 'bg-base-foreground'\n : 'bg-base-foreground/50 hover:bg-base-foreground/80'\n ]\n}\n\nconst handleKeyDown = (event: KeyboardEvent) => {\n if (props.imageUrls.length <= 1) return\n\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n setCurrentIndex(\n currentIndex.value > 0\n ? currentIndex.value - 1\n : props.imageUrls.length - 1\n )\n break\n case 'ArrowRight':\n event.preventDefault()\n setCurrentIndex(\n currentIndex.value < props.imageUrls.length - 1\n ? currentIndex.value + 1\n : 0\n )\n break\n case 'Home':\n event.preventDefault()\n setCurrentIndex(0)\n break\n case 'End':\n event.preventDefault()\n setCurrentIndex(props.imageUrls.length - 1)\n break\n }\n}\n\nconst getImageFilename = (url: string): string => {\n try {\n return new URL(url).searchParams.get('filename') || 'Unknown file'\n } catch {\n return 'Invalid URL'\n }\n}\n</script>\n","<template>\n <div v-if=\"renderError\" class=\"node-error p-2 text-sm text-red-500\">\n {{ st('nodeErrors.content', 'Node Content Error') }}\n </div>\n <div v-else class=\"lg-node-content flex grow flex-col\">\n <!-- Default slot for custom content -->\n <slot>\n <VideoPreview\n v-if=\"hasMedia && media?.type === 'video'\"\n :image-urls=\"media.urls\"\n :node-id=\"nodeId\"\n class=\"mt-2\"\n />\n <ImagePreview\n v-else-if=\"hasMedia && media?.type === 'image'\"\n :image-urls=\"media.urls\"\n :node-id=\"nodeId\"\n class=\"mt-2\"\n />\n </slot>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onErrorCaptured, ref } from 'vue'\n\nimport type { VueNodeData } from '@/composables/graph/useGraphNodeManager'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { st } from '@/i18n'\n\nimport VideoPreview from '../VideoPreview.vue'\nimport ImagePreview from './ImagePreview.vue'\n\ninterface NodeContentProps {\n nodeData?: VueNodeData\n media?: {\n type: 'image' | 'video'\n urls: string[]\n }\n}\n\nconst props = defineProps<NodeContentProps>()\n\nconst hasMedia = computed(() => props.media && props.media.urls.length > 0)\n\n// Get node ID from nodeData\nconst nodeId = computed(() => props.nodeData?.id?.toString())\n\n// Error boundary implementation\nconst renderError = ref<string | null>(null)\nconst { toastErrorHandler } = useErrorHandling()\n\nonErrorCaptured((error) => {\n renderError.value = error.message\n toastErrorHandler(error)\n return false\n})\n</script>\n","<template>\n <div v-if=\"renderError\" class=\"node-error p-2 text-sm text-red-500\">\n {{ st('nodeErrors.render', 'Node Render Error') }}\n </div>\n <div\n v-else\n ref=\"nodeContainerRef\"\n tabindex=\"0\"\n :data-node-id=\"nodeData.id\"\n :class=\"\n cn(\n 'bg-component-node-background lg-node absolute text-sm',\n 'contain-style contain-layout min-w-[225px] min-h-(--node-height) w-(--node-width)',\n shapeClass,\n 'touch-none flex flex-col',\n 'border-1 border-solid border-component-node-border',\n // hover (only when node should handle events)\n shouldHandleNodePointerEvents &&\n 'hover:ring-7 ring-node-component-ring',\n 'outline-transparent outline-2 focus-visible:outline-node-component-outline',\n borderClass,\n outlineClass,\n cursorClass,\n {\n [`${beforeShapeClass} before:pointer-events-none before:absolute before:bg-bypass/60 before:inset-0`]:\n bypassed,\n [`${beforeShapeClass} before:pointer-events-none before:absolute before:inset-0`]:\n muted,\n 'ring-4 ring-primary-500 bg-primary-500/10': isDraggingOver\n },\n\n shouldHandleNodePointerEvents\n ? 'pointer-events-auto'\n : 'pointer-events-none',\n !isCollapsed && ' pb-1'\n )\n \"\n :style=\"[\n {\n transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`,\n zIndex: zIndex,\n opacity: nodeOpacity,\n '--component-node-background': nodeBodyBackgroundColor\n }\n ]\"\n v-bind=\"remainingPointerHandlers\"\n @pointerdown=\"nodeOnPointerdown\"\n @wheel=\"handleWheel\"\n @contextmenu=\"handleContextMenu\"\n @dragover.prevent=\"handleDragOver\"\n @dragleave=\"handleDragLeave\"\n @drop.stop.prevent=\"handleDrop\"\n >\n <div\n v-if=\"displayHeader\"\n class=\"flex flex-col justify-center items-center relative\"\n >\n <template v-if=\"isCollapsed\">\n <SlotConnectionDot\n v-if=\"hasInputs\"\n multi\n class=\"absolute left-0 -translate-x-1/2\"\n />\n <SlotConnectionDot\n v-if=\"hasOutputs\"\n multi\n class=\"absolute right-0 translate-x-1/2\"\n />\n <NodeSlots :node-data=\"nodeData\" unified />\n </template>\n <NodeHeader\n :node-data=\"nodeData\"\n :collapsed=\"isCollapsed\"\n @collapse=\"handleCollapse\"\n @update:title=\"handleHeaderTitleUpdate\"\n @enter-subgraph=\"handleEnterSubgraph\"\n />\n </div>\n\n <div\n v-if=\"isCollapsed && executing && progress !== undefined\"\n :class=\"\n cn(\n 'absolute inset-x-4 -bottom-[1px] translate-y-1/2 rounded-full',\n progressClasses\n )\n \"\n :style=\"{ width: `${Math.min(progress * 100, 100)}%` }\"\n />\n\n <template v-if=\"!isCollapsed\">\n <div class=\"relative mb-1\">\n <!-- Progress bar for executing state -->\n <div\n v-if=\"executing && progress !== undefined\"\n :class=\"\n cn(\n 'absolute inset-x-0 top-1/2 -translate-y-1/2',\n !!(progress < 1) && 'rounded-r-full',\n progressClasses\n )\n \"\n :style=\"{ width: `${Math.min(progress * 100, 100)}%` }\"\n />\n </div>\n\n <div\n class=\"flex flex-1 flex-col gap-1 pb-2\"\n :data-testid=\"`node-body-${nodeData.id}`\"\n >\n <NodeSlots :node-data=\"nodeData\" />\n\n <NodeWidgets v-if=\"nodeData.widgets?.length\" :node-data=\"nodeData\" />\n\n <div v-if=\"hasCustomContent\" class=\"min-h-0 flex-1 flex\">\n <NodeContent :node-data=\"nodeData\" :media=\"nodeMedia\" />\n </div>\n <!-- Live mid-execution preview images -->\n <div v-if=\"shouldShowPreviewImg\" class=\"min-h-0 flex-1 px-4\">\n <LivePreview :image-url=\"latestPreviewUrl || null\" />\n </div>\n </div>\n </template>\n\n <!-- Resize handle (bottom-right only) -->\n <div\n v-if=\"!isCollapsed && nodeData.resizable !== false\"\n role=\"button\"\n :aria-label=\"t('g.resizeFromBottomRight')\"\n :class=\"cn(baseResizeHandleClasses, 'right-0 bottom-0 cursor-se-resize')\"\n @pointerdown.stop=\"handleResizePointerDown\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { storeToRefs } from 'pinia'\nimport { computed, nextTick, onErrorCaptured, onMounted, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport type { VueNodeData } from '@/composables/graph/useGraphNodeManager'\nimport { showNodeOptions } from '@/composables/graph/useMoreOptionsMenu'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { st } from '@/i18n'\nimport {\n LGraphCanvas,\n LGraphEventMode,\n LiteGraph,\n RenderShape\n} from '@/lib/litegraph/src/litegraph'\nimport { TitleMode } from '@/lib/litegraph/src/types/globalEnums'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'\nimport { layoutStore } from '@/renderer/core/layout/store/layoutStore'\nimport SlotConnectionDot from '@/renderer/extensions/vueNodes/components/SlotConnectionDot.vue'\nimport { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'\nimport { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'\nimport { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'\nimport { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'\nimport { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'\nimport { useNodeDrag } from '@/renderer/extensions/vueNodes/layout/useNodeDrag'\nimport { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'\nimport { useNodePreviewState } from '@/renderer/extensions/vueNodes/preview/useNodePreviewState'\nimport { nonWidgetedInputs } from '@/renderer/extensions/vueNodes/utils/nodeDataUtils'\nimport { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils'\nimport { app } from '@/scripts/app'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useNodeOutputStore } from '@/stores/imagePreviewStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { isTransparent } from '@/utils/colorUtil'\nimport {\n getLocatorIdFromNodeData,\n getNodeByLocatorId\n} from '@/utils/graphTraversalUtil'\nimport { cn } from '@/utils/tailwindUtil'\n\nimport { useNodeResize } from '../interactions/resize/useNodeResize'\nimport LivePreview from './LivePreview.vue'\nimport NodeContent from './NodeContent.vue'\nimport NodeHeader from './NodeHeader.vue'\nimport NodeSlots from './NodeSlots.vue'\nimport NodeWidgets from './NodeWidgets.vue'\n\n// Extended props for main node component\ninterface LGraphNodeProps {\n nodeData: VueNodeData\n error?: string | null\n}\n\nconst { nodeData, error = null } = defineProps<LGraphNodeProps>()\n\nconst { t } = useI18n()\n\nconst { handleNodeCollapse, handleNodeTitleUpdate, handleNodeRightClick } =\n useNodeEventHandlers()\nconst { bringNodeToFront } = useNodeZIndex()\n\nuseVueElementTracking(() => nodeData.id, 'node')\n\nconst { selectedNodeIds } = storeToRefs(useCanvasStore())\nconst isSelected = computed(() => {\n return selectedNodeIds.value.has(nodeData.id)\n})\n\nconst nodeLocatorId = computed(() => getLocatorIdFromNodeData(nodeData))\nconst { executing, progress } = useNodeExecutionState(nodeLocatorId)\nconst executionStore = useExecutionStore()\nconst hasExecutionError = computed(\n () => executionStore.lastExecutionErrorNodeId === nodeData.id\n)\n\nconst hasAnyError = computed((): boolean => {\n return !!(\n hasExecutionError.value ||\n nodeData.hasErrors ||\n error ||\n (executionStore.lastNodeErrors?.[nodeData.id]?.errors.length ?? 0) > 0\n )\n})\n\nconst displayHeader = computed(() => nodeData.titleMode !== TitleMode.NO_TITLE)\n\nconst isCollapsed = computed(() => nodeData.flags?.collapsed ?? false)\nconst bypassed = computed(\n (): boolean => nodeData.mode === LGraphEventMode.BYPASS\n)\nconst muted = computed((): boolean => nodeData.mode === LGraphEventMode.NEVER)\n\nconst nodeBodyBackgroundColor = computed(() => {\n const colorPaletteStore = useColorPaletteStore()\n\n if (!nodeData.bgcolor) {\n return ''\n }\n\n return applyLightThemeColor(\n nodeData.bgcolor,\n Boolean(colorPaletteStore.completedActivePalette.light_theme)\n )\n})\n\nconst nodeOpacity = computed(() => {\n const globalOpacity = useSettingStore().get('Comfy.Node.Opacity') ?? 1\n\n // For muted/bypassed nodes, apply the 0.5 multiplier on top of global opacity\n if (bypassed.value || muted.value) {\n return globalOpacity * 0.5\n }\n\n return globalOpacity\n})\n\nconst hasInputs = computed(() => nonWidgetedInputs(nodeData).length > 0)\nconst hasOutputs = computed((): boolean => !!nodeData.outputs?.length)\n\n// Use canvas interactions for proper wheel event handling and pointer event capture control\nconst { handleWheel, shouldHandleNodePointerEvents } = useCanvasInteractions()\n\n// Error boundary implementation\nconst renderError = ref<string | null>(null)\nconst { toastErrorHandler } = useErrorHandling()\n\nonErrorCaptured((error) => {\n renderError.value = error.message\n toastErrorHandler(error)\n return false // Prevent error propagation\n})\n\nconst { position, size, zIndex } = useNodeLayout(() => nodeData.id)\nconst { pointerHandlers } = useNodePointerInteractions(() => nodeData.id)\nconst { onPointerdown, ...remainingPointerHandlers } = pointerHandlers\nconst { startDrag } = useNodeDrag()\n\nasync function nodeOnPointerdown(event: PointerEvent) {\n if (event.altKey && lgraphNode.value) {\n const result = LGraphCanvas.cloneNodes([lgraphNode.value])\n if (result?.created?.length) {\n const [newNode] = result.created\n startDrag(event, `${newNode.id}`)\n layoutStore.isDraggingVueNodes.value = true\n await nextTick()\n bringNodeToFront(`${newNode.id}`)\n return\n }\n }\n onPointerdown(event)\n}\n\n// Handle right-click context menu\nconst handleContextMenu = (event: MouseEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n // First handle the standard right-click behavior (selection)\n handleNodeRightClick(event as PointerEvent, nodeData.id)\n\n // Show the node options menu at the cursor position\n showNodeOptions(event)\n}\n\nonMounted(() => {\n initSizeStyles()\n})\n\n/**\n * Set initial DOM size from layout store, but respect intrinsic content minimum.\n * Important: nodes can mount in a collapsed state, and the collapse watcher won't\n * run initially. Match the collapsed runtime behavior by writing to the correct\n * CSS variables on mount.\n */\nfunction initSizeStyles() {\n const el = nodeContainerRef.value\n const { width, height } = size.value\n if (!el) return\n\n const suffix = isCollapsed.value ? '-x' : ''\n\n el.style.setProperty(`--node-width${suffix}`, `${width}px`)\n el.style.setProperty(`--node-height${suffix}`, `${height}px`)\n}\n\nconst baseResizeHandleClasses =\n 'absolute h-3 w-3 opacity-0 pointer-events-auto focus-visible:outline focus-visible:outline-2 focus-visible:outline-white/40'\n\nconst MIN_NODE_WIDTH = 225\n\nconst { startResize } = useNodeResize((result, element) => {\n if (isCollapsed.value) return\n\n // Clamp width to minimum to avoid conflicts with CSS min-width\n const clampedWidth = Math.max(result.size.width, MIN_NODE_WIDTH)\n\n // Apply size directly to DOM element - ResizeObserver will pick this up\n element.style.setProperty('--node-width', `${clampedWidth}px`)\n element.style.setProperty('--node-height', `${result.size.height}px`)\n})\n\nconst handleResizePointerDown = (event: PointerEvent) => {\n if (event.button !== 0) return\n if (!shouldHandleNodePointerEvents.value) return\n if (nodeData.flags?.pinned) return\n if (nodeData.resizable === false) return\n startResize(event)\n}\n\nwatch(isCollapsed, (collapsed) => {\n const element = nodeContainerRef.value\n if (!element) return\n const [from, to] = collapsed ? ['', '-x'] : ['-x', '']\n const currentWidth = element.style.getPropertyValue(`--node-width${from}`)\n element.style.setProperty(`--node-width${to}`, currentWidth)\n element.style.setProperty(`--node-width${from}`, '')\n\n const currentHeight = element.style.getPropertyValue(`--node-height${from}`)\n element.style.setProperty(`--node-height${to}`, currentHeight)\n element.style.setProperty(`--node-height${from}`, '')\n})\n\n// Check if node has custom content (like image/video outputs)\nconst hasCustomContent = computed(() => {\n // Show custom content if node has media outputs\n return !!nodeMedia.value && nodeMedia.value.urls.length > 0\n})\n\n// Computed classes and conditions for better reusability\nconst progressClasses = 'h-2 bg-primary-500 transition-all duration-300'\n\nconst { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(\n () => nodeData.id,\n {\n isCollapsed\n }\n)\n\nconst borderClass = computed(() => {\n if (hasAnyError.value) return 'border-node-stroke-error'\n //FIXME need a better way to detecting transparency\n if (\n !displayHeader.value &&\n nodeData.bgcolor &&\n isTransparent(nodeData.bgcolor)\n )\n return 'border-0'\n return ''\n})\n\nconst outlineClass = computed(() => {\n return cn(\n isSelected.value && 'outline-node-component-outline',\n hasAnyError.value && 'outline-node-stroke-error',\n executing.value && 'outline-node-stroke-executing'\n )\n})\n\nconst cursorClass = computed(() => {\n return cn(\n nodeData.flags?.pinned\n ? 'cursor-default'\n : layoutStore.isDraggingVueNodes.value\n ? 'cursor-grabbing'\n : 'cursor-grab'\n )\n})\n\nconst shapeClass = computed(() => {\n switch (nodeData.shape) {\n case RenderShape.BOX:\n return 'rounded-none'\n case RenderShape.CARD:\n return 'rounded-tl-2xl rounded-br-2xl rounded-tr-none rounded-bl-none'\n default:\n return 'rounded-2xl'\n }\n})\n\nconst beforeShapeClass = computed(() => {\n switch (nodeData.shape) {\n case RenderShape.BOX:\n return 'before:rounded-none'\n case RenderShape.CARD:\n return 'before:rounded-tl-2xl before:rounded-br-2xl before:rounded-tr-none before:rounded-bl-none'\n default:\n return 'before:rounded-2xl'\n }\n})\n\n// Event handlers\nconst handleCollapse = () => {\n handleNodeCollapse(nodeData.id, !isCollapsed.value)\n}\n\nconst handleHeaderTitleUpdate = (newTitle: string) => {\n handleNodeTitleUpdate(nodeData.id, newTitle)\n}\n\nconst handleEnterSubgraph = () => {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'graph_node_open_subgraph_clicked'\n })\n const graph = app.rootGraph\n if (!graph) {\n console.warn('LGraphNode: No graph available for subgraph navigation')\n return\n }\n\n const locatorId = getLocatorIdFromNodeData(nodeData)\n\n const litegraphNode = getNodeByLocatorId(graph, locatorId)\n\n if (!litegraphNode?.isSubgraphNode() || !('subgraph' in litegraphNode)) {\n console.warn('LGraphNode: Node is not a valid subgraph node', litegraphNode)\n return\n }\n\n const canvas = app.canvas\n if (!canvas || typeof canvas.openSubgraph !== 'function') {\n console.warn('LGraphNode: Canvas or openSubgraph method not available')\n return\n }\n\n canvas.openSubgraph(litegraphNode.subgraph, litegraphNode)\n}\n\nconst nodeOutputs = useNodeOutputStore()\n\nconst nodeOutputLocatorId = computed(() =>\n nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id\n)\n\nconst lgraphNode = computed(() => {\n const locatorId = getLocatorIdFromNodeData(nodeData)\n return getNodeByLocatorId(app.rootGraph, locatorId)\n})\n\nconst nodeMedia = computed(() => {\n const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value]\n const node = lgraphNode.value\n\n if (!node || !newOutputs?.images?.length) return undefined\n\n const urls = nodeOutputs.getNodeImageUrls(node)\n if (!urls?.length) return undefined\n\n // Determine media type from previewMediaType or fallback to input slot types\n // Note: Despite the field name \"images\", videos are also included in outputs\n // TODO: fix the backend to return videos using the videos key instead of the images key\n const hasVideoInput = node.inputs?.some((input) => input.type === 'VIDEO')\n const type =\n node.previewMediaType === 'video' ||\n (!node.previewMediaType && hasVideoInput)\n ? 'video'\n : 'image'\n\n return { type, urls } as const\n})\n\nconst nodeContainerRef = ref<HTMLDivElement>()\n\n// Drag and drop support\nconst isDraggingOver = ref(false)\n\nfunction handleDragOver(event: DragEvent) {\n const node = lgraphNode.value\n if (!node || !node.onDragOver) {\n isDraggingOver.value = false\n return\n }\n\n // Call the litegraph node's onDragOver callback to check if files are valid\n const canDrop = node.onDragOver(event)\n isDraggingOver.value = canDrop\n}\n\nfunction handleDragLeave() {\n isDraggingOver.value = false\n}\n\nasync function handleDrop(event: DragEvent) {\n isDraggingOver.value = false\n\n const node = lgraphNode.value\n if (!node || !node.onDragDrop) {\n return\n }\n\n // Forward the drop event to the litegraph node's onDragDrop callback\n await node.onDragDrop(event)\n}\n</script>\n","import type { useSettingStore } from '@/platform/settings/settingStore'\n\nlet pendingCallbacks: Array<() => Promise<void>> = []\nlet isNewUserDetermined = false\nlet isNewUserCached: boolean | null = null\n\nexport const newUserService = () => {\n function checkIsNewUser(\n settingStore: ReturnType<typeof useSettingStore>\n ): boolean {\n const isNewUserSettings =\n Object.keys(settingStore.settingValues).length === 0 ||\n !settingStore.get('Comfy.TutorialCompleted')\n const hasNoWorkflow = !localStorage.getItem('workflow')\n const hasNoPreviousWorkflow = !localStorage.getItem(\n 'Comfy.PreviousWorkflow'\n )\n\n return isNewUserSettings && hasNoWorkflow && hasNoPreviousWorkflow\n }\n\n async function registerInitCallback(callback: () => Promise<void>) {\n if (isNewUserDetermined) {\n if (isNewUserCached) {\n try {\n await callback()\n } catch (error) {\n console.error('New user initialization callback failed:', error)\n }\n }\n } else {\n pendingCallbacks.push(callback)\n }\n }\n\n async function initializeIfNewUser(\n settingStore: ReturnType<typeof useSettingStore>\n ) {\n if (isNewUserDetermined) return\n\n isNewUserCached = checkIsNewUser(settingStore)\n isNewUserDetermined = true\n\n if (!isNewUserCached) {\n pendingCallbacks = []\n return\n }\n\n await settingStore.set(\n 'Comfy.InstalledVersion',\n __COMFYUI_FRONTEND_VERSION__\n )\n\n for (const callback of pendingCallbacks) {\n try {\n await callback()\n } catch (error) {\n console.error('New user initialization callback failed:', error)\n }\n }\n\n pendingCallbacks = []\n }\n\n function isNewUser(): boolean | null {\n return isNewUserDetermined ? isNewUserCached : null\n }\n\n return {\n registerInitCallback,\n initializeIfNewUser,\n isNewUser\n }\n}\n","<template>\n <div\n v-if=\"isVisible\"\n class=\"pointer-events-none absolute z-9999 border border-blue-400 bg-blue-500/20\"\n :style=\"rectangleStyle\"\n />\n</template>\n\n<script setup lang=\"ts\">\nimport { useRafFn } from '@vueuse/core'\nimport { computed, ref } from 'vue'\n\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\n\nconst canvasStore = useCanvasStore()\n\nconst selectionRect = ref<{\n x: number\n y: number\n w: number\n h: number\n} | null>(null)\n\nuseRafFn(() => {\n const canvas = canvasStore.canvas\n if (!canvas) {\n selectionRect.value = null\n return\n }\n\n const { pointer, dragging_rectangle } = canvas\n\n if (dragging_rectangle && pointer.eDown && pointer.eMove) {\n const x = pointer.eDown.safeOffsetX\n const y = pointer.eDown.safeOffsetY\n const w = pointer.eMove.safeOffsetX - x\n const h = pointer.eMove.safeOffsetY - y\n\n selectionRect.value = { x, y, w, h }\n } else {\n selectionRect.value = null\n }\n})\n\nconst isVisible = computed(() => selectionRect.value !== null)\n\nconst rectangleStyle = computed(() => {\n const rect = selectionRect.value\n if (!rect) return {}\n\n const left = rect.w >= 0 ? rect.x : rect.x + rect.w\n const top = rect.h >= 0 ? rect.y : rect.y + rect.h\n const width = Math.abs(rect.w)\n const height = Math.abs(rect.h)\n\n return {\n left: `${left}px`,\n top: `${top}px`,\n width: `${width}px`,\n height: `${height}px`\n }\n})\n</script>\n","<template>\n <!-- Load splitter overlay only after comfyApp is ready. -->\n <!-- If load immediately, the top-level splitter stateKey won't be correctly\n synced with the stateStorage (localStorage). -->\n <LiteGraphCanvasSplitterOverlay v-if=\"comfyAppReady\">\n <template v-if=\"showUI\" #workflow-tabs>\n <div\n v-if=\"workflowTabsPosition === 'Topbar'\"\n class=\"workflow-tabs-container pointer-events-auto relative h-9.5 w-full\"\n >\n <!-- Native drag area for Electron -->\n <div\n v-if=\"isNativeWindow() && workflowTabsPosition !== 'Topbar'\"\n class=\"app-drag fixed top-0 left-0 z-10 h-[var(--comfy-topbar-height)] w-full\"\n />\n <div\n class=\"flex h-full items-center border-b border-interface-stroke bg-comfy-menu-bg shadow-interface\"\n >\n <WorkflowTabs />\n <TopbarBadges />\n </div>\n </div>\n </template>\n <template v-if=\"showUI\" #side-toolbar>\n <SideToolbar />\n </template>\n <template v-if=\"showUI\" #side-bar-panel>\n <div\n class=\"sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto\"\n >\n <ExtensionSlot v-if=\"activeSidebarTab\" :extension=\"activeSidebarTab\" />\n </div>\n </template>\n <template v-if=\"showUI\" #topmenu>\n <TopMenuSection />\n </template>\n <template v-if=\"showUI\" #bottom-panel>\n <BottomPanel />\n </template>\n <template v-if=\"showUI\" #right-side-panel>\n <NodePropertiesPanel />\n </template>\n <template #graph-canvas-panel>\n <GraphCanvasMenu v-if=\"canvasMenuEnabled\" class=\"pointer-events-auto\" />\n <MiniMap\n v-if=\"comfyAppReady && minimapEnabled && betaMenuEnabled\"\n class=\"pointer-events-auto\"\n />\n </template>\n </LiteGraphCanvasSplitterOverlay>\n <canvas\n id=\"graph-canvas\"\n ref=\"canvasRef\"\n tabindex=\"1\"\n class=\"absolute inset-0 size-full touch-none\"\n />\n\n <!-- TransformPane for Vue node rendering -->\n <TransformPane\n v-if=\"shouldRenderVueNodes && comfyApp.canvas && comfyAppReady\"\n :canvas=\"comfyApp.canvas\"\n @wheel.capture=\"canvasInteractions.forwardEventToCanvas\"\n >\n <!-- Vue nodes rendered based on graph nodes -->\n <LGraphNode\n v-for=\"nodeData in allNodes\"\n :key=\"nodeData.id\"\n :node-data=\"nodeData\"\n :error=\"\n executionStore.lastExecutionError?.node_id === nodeData.id\n ? 'Execution error'\n : null\n \"\n :zoom-level=\"canvasStore.canvas?.ds?.scale || 1\"\n :data-node-id=\"nodeData.id\"\n />\n </TransformPane>\n\n <!-- Selection rectangle overlay - rendered in DOM layer to appear above DOM widgets -->\n <SelectionRectangle v-if=\"comfyAppReady\" />\n\n <NodeTooltip v-if=\"tooltipEnabled\" />\n <NodeSearchboxPopover ref=\"nodeSearchboxPopoverRef\" />\n\n <!-- Initialize components after comfyApp is ready. useAbsolutePosition requires\n canvasStore.canvas to be initialized. -->\n <template v-if=\"comfyAppReady\">\n <TitleEditor />\n <SelectionToolbox v-if=\"selectionToolboxEnabled\" />\n <!-- Render legacy DOM widgets only when Vue nodes are disabled -->\n <DomWidgets v-if=\"!shouldRenderVueNodes\" />\n </template>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener, whenever } from '@vueuse/core'\nimport {\n computed,\n nextTick,\n onMounted,\n onUnmounted,\n ref,\n shallowRef,\n watch,\n watchEffect\n} from 'vue'\n\nimport LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'\nimport TopMenuSection from '@/components/TopMenuSection.vue'\nimport BottomPanel from '@/components/bottomPanel/BottomPanel.vue'\nimport ExtensionSlot from '@/components/common/ExtensionSlot.vue'\nimport DomWidgets from '@/components/graph/DomWidgets.vue'\nimport GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue'\nimport NodeTooltip from '@/components/graph/NodeTooltip.vue'\nimport SelectionToolbox from '@/components/graph/SelectionToolbox.vue'\nimport TitleEditor from '@/components/graph/TitleEditor.vue'\nimport NodePropertiesPanel from '@/components/rightSidePanel/RightSidePanel.vue'\nimport NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'\nimport SideToolbar from '@/components/sidebar/SideToolbar.vue'\nimport TopbarBadges from '@/components/topbar/TopbarBadges.vue'\nimport WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'\nimport { useChainCallback } from '@/composables/functional/useChainCallback'\nimport type { VueNodeData } from '@/composables/graph/useGraphNodeManager'\nimport { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'\nimport { useNodeBadge } from '@/composables/node/useNodeBadge'\nimport { useCanvasDrop } from '@/composables/useCanvasDrop'\nimport { useContextMenuTranslation } from '@/composables/useContextMenuTranslation'\nimport { useCopy } from '@/composables/useCopy'\nimport { useGlobalLitegraph } from '@/composables/useGlobalLitegraph'\nimport { usePaste } from '@/composables/usePaste'\nimport { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'\nimport { mergeCustomNodesI18n, t } from '@/i18n'\nimport { LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport { useLitegraphSettings } from '@/platform/settings/composables/useLitegraphSettings'\nimport { CORE_SETTINGS } from '@/platform/settings/constants/coreSettings'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useToastStore } from '@/platform/updates/common/toastStore'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useWorkflowAutoSave } from '@/platform/workflow/persistence/composables/useWorkflowAutoSave'\nimport { useWorkflowPersistence } from '@/platform/workflow/persistence/composables/useWorkflowPersistence'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'\nimport TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'\nimport MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'\nimport LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'\nimport { UnauthorizedError, api } from '@/scripts/api'\nimport { app as comfyApp } from '@/scripts/app'\nimport { ChangeTracker } from '@/scripts/changeTracker'\nimport { IS_CONTROL_WIDGET, updateControlWidgetLabel } from '@/scripts/widgets'\nimport { useColorPaletteService } from '@/services/colorPaletteService'\nimport { newUserService } from '@/services/newUserService'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useNodeDefStore } from '@/stores/nodeDefStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\nimport { isNativeWindow } from '@/utils/envUtil'\nimport { forEachNode } from '@/utils/graphTraversalUtil'\n\nimport SelectionRectangle from './SelectionRectangle.vue'\n\nconst emit = defineEmits<{\n ready: []\n}>()\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\nconst nodeSearchboxPopoverRef = shallowRef<InstanceType<\n typeof NodeSearchboxPopover\n> | null>(null)\nconst settingStore = useSettingStore()\nconst nodeDefStore = useNodeDefStore()\nconst workspaceStore = useWorkspaceStore()\nconst canvasStore = useCanvasStore()\nconst executionStore = useExecutionStore()\nconst toastStore = useToastStore()\nconst colorPaletteStore = useColorPaletteStore()\nconst colorPaletteService = useColorPaletteService()\nconst canvasInteractions = useCanvasInteractions()\n\nconst betaMenuEnabled = computed(\n () => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'\n)\nconst workflowTabsPosition = computed(() =>\n settingStore.get('Comfy.Workflow.WorkflowTabsPosition')\n)\nconst canvasMenuEnabled = computed(() =>\n settingStore.get('Comfy.Graph.CanvasMenu')\n)\nconst tooltipEnabled = computed(() => settingStore.get('Comfy.EnableTooltips'))\nconst selectionToolboxEnabled = computed(() =>\n settingStore.get('Comfy.Canvas.SelectionToolbox')\n)\nconst activeSidebarTab = computed(() => {\n return workspaceStore.sidebarTab.activeSidebarTab\n})\nconst showUI = computed(\n () => !workspaceStore.focusMode && betaMenuEnabled.value\n)\n\nconst minimapEnabled = computed(() => settingStore.get('Comfy.Minimap.Visible'))\n\n// Feature flags\nconst { shouldRenderVueNodes } = useVueFeatureFlags()\n\n// Vue node system\nconst vueNodeLifecycle = useVueNodeLifecycle()\n\nconst handleVueNodeLifecycleReset = async () => {\n if (shouldRenderVueNodes.value) {\n vueNodeLifecycle.disposeNodeManagerAndSyncs()\n await nextTick()\n vueNodeLifecycle.initializeNodeManager()\n }\n}\n\nwatch(() => canvasStore.currentGraph, handleVueNodeLifecycleReset)\n\nwatch(\n () => canvasStore.isInSubgraph,\n async (newValue, oldValue) => {\n if (oldValue && !newValue) {\n useWorkflowStore().updateActiveGraph()\n }\n await handleVueNodeLifecycleReset()\n }\n)\n\nconst allNodes = computed((): VueNodeData[] =>\n Array.from(vueNodeLifecycle.nodeManager.value?.vueNodeData?.values() ?? [])\n)\n\nwatchEffect(() => {\n LiteGraph.nodeOpacity = settingStore.get('Comfy.Node.Opacity')\n})\nwatchEffect(() => {\n LiteGraph.nodeLightness = colorPaletteStore.completedActivePalette.light_theme\n ? 0.5\n : undefined\n})\n\nwatchEffect(() => {\n nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated')\n})\n\nwatchEffect(() => {\n nodeDefStore.showExperimental = settingStore.get(\n 'Comfy.Node.ShowExperimental'\n )\n})\n\nwatchEffect(() => {\n const spellcheckEnabled = settingStore.get('Comfy.TextareaWidget.Spellcheck')\n const textareas = document.querySelectorAll<HTMLTextAreaElement>(\n 'textarea.comfy-multiline-input'\n )\n\n textareas.forEach((textarea: HTMLTextAreaElement) => {\n textarea.spellcheck = spellcheckEnabled\n // Force recheck to ensure visual update\n textarea.focus()\n textarea.blur()\n })\n})\n\nwatch(\n () => settingStore.get('Comfy.WidgetControlMode'),\n () => {\n if (!canvasStore.canvas) return\n\n forEachNode(comfyApp.rootGraph, (n) => {\n if (!n.widgets) return\n for (const w of n.widgets) {\n if (!w[IS_CONTROL_WIDGET]) continue\n updateControlWidgetLabel(w)\n if (!w.linkedWidgets) continue\n for (const l of w.linkedWidgets) {\n updateControlWidgetLabel(l)\n }\n }\n })\n canvasStore.canvas.setDirty(true)\n }\n)\n\nwatch(\n [() => canvasStore.canvas, () => settingStore.get('Comfy.ColorPalette')],\n async ([canvas, currentPaletteId]) => {\n if (!canvas) return\n\n await colorPaletteService.loadColorPalette(currentPaletteId)\n }\n)\n\nwatch(\n () => settingStore.get('Comfy.Canvas.BackgroundImage'),\n async () => {\n if (!canvasStore.canvas) return\n const currentPaletteId = colorPaletteStore.activePaletteId\n if (!currentPaletteId) return\n\n // Reload color palette to apply background image\n await colorPaletteService.loadColorPalette(currentPaletteId)\n // Mark background canvas as dirty\n canvasStore.canvas.setDirty(false, true)\n }\n)\nwatch(\n () => colorPaletteStore.activePaletteId,\n async (newValue) => {\n await settingStore.set('Comfy.ColorPalette', newValue)\n }\n)\n\n// Update the progress of executing nodes\nwatch(\n () =>\n [executionStore.nodeLocationProgressStates, canvasStore.canvas] as const,\n ([nodeLocationProgressStates, canvas]) => {\n if (!canvas?.graph) return\n for (const node of canvas.graph.nodes) {\n const nodeLocatorId = useWorkflowStore().nodeIdToNodeLocatorId(node.id)\n const progressState = nodeLocationProgressStates[nodeLocatorId]\n if (progressState && progressState.state === 'running') {\n node.progress = progressState.value / progressState.max\n } else {\n node.progress = undefined\n }\n }\n\n // Force canvas redraw to ensure progress updates are visible\n canvas.setDirty(true, false)\n },\n { deep: true }\n)\n\n// Update node slot errors for LiteGraph nodes\n// (Vue nodes read from store directly)\nwatch(\n () => executionStore.lastNodeErrors,\n (lastNodeErrors) => {\n if (!comfyApp.graph) return\n\n forEachNode(comfyApp.rootGraph, (node) => {\n // Clear existing errors\n for (const slot of node.inputs) {\n delete slot.hasErrors\n }\n for (const slot of node.outputs) {\n delete slot.hasErrors\n }\n\n const nodeErrors = lastNodeErrors?.[node.id]\n if (!nodeErrors) return\n\n const validErrors = nodeErrors.errors.filter(\n (error) => error.extra_info?.input_name !== undefined\n )\n\n validErrors.forEach((error) => {\n const inputName = error.extra_info!.input_name!\n const inputIndex = node.findInputSlot(inputName)\n if (inputIndex !== -1) {\n node.inputs[inputIndex].hasErrors = true\n }\n })\n })\n\n comfyApp.canvas.setDirty(true, true)\n }\n)\n\nuseEventListener(\n canvasRef,\n 'litegraph:no-items-selected',\n () => {\n toastStore.add({\n severity: 'warn',\n summary: t('toastMessages.nothingSelected'),\n life: 2000\n })\n },\n { passive: true }\n)\n\nconst loadCustomNodesI18n = async () => {\n try {\n const i18nData = await api.getCustomNodesI18n()\n mergeCustomNodesI18n(i18nData)\n } catch (error) {\n console.error('Failed to load custom nodes i18n', error)\n }\n}\n\nconst comfyAppReady = ref(false)\nconst workflowPersistence = useWorkflowPersistence()\nuseCanvasDrop(canvasRef)\nuseLitegraphSettings()\nuseNodeBadge()\n\nonMounted(async () => {\n useGlobalLitegraph()\n useContextMenuTranslation()\n useCopy()\n usePaste()\n useWorkflowAutoSave()\n useVueFeatureFlags()\n\n comfyApp.vueAppReady = true\n\n workspaceStore.spinner = true\n // ChangeTracker needs to be initialized before setup, as it will overwrite\n // some listeners of litegraph canvas.\n ChangeTracker.init()\n await loadCustomNodesI18n()\n try {\n await settingStore.loadSettingValues()\n } catch (error) {\n if (error instanceof UnauthorizedError) {\n localStorage.removeItem('Comfy.userId')\n localStorage.removeItem('Comfy.userName')\n window.location.reload()\n } else {\n throw error\n }\n }\n CORE_SETTINGS.forEach(settingStore.addSetting)\n\n await newUserService().initializeIfNewUser(settingStore)\n\n // @ts-expect-error fixme ts strict error\n await comfyApp.setup(canvasRef.value)\n canvasStore.canvas = comfyApp.canvas\n canvasStore.canvas.render_canvas_border = false\n workspaceStore.spinner = false\n useSearchBoxStore().setPopoverRef(nodeSearchboxPopoverRef.value)\n\n window.app = comfyApp\n window.graph = comfyApp.graph\n\n comfyAppReady.value = true\n\n vueNodeLifecycle.setupEmptyGraphListener()\n\n comfyApp.canvas.onSelectionChange = useChainCallback(\n comfyApp.canvas.onSelectionChange,\n () => canvasStore.updateSelectedItems()\n )\n\n // Load color palette\n colorPaletteStore.customPalettes = settingStore.get(\n 'Comfy.CustomColorPalettes'\n )\n\n // Restore saved workflow and workflow tabs state\n await workflowPersistence.initializeWorkflow()\n workflowPersistence.restoreWorkflowTabsState()\n\n // Load template from URL if present\n await workflowPersistence.loadTemplateFromUrlIfPresent()\n\n // Initialize release store to fetch releases from comfy-api (fire-and-forget)\n const { useReleaseStore } =\n await import('@/platform/updates/common/releaseStore')\n const releaseStore = useReleaseStore()\n void releaseStore.initialize()\n\n // Start watching for locale change after the initial value is loaded.\n watch(\n () => settingStore.get('Comfy.Locale'),\n async () => {\n await useCommandStore().execute('Comfy.RefreshNodeDefinitions')\n await useWorkflowService().reloadCurrentWorkflow()\n }\n )\n\n whenever(\n () => useCanvasStore().canvas,\n (canvas) => {\n useEventListener(canvas.canvas, 'litegraph:set-graph', () => {\n useWorkflowStore().updateActiveGraph()\n })\n },\n { immediate: true }\n )\n\n emit('ready')\n})\n\nonUnmounted(() => {\n vueNodeLifecycle.cleanup()\n})\n</script>\n","<template>\n <Toast group=\"reroute-migration\">\n <template #message>\n <div class=\"flex flex-auto flex-col items-start\">\n <div class=\"my-4 text-lg font-medium\">\n {{ t('toastMessages.migrateToLitegraphReroute') }}\n </div>\n <Button class=\"self-end\" size=\"sm\" @click=\"migrateToLitegraphReroute\">\n {{ t('g.migrate') }}\n </Button>\n </div>\n </template>\n </Toast>\n</template>\n\n<script setup lang=\"ts\">\nimport { useToast } from 'primevue'\nimport Toast from 'primevue/toast'\nimport { useI18n } from 'vue-i18n'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport type { WorkflowJSON04 } from '@/platform/workflow/validation/schemas/workflowSchema'\nimport { app } from '@/scripts/app'\nimport { migrateLegacyRerouteNodes } from '@/utils/migration/migrateReroute'\n\nconst { t } = useI18n()\nconst toast = useToast()\n\nconst workflowStore = useWorkflowStore()\nconst migrateToLitegraphReroute = async () => {\n const workflowJSON = app.rootGraph.serialize() as unknown as WorkflowJSON04\n const migratedWorkflowJSON = migrateLegacyRerouteNodes(workflowJSON)\n await app.loadGraphData(\n migratedWorkflowJSON,\n false,\n false,\n workflowStore.activeWorkflow\n )\n toast.removeGroup('reroute-migration')\n}\n</script>\n","import { useTitle } from '@vueuse/core'\nimport { computed } from 'vue'\n\nimport { t } from '@/i18n'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\n\nconst DEFAULT_TITLE = 'ComfyUI'\nconst TITLE_SUFFIX = ' - ComfyUI'\n\nexport const useBrowserTabTitle = () => {\n const executionStore = useExecutionStore()\n const settingStore = useSettingStore()\n const workflowStore = useWorkflowStore()\n const workspaceStore = useWorkspaceStore()\n\n const executionText = computed(() =>\n executionStore.isIdle\n ? ''\n : `[${Math.round(executionStore.executionProgress * 100)}%]`\n )\n\n const newMenuEnabled = computed(\n () => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'\n )\n\n const isAutoSaveEnabled = computed(\n () => settingStore.get('Comfy.Workflow.AutoSave') === 'after delay'\n )\n\n const isActiveWorkflowModified = computed(\n () => !!workflowStore.activeWorkflow?.isModified\n )\n const isActiveWorkflowPersisted = computed(\n () => !!workflowStore.activeWorkflow?.isPersisted\n )\n\n const shouldShowUnsavedIndicator = computed(() => {\n if (workspaceStore.shiftDown) return false\n if (isAutoSaveEnabled.value) return false\n if (!isActiveWorkflowPersisted.value) return true\n if (isActiveWorkflowModified.value) return true\n return false\n })\n\n const isUnsavedText = computed(() =>\n shouldShowUnsavedIndicator.value ? ' *' : ''\n )\n const workflowNameText = computed(() => {\n const workflowName = workflowStore.activeWorkflow?.filename\n return workflowName\n ? isUnsavedText.value + workflowName + TITLE_SUFFIX\n : DEFAULT_TITLE\n })\n\n const nodeExecutionTitle = computed(() => {\n // Check if any nodes are in progress\n const nodeProgressEntries = Object.entries(\n executionStore.nodeProgressStates\n )\n const runningNodes = nodeProgressEntries.filter(\n ([_, state]) => state.state === 'running'\n )\n\n if (runningNodes.length === 0) {\n return ''\n }\n\n // If multiple nodes are running\n if (runningNodes.length > 1) {\n return `${executionText.value}[${runningNodes.length} ${t('g.nodesRunning', 'nodes running')}]`\n }\n\n // If only one node is running\n const [nodeId, state] = runningNodes[0]\n const progress = Math.round((state.value / state.max) * 100)\n const nodeType =\n executionStore.activePrompt?.workflow?.changeTracker?.activeState.nodes.find(\n (n) => String(n.id) === nodeId\n )?.type || 'Node'\n\n return `${executionText.value}[${progress}%] ${nodeType}`\n })\n\n const workflowTitle = computed(\n () =>\n executionText.value +\n (newMenuEnabled.value ? workflowNameText.value : DEFAULT_TITLE)\n )\n\n const title = computed(() => nodeExecutionTitle.value || workflowTitle.value)\n useTitle(title)\n}\n","<template>\n <div class=\"size-full bg-modal-panel-background pr-6 pb-8 pl-4\">\n <slot></slot>\n </div>\n</template>\n","<template>\n <BaseModalLayout :content-title=\"$t('assetBrowser.checkpoints')\">\n <template #leftPanel>\n <LeftSidePanel v-model=\"selectedNavItem\" :nav-items=\"tempNavigation\">\n <template #header-icon>\n <i class=\"text-neutral icon-[lucide--puzzle]\" />\n </template>\n <template #header-title>\n <span class=\"text-neutral text-base\">{{ $t('g.title') }}</span>\n </template>\n </LeftSidePanel>\n </template>\n\n <template #header>\n <SearchBox v-model=\"searchQuery\" size=\"lg\" class=\"max-w-[384px]\" />\n </template>\n\n <template #header-right-area>\n <div class=\"flex gap-2\">\n <Button variant=\"primary\" @click=\"() => {}\">\n <i class=\"icon-[lucide--upload]\" />\n <span>{{ $t('g.upload') }}</span>\n </Button>\n <MoreButton>\n <template #default=\"{ close }\">\n <Button\n variant=\"secondary\"\n @click=\"\n () => {\n close()\n }\n \"\n >\n <i class=\"icon-[lucide--download]\" />\n <span>{{ $t('g.settings') }}</span>\n </Button>\n <Button\n variant=\"primary\"\n @click=\"\n () => {\n close()\n }\n \"\n >\n <i class=\"icon-[lucide--scroll]\" />\n <span>{{ $t('g.profile') }}</span>\n </Button>\n </template>\n </MoreButton>\n </div>\n </template>\n\n <template #contentFilter>\n <div class=\"relative flex gap-2 px-6 pb-4\">\n <MultiSelect\n v-model=\"selectedFrameworks\"\n v-model:search-query=\"searchText\"\n class=\"w-[250px]\"\n :label=\"$t('assetBrowser.selectFrameworks')\"\n :options=\"frameworkOptions\"\n :show-search-box=\"true\"\n :show-selected-count=\"true\"\n :show-clear-button=\"true\"\n />\n <MultiSelect\n v-model=\"selectedProjects\"\n :label=\"$t('assetBrowser.selectProjects')\"\n :options=\"projectOptions\"\n />\n <SingleSelect\n v-model=\"selectedSort\"\n :label=\"$t('assetBrowser.sortingType')\"\n :options=\"sortOptions\"\n class=\"w-[135px]\"\n >\n <template #icon>\n <i class=\"icon-[lucide--filter]\" />\n </template>\n </SingleSelect>\n </div>\n </template>\n\n <template #content>\n <!-- Card Examples -->\n <div :style=\"gridStyle\">\n <CardContainer v-for=\"i in 100\" :key=\"i\" size=\"regular\">\n <template #top>\n <CardTop ratio=\"landscape\">\n <template #default>\n <div class=\"h-full w-full bg-blue-500\"></div>\n </template>\n <template #top-right>\n <Button\n size=\"icon\"\n class=\"!bg-white !text-neutral-900\"\n @click=\"() => {}\"\n >\n <i class=\"icon-[lucide--info]\" />\n </Button>\n </template>\n <template #bottom-right>\n <SquareChip label=\"png\" />\n <SquareChip label=\"1.2 MB\" />\n <SquareChip label=\"LoRA\">\n <template #icon>\n <i class=\"icon-[lucide--folder]\" />\n </template>\n </SquareChip>\n </template>\n </CardTop>\n </template>\n <template #bottom>\n <CardBottom></CardBottom>\n </template>\n </CardContainer>\n </div>\n </template>\n\n <template #rightPanel>\n <RightSidePanel></RightSidePanel>\n </template>\n </BaseModalLayout>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, provide, ref } from 'vue'\n\nimport MoreButton from '@/components/button/MoreButton.vue'\nimport CardBottom from '@/components/card/CardBottom.vue'\nimport CardContainer from '@/components/card/CardContainer.vue'\nimport CardTop from '@/components/card/CardTop.vue'\nimport SquareChip from '@/components/chip/SquareChip.vue'\nimport SearchBox from '@/components/common/SearchBox.vue'\nimport MultiSelect from '@/components/input/MultiSelect.vue'\nimport SingleSelect from '@/components/input/SingleSelect.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'\nimport LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'\nimport RightSidePanel from '@/components/widget/panel/RightSidePanel.vue'\nimport type { NavGroupData, NavItemData } from '@/types/navTypes'\nimport { OnCloseKey } from '@/types/widgetTypes'\nimport { createGridStyle } from '@/utils/gridUtil'\n\nconst frameworkOptions = ref([\n { name: 'Vue', value: 'vue' },\n { name: 'React', value: 'react' },\n { name: 'Angular', value: 'angular' },\n { name: 'Svelte', value: 'svelte' }\n])\n\nconst projectOptions = ref([\n { name: 'Project A', value: 'proj-a' },\n { name: 'Project B', value: 'proj-b' },\n { name: 'Project C', value: 'proj-c' }\n])\n\nconst sortOptions = ref([\n { name: 'Popular', value: 'popular' },\n { name: 'Latest', value: 'latest' },\n { name: 'A → Z', value: 'az' }\n])\n\nconst tempNavigation = ref<(NavItemData | NavGroupData)[]>([\n { id: 'installed', label: 'Installed', icon: 'icon-[lucide--download]' },\n {\n title: 'TAGS',\n items: [\n { id: 'tag-sd15', label: 'SD 1.5', icon: 'icon-[lucide--tag]' },\n { id: 'tag-sdxl', label: 'SDXL', icon: 'icon-[lucide--tag]' },\n { id: 'tag-utility', label: 'Utility', icon: 'icon-[lucide--tag]' }\n ]\n },\n {\n title: 'CATEGORIES',\n items: [\n { id: 'cat-models', label: 'Models', icon: 'icon-[lucide--layers]' },\n { id: 'cat-nodes', label: 'Nodes', icon: 'icon-[lucide--grid-3x3]' }\n ]\n }\n])\n\nconst { onClose } = defineProps<{\n onClose: () => void\n}>()\n\nprovide(OnCloseKey, onClose)\n\nconst searchQuery = ref<string>('')\nconst searchText = ref<string>('')\nconst selectedFrameworks = ref([])\nconst selectedProjects = ref([])\nconst selectedSort = ref<string>('popular')\n\nconst selectedNavItem = ref<string | null>('installed')\n\nconst gridStyle = computed(() => createGridStyle())\n</script>\n","import SampleModelSelector from '@/components/widget/SampleModelSelector.vue'\nimport { useDialogService } from '@/services/dialogService'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst DIALOG_KEY = 'global-model-selector'\n\nexport const useModelSelectorDialog = () => {\n const dialogService = useDialogService()\n const dialogStore = useDialogStore()\n\n function hide() {\n dialogStore.closeDialog({ key: DIALOG_KEY })\n }\n\n function show() {\n dialogService.showLayoutDialog({\n key: DIALOG_KEY,\n component: SampleModelSelector,\n props: {\n onClose: hide\n }\n })\n }\n\n return {\n show,\n hide\n }\n}\n","import { LiteGraph } from '@/lib/litegraph/src/litegraph'\nimport type { LGraphNode, Point } from '@/lib/litegraph/src/litegraph'\nimport { assetItemSchema } from '@/platform/assets/schemas/assetSchema'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\nimport {\n MISSING_TAG,\n MODELS_TAG\n} from '@/platform/assets/services/assetService'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport { app } from '@/scripts/app'\nimport { useLitegraphService } from '@/services/litegraphService'\nimport { useModelToNodeStore } from '@/stores/modelToNodeStore'\n\ninterface CreateNodeOptions {\n position?: Point\n}\n\ntype NodeCreationErrorCode =\n | 'INVALID_ASSET'\n | 'NO_PROVIDER'\n | 'NODE_CREATION_FAILED'\n | 'MISSING_WIDGET'\n | 'NO_GRAPH'\n\ninterface NodeCreationError {\n code: NodeCreationErrorCode\n message: string\n assetId: string\n details?: Record<string, unknown>\n}\n\ntype Result<T, E> = { success: true; value: T } | { success: false; error: E }\n\n/**\n * Creates a LiteGraph node from an asset item.\n *\n * **Boundary Function**: Bridges Vue reactive domain with LiteGraph canvas domain.\n *\n * @param asset - Asset item to create node from (Vue domain)\n * @param options - Optional position and configuration\n * @returns Result with LiteGraph node (Canvas domain) or error details\n *\n * @remarks\n * This function performs side effects on the canvas graph. Validation failures\n * return error results rather than throwing to allow graceful degradation in UI contexts.\n * Widget validation occurs before graph mutation to prevent orphaned nodes.\n */\nexport function createModelNodeFromAsset(\n asset: AssetItem,\n options?: CreateNodeOptions\n): Result<LGraphNode, NodeCreationError> {\n const validatedAsset = assetItemSchema.safeParse(asset)\n\n if (!validatedAsset.success) {\n const errorMessage = validatedAsset.error.errors\n .map((e) => `${e.path.join('.')}: ${e.message}`)\n .join(', ')\n console.error('Invalid asset item:', errorMessage)\n return {\n success: false,\n error: {\n code: 'INVALID_ASSET',\n message: 'Asset schema validation failed',\n assetId: asset.id,\n details: { validationErrors: errorMessage }\n }\n }\n }\n\n const validAsset = validatedAsset.data\n\n const userMetadata = validAsset.user_metadata\n if (!userMetadata) {\n console.error(`Asset ${validAsset.id} missing required user_metadata`)\n return {\n success: false,\n error: {\n code: 'INVALID_ASSET',\n message: 'Asset missing required user_metadata',\n assetId: validAsset.id\n }\n }\n }\n\n const filename = userMetadata.filename\n if (typeof filename !== 'string' || filename.length === 0) {\n console.error(\n `Asset ${validAsset.id} has invalid user_metadata.filename (expected non-empty string, got ${typeof filename})`\n )\n return {\n success: false,\n error: {\n code: 'INVALID_ASSET',\n message: `Invalid filename (expected non-empty string, got ${typeof filename})`,\n assetId: validAsset.id\n }\n }\n }\n\n if (validAsset.tags.length === 0) {\n console.error(\n `Asset ${validAsset.id} has no tags defined (expected at least one category tag)`\n )\n return {\n success: false,\n error: {\n code: 'INVALID_ASSET',\n message: 'Asset has no tags defined',\n assetId: validAsset.id\n }\n }\n }\n\n const category = validAsset.tags.find(\n (tag) => tag !== MODELS_TAG && tag !== MISSING_TAG\n )\n if (!category) {\n console.error(\n `Asset ${validAsset.id} has no valid category tag. Available tags: ${validAsset.tags.join(', ')} (expected tag other than '${MODELS_TAG}' or '${MISSING_TAG}')`\n )\n return {\n success: false,\n error: {\n code: 'INVALID_ASSET',\n message: 'Asset has no valid category tag',\n assetId: validAsset.id,\n details: { availableTags: validAsset.tags }\n }\n }\n }\n\n const modelToNodeStore = useModelToNodeStore()\n const provider = modelToNodeStore.getNodeProvider(category)\n if (!provider) {\n console.error(`No node provider registered for category: ${category}`)\n return {\n success: false,\n error: {\n code: 'NO_PROVIDER',\n message: `No node provider registered for category: ${category}`,\n assetId: validAsset.id,\n details: { category }\n }\n }\n }\n\n const litegraphService = useLitegraphService()\n const pos = options?.position ?? litegraphService.getCanvasCenter()\n\n const node = LiteGraph.createNode(\n provider.nodeDef.name,\n provider.nodeDef.display_name,\n { pos }\n )\n\n if (!node) {\n console.error(`Failed to create node for type: ${provider.nodeDef.name}`)\n return {\n success: false,\n error: {\n code: 'NODE_CREATION_FAILED',\n message: `Failed to create node for type: ${provider.nodeDef.name}`,\n assetId: validAsset.id,\n details: { nodeType: provider.nodeDef.name }\n }\n }\n }\n\n const workflowStore = useWorkflowStore()\n const targetGraph = workflowStore.isSubgraphActive\n ? workflowStore.activeSubgraph\n : app.canvas.graph\n\n if (!targetGraph) {\n console.error('No active graph available')\n return {\n success: false,\n error: {\n code: 'NO_GRAPH',\n message: 'No active graph available',\n assetId: validAsset.id\n }\n }\n }\n\n const widget = node.widgets?.find((w) => w.name === provider.key)\n if (!widget) {\n console.error(\n `Widget ${provider.key} not found on node ${provider.nodeDef.name}`\n )\n return {\n success: false,\n error: {\n code: 'MISSING_WIDGET',\n message: `Widget ${provider.key} not found on node ${provider.nodeDef.name}`,\n assetId: validAsset.id,\n details: { widgetName: provider.key, nodeType: provider.nodeDef.name }\n }\n }\n }\n\n // Set widget value BEFORE adding to graph so the node is created with correct value\n widget.value = filename\n\n // Now add the node to the graph with the correct widget value already set\n targetGraph.add(node)\n\n return { success: true, value: node }\n}\n","import { isCloud } from '@/platform/distribution/types'\n\n/**\n * Zendesk ticket form field IDs.\n */\nconst ZENDESK_FIELDS = {\n /** Distribution tag (cloud vs OSS) */\n DISTRIBUTION: 'tf_42243568391700',\n /** User email (anonymous requester) */\n ANONYMOUS_EMAIL: 'tf_anonymous_requester_email',\n /** User email (authenticated) */\n EMAIL: 'tf_40029135130388',\n /** User ID */\n USER_ID: 'tf_42515251051412'\n} as const\n\nconst SUPPORT_BASE_URL = 'https://support.comfy.org/hc/en-us/requests/new'\n\n/**\n * Builds the support URL with optional user information for pre-filling.\n * Users without login information will still get a valid support URL without pre-fill.\n *\n * @param params - User information to pre-fill in the support form\n * @returns Complete Zendesk support URL with query parameters\n */\nexport function buildSupportUrl(params?: {\n userEmail?: string | null\n userId?: string | null\n}): string {\n const searchParams = new URLSearchParams({\n [ZENDESK_FIELDS.DISTRIBUTION]: isCloud ? 'ccloud' : 'oss'\n })\n\n if (params?.userEmail) {\n searchParams.append(ZENDESK_FIELDS.ANONYMOUS_EMAIL, params.userEmail)\n searchParams.append(ZENDESK_FIELDS.EMAIL, params.userEmail)\n }\n if (params?.userId) {\n searchParams.append(ZENDESK_FIELDS.USER_ID, params.userId)\n }\n\n return `${SUPPORT_BASE_URL}?${searchParams.toString()}`\n}\n","import { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'\nimport { useSubgraphOperations } from '@/composables/graph/useSubgraphOperations'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport { useModelSelectorDialog } from '@/composables/useModelSelectorDialog'\nimport {\n DEFAULT_DARK_COLOR_PALETTE,\n DEFAULT_LIGHT_COLOR_PALETTE\n} from '@/constants/coreColorPalettes'\nimport { tryToggleWidgetPromotion } from '@/core/graph/subgraph/proxyWidgetUtils'\nimport { t } from '@/i18n'\nimport {\n LGraphEventMode,\n LGraphGroup,\n LGraphNode,\n LiteGraph\n} from '@/lib/litegraph/src/litegraph'\nimport type { Point } from '@/lib/litegraph/src/litegraph'\nimport { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'\nimport { createModelNodeFromAsset } from '@/platform/assets/utils/createModelNodeFromAsset'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { buildSupportUrl } from '@/platform/support/config'\nimport { useTelemetry } from '@/platform/telemetry'\nimport type { ExecutionTriggerSource } from '@/platform/telemetry/types'\nimport { useToastStore } from '@/platform/updates/common/toastStore'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'\nimport type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'\nimport {\n useCanvasStore,\n useTitleEditorStore\n} from '@/renderer/core/canvas/canvasStore'\nimport { api } from '@/scripts/api'\nimport { app } from '@/scripts/app'\nimport { useDialogService } from '@/services/dialogService'\nimport { useLitegraphService } from '@/services/litegraphService'\nimport type { ComfyCommand } from '@/stores/commandStore'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useHelpCenterStore } from '@/stores/helpCenterStore'\nimport {\n useQueueSettingsStore,\n useQueueStore,\n useQueueUIStore\n} from '@/stores/queueStore'\nimport { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'\nimport { useSubgraphStore } from '@/stores/subgraphStore'\nimport { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'\nimport { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\nimport {\n getAllNonIoNodesInSubgraph,\n getExecutionIdsForSelectedNodes\n} from '@/utils/graphTraversalUtil'\nimport { filterOutputNodes } from '@/utils/nodeFilterUtil'\nimport {\n ManagerUIState,\n useManagerState\n} from '@/workbench/extensions/manager/composables/useManagerState'\nimport { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'\n\nimport { useWorkflowTemplateSelectorDialog } from './useWorkflowTemplateSelectorDialog'\n\nimport { useMaskEditorStore } from '@/stores/maskEditorStore'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst { isActiveSubscription, showSubscriptionDialog } = useSubscription()\n\nconst moveSelectedNodesVersionAdded = '1.22.2'\nexport function useCoreCommands(): ComfyCommand[] {\n const workflowService = useWorkflowService()\n const workflowStore = useWorkflowStore()\n const dialogService = useDialogService()\n const colorPaletteStore = useColorPaletteStore()\n const firebaseAuthActions = useFirebaseAuthActions()\n const toastStore = useToastStore()\n const canvasStore = useCanvasStore()\n const executionStore = useExecutionStore()\n const telemetry = useTelemetry()\n const { staticUrls, buildDocsUrl } = useExternalLink()\n const settingStore = useSettingStore()\n\n const bottomPanelStore = useBottomPanelStore()\n\n const dialogStore = useDialogStore()\n const maskEditorStore = useMaskEditorStore()\n\n const { getSelectedNodes, toggleSelectedNodesMode } =\n useSelectedLiteGraphItems()\n const getTracker = () => workflowStore.activeWorkflow?.changeTracker\n\n function isQueuePanelV2Enabled() {\n return settingStore.get('Comfy.Queue.QPOV2')\n }\n\n async function toggleQueuePanelV2() {\n await settingStore.set('Comfy.Queue.QPOV2', !isQueuePanelV2Enabled())\n }\n\n const moveSelectedNodes = (\n positionUpdater: (pos: Point, gridSize: number) => Point\n ) => {\n const selectedNodes = getSelectedNodes()\n if (selectedNodes.length === 0) return\n\n const gridSize = useSettingStore().get('Comfy.SnapToGrid.GridSize')\n selectedNodes.forEach((node) => {\n node.pos = positionUpdater(node.pos, gridSize)\n })\n app.canvas.state.selectionChanged = true\n app.canvas.setDirty(true, true)\n }\n\n const commands = [\n {\n id: 'Comfy.NewBlankWorkflow',\n icon: 'pi pi-plus',\n label: 'New Blank Workflow',\n menubarLabel: 'New',\n category: 'essentials' as const,\n function: async () => {\n const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0\n await workflowService.loadBlankWorkflow()\n telemetry?.trackWorkflowCreated({\n workflow_type: 'blank',\n previous_workflow_had_nodes: previousWorkflowHadNodes\n })\n }\n },\n {\n id: 'Comfy.OpenWorkflow',\n icon: 'pi pi-folder-open',\n label: 'Open Workflow',\n menubarLabel: 'Open',\n category: 'essentials' as const,\n function: () => {\n app.ui.loadFile()\n }\n },\n {\n id: 'Comfy.LoadDefaultWorkflow',\n icon: 'pi pi-code',\n label: 'Load Default Workflow',\n function: async () => {\n const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0\n await workflowService.loadDefaultWorkflow()\n telemetry?.trackWorkflowCreated({\n workflow_type: 'default',\n previous_workflow_had_nodes: previousWorkflowHadNodes\n })\n }\n },\n {\n id: 'Comfy.SaveWorkflow',\n icon: 'pi pi-save',\n label: 'Save Workflow',\n menubarLabel: 'Save',\n category: 'essentials' as const,\n function: async () => {\n const workflow = useWorkflowStore().activeWorkflow as ComfyWorkflow\n if (!workflow) return\n\n await workflowService.saveWorkflow(workflow)\n }\n },\n {\n id: 'Comfy.PublishSubgraph',\n icon: 'pi pi-save',\n label: 'Publish Subgraph',\n menubarLabel: 'Publish',\n function: async () => {\n await useSubgraphStore().publishSubgraph()\n }\n },\n {\n id: 'Comfy.SaveWorkflowAs',\n icon: 'pi pi-save',\n label: 'Save Workflow As',\n menubarLabel: 'Save As',\n category: 'essentials' as const,\n function: async () => {\n const workflow = useWorkflowStore().activeWorkflow as ComfyWorkflow\n if (!workflow) return\n\n await workflowService.saveWorkflowAs(workflow)\n }\n },\n {\n id: 'Comfy.RenameWorkflow',\n icon: 'pi pi-pencil',\n label: 'Rename Workflow',\n menubarLabel: 'Rename',\n function: async () => {\n const workflow = workflowStore.activeWorkflow\n if (!workflow || !workflow.isPersisted) return\n\n const newName = await dialogService.prompt({\n title: t('g.rename'),\n message: t('workflowService.enterFilename') + ':',\n defaultValue: workflow.filename\n })\n if (!newName || newName === workflow.filename) return\n\n const newPath = workflow.directory + '/' + newName + '.json'\n await workflowService.renameWorkflow(workflow, newPath)\n }\n },\n {\n id: 'Comfy.ExportWorkflow',\n icon: 'pi pi-download',\n label: 'Export Workflow',\n menubarLabel: 'Export',\n category: 'essentials' as const,\n function: async () => {\n await workflowService.exportWorkflow('workflow', 'workflow')\n }\n },\n {\n id: 'Comfy.ExportWorkflowAPI',\n icon: 'pi pi-download',\n label: 'Export Workflow (API Format)',\n menubarLabel: 'Export (API)',\n function: async () => {\n await workflowService.exportWorkflow('workflow_api', 'output')\n }\n },\n {\n id: 'Comfy.Undo',\n icon: 'pi pi-undo',\n label: 'Undo',\n category: 'essentials' as const,\n function: async () => {\n // If Mask Editor is open, use its history instead of the graph\n if (dialogStore.isDialogOpen('global-mask-editor')) {\n maskEditorStore.canvasHistory.undo()\n } else {\n await getTracker()?.undo?.()\n }\n }\n },\n {\n id: 'Comfy.Redo',\n icon: 'pi pi-refresh',\n label: 'Redo',\n category: 'essentials' as const,\n function: async () => {\n if (dialogStore.isDialogOpen('global-mask-editor')) {\n maskEditorStore.canvasHistory.redo()\n } else {\n await getTracker()?.redo?.()\n }\n }\n },\n {\n id: 'Comfy.ClearWorkflow',\n icon: 'pi pi-trash',\n label: 'Clear Workflow',\n category: 'essentials' as const,\n function: () => {\n const settingStore = useSettingStore()\n if (\n !settingStore.get('Comfy.ConfirmClear') ||\n confirm('Clear workflow?')\n ) {\n app.clean()\n if (app.canvas.subgraph) {\n // `clear` is not implemented on subgraphs and the parent class's\n // (`LGraph`) `clear` breaks the subgraph structure. For subgraphs,\n // just clear the nodes but preserve input/output nodes and structure\n const subgraph = app.canvas.subgraph\n const nonIoNodes = getAllNonIoNodesInSubgraph(subgraph)\n nonIoNodes.forEach((node) => subgraph.remove(node))\n }\n api.dispatchCustomEvent('graphCleared')\n }\n }\n },\n {\n id: 'Comfy.Canvas.ResetView',\n icon: 'pi pi-expand',\n label: 'Reset View',\n function: () => {\n useLitegraphService().resetView()\n }\n },\n {\n id: 'Comfy.OpenClipspace',\n icon: 'pi pi-clipboard',\n label: 'Clipspace',\n function: () => {\n app.openClipspace()\n }\n },\n {\n id: 'Comfy.RefreshNodeDefinitions',\n icon: 'pi pi-refresh',\n label: 'Refresh Node Definitions',\n category: 'essentials' as const,\n function: async () => {\n await app.refreshComboInNodes()\n }\n },\n {\n id: 'Comfy.Interrupt',\n icon: 'pi pi-stop',\n label: 'Interrupt',\n category: 'essentials' as const,\n function: async () => {\n await api.interrupt(executionStore.activePromptId)\n toastStore.add({\n severity: 'info',\n summary: t('g.interrupted'),\n detail: t('toastMessages.interrupted'),\n life: 1000\n })\n }\n },\n {\n id: 'Comfy.ClearPendingTasks',\n icon: 'pi pi-stop',\n label: 'Clear Pending Tasks',\n category: 'essentials' as const,\n function: async () => {\n await useQueueStore().clear(['queue'])\n toastStore.add({\n severity: 'info',\n summary: t('g.confirmed'),\n detail: t('toastMessages.pendingTasksDeleted'),\n life: 3000\n })\n }\n },\n {\n id: 'Comfy.BrowseTemplates',\n icon: 'pi pi-folder-open',\n label: 'Browse Templates',\n function: () => {\n useWorkflowTemplateSelectorDialog().show()\n }\n },\n {\n id: 'Comfy.Canvas.ZoomIn',\n icon: 'pi pi-plus',\n label: 'Zoom In',\n category: 'view-controls' as const,\n function: () => {\n const ds = app.canvas.ds\n ds.changeScale(\n ds.scale * 1.1,\n ds.element ? [ds.element.width / 2, ds.element.height / 2] : undefined\n )\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.Canvas.ZoomOut',\n icon: 'pi pi-minus',\n label: 'Zoom Out',\n category: 'view-controls' as const,\n function: () => {\n const ds = app.canvas.ds\n ds.changeScale(\n ds.scale / 1.1,\n ds.element ? [ds.element.width / 2, ds.element.height / 2] : undefined\n )\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Experimental.ToggleVueNodes',\n label: () =>\n `Experimental: ${\n useSettingStore().get('Comfy.VueNodes.Enabled') ? 'Disable' : 'Enable'\n } Nodes 2.0`,\n function: async () => {\n const settingStore = useSettingStore()\n const current = settingStore.get('Comfy.VueNodes.Enabled') ?? false\n await settingStore.set('Comfy.VueNodes.Enabled', !current)\n }\n },\n {\n id: 'Comfy.Canvas.FitView',\n icon: 'pi pi-expand',\n label: 'Fit view to selected nodes',\n menubarLabel: 'Zoom to fit',\n category: 'view-controls' as const,\n function: () => {\n if (app.canvas.empty) {\n toastStore.add({\n severity: 'error',\n summary: t('toastMessages.emptyCanvas'),\n life: 3000\n })\n return\n }\n app.canvas.fitViewToSelectionAnimated()\n }\n },\n {\n id: 'Comfy.Canvas.ToggleLock',\n icon: 'pi pi-lock',\n label: 'Canvas Toggle Lock',\n category: 'view-controls' as const,\n function: () => {\n app.canvas.state.readOnly = !app.canvas.state.readOnly\n }\n },\n {\n id: 'Comfy.Canvas.Lock',\n icon: 'pi pi-lock',\n label: 'Lock Canvas',\n category: 'view-controls' as const,\n function: () => {\n app.canvas.state.readOnly = true\n }\n },\n {\n id: 'Comfy.Canvas.Unlock',\n icon: 'pi pi-lock-open',\n label: 'Unlock Canvas',\n function: () => {\n app.canvas.state.readOnly = false\n }\n },\n {\n id: 'Comfy.Canvas.ToggleLinkVisibility',\n icon: 'pi pi-eye',\n label: 'Canvas Toggle Link Visibility',\n menubarLabel: 'Node Links',\n versionAdded: '1.3.6',\n\n function: (() => {\n const settingStore = useSettingStore()\n let lastLinksRenderMode = LiteGraph.SPLINE_LINK\n\n return async () => {\n const currentMode = settingStore.get('Comfy.LinkRenderMode')\n\n if (currentMode === LiteGraph.HIDDEN_LINK) {\n // If links are hidden, restore the last positive value or default to spline mode\n await settingStore.set('Comfy.LinkRenderMode', lastLinksRenderMode)\n } else {\n // If links are visible, store the current mode and hide links\n lastLinksRenderMode = currentMode\n await settingStore.set(\n 'Comfy.LinkRenderMode',\n LiteGraph.HIDDEN_LINK\n )\n }\n }\n })(),\n active: () =>\n useSettingStore().get('Comfy.LinkRenderMode') !== LiteGraph.HIDDEN_LINK\n },\n {\n id: 'Comfy.Canvas.ToggleMinimap',\n icon: 'pi pi-map',\n label: 'Canvas Toggle Minimap',\n menubarLabel: 'Minimap',\n versionAdded: '1.24.1',\n function: async () => {\n const settingStore = useSettingStore()\n await settingStore.set(\n 'Comfy.Minimap.Visible',\n !settingStore.get('Comfy.Minimap.Visible')\n )\n },\n active: () => useSettingStore().get('Comfy.Minimap.Visible')\n },\n {\n id: 'Comfy.Queue.ToggleOverlay',\n icon: 'pi pi-history',\n label: () => t('queue.toggleJobHistory'),\n menubarLabel: () => t('queue.jobHistory'),\n versionAdded: '1.37.0',\n category: 'view-controls' as const,\n function: () => {\n useQueueUIStore().toggleOverlay()\n },\n active: () => useQueueUIStore().isOverlayExpanded\n },\n {\n id: 'Comfy.QueuePrompt',\n icon: 'pi pi-play',\n label: 'Queue Prompt',\n versionAdded: '1.3.7',\n category: 'essentials' as const,\n function: async (metadata?: {\n subscribe_to_run?: boolean\n trigger_source?: ExecutionTriggerSource\n }) => {\n useTelemetry()?.trackRunButton(metadata)\n if (!isActiveSubscription.value) {\n showSubscriptionDialog()\n return\n }\n\n const batchCount = useQueueSettingsStore().batchCount\n\n useTelemetry()?.trackWorkflowExecution()\n\n await app.queuePrompt(0, batchCount)\n }\n },\n {\n id: 'Comfy.QueuePromptFront',\n icon: 'pi pi-play',\n label: 'Queue Prompt (Front)',\n versionAdded: '1.3.7',\n category: 'essentials' as const,\n function: async (metadata?: {\n subscribe_to_run?: boolean\n trigger_source?: ExecutionTriggerSource\n }) => {\n useTelemetry()?.trackRunButton(metadata)\n if (!isActiveSubscription.value) {\n showSubscriptionDialog()\n return\n }\n\n const batchCount = useQueueSettingsStore().batchCount\n\n useTelemetry()?.trackWorkflowExecution()\n\n await app.queuePrompt(-1, batchCount)\n }\n },\n {\n id: 'Comfy.QueueSelectedOutputNodes',\n icon: 'pi pi-play',\n label: 'Queue Selected Output Nodes',\n versionAdded: '1.19.6',\n function: async (metadata?: {\n subscribe_to_run?: boolean\n trigger_source?: ExecutionTriggerSource\n }) => {\n useTelemetry()?.trackRunButton(metadata)\n if (!isActiveSubscription.value) {\n showSubscriptionDialog()\n return\n }\n\n const batchCount = useQueueSettingsStore().batchCount\n const selectedNodes = getSelectedNodes()\n const selectedOutputNodes = filterOutputNodes(selectedNodes)\n\n if (selectedOutputNodes.length === 0) {\n toastStore.add({\n severity: 'error',\n summary: t('toastMessages.nothingToQueue'),\n detail: t('toastMessages.pleaseSelectOutputNodes'),\n life: 3000\n })\n return\n }\n\n // Get execution IDs for all selected output nodes and their descendants\n const executionIds =\n getExecutionIdsForSelectedNodes(selectedOutputNodes)\n\n if (executionIds.length === 0) {\n toastStore.add({\n severity: 'error',\n summary: t('toastMessages.failedToQueue'),\n detail: t('toastMessages.failedExecutionPathResolution'),\n life: 3000\n })\n return\n }\n useTelemetry()?.trackWorkflowExecution()\n await app.queuePrompt(0, batchCount, executionIds)\n }\n },\n {\n id: 'Comfy.ShowSettingsDialog',\n icon: 'pi pi-cog',\n label: 'Show Settings Dialog',\n versionAdded: '1.3.7',\n category: 'view-controls' as const,\n function: () => {\n dialogService.showSettingsDialog()\n }\n },\n {\n id: 'Comfy.Graph.GroupSelectedNodes',\n icon: 'pi pi-sitemap',\n label: 'Group Selected Nodes',\n versionAdded: '1.3.7',\n category: 'essentials' as const,\n function: () => {\n const { canvas } = app\n if (!canvas.selectedItems?.size) {\n toastStore.add({\n severity: 'error',\n summary: t('toastMessages.nothingToGroup'),\n detail: t('toastMessages.pleaseSelectNodesToGroup'),\n life: 3000\n })\n return\n }\n const group = new LGraphGroup()\n const padding = useSettingStore().get(\n 'Comfy.GroupSelectedNodes.Padding'\n )\n group.resizeTo(canvas.selectedItems, padding)\n canvas.graph?.add(group)\n\n group.recomputeInsideNodes()\n\n useTitleEditorStore().titleEditorTarget = group\n }\n },\n {\n id: 'Workspace.NextOpenedWorkflow',\n icon: 'pi pi-step-forward',\n label: 'Next Opened Workflow',\n versionAdded: '1.3.9',\n function: async () => {\n await workflowService.loadNextOpenedWorkflow()\n }\n },\n {\n id: 'Workspace.PreviousOpenedWorkflow',\n icon: 'pi pi-step-backward',\n label: 'Previous Opened Workflow',\n versionAdded: '1.3.9',\n function: async () => {\n await workflowService.loadPreviousOpenedWorkflow()\n }\n },\n {\n id: 'Comfy.Canvas.ToggleSelectedNodes.Mute',\n icon: 'pi pi-volume-off',\n label: 'Mute/Unmute Selected Nodes',\n versionAdded: '1.3.11',\n category: 'essentials' as const,\n function: () => {\n toggleSelectedNodesMode(LGraphEventMode.NEVER)\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.Canvas.ToggleSelectedNodes.Bypass',\n icon: 'pi pi-shield',\n label: 'Bypass/Unbypass Selected Nodes',\n versionAdded: '1.3.11',\n category: 'essentials' as const,\n function: () => {\n toggleSelectedNodesMode(LGraphEventMode.BYPASS)\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.Canvas.ToggleSelectedNodes.Pin',\n icon: 'pi pi-pin',\n label: 'Pin/Unpin Selected Nodes',\n versionAdded: '1.3.11',\n category: 'essentials' as const,\n function: () => {\n getSelectedNodes().forEach((node) => {\n node.pin(!node.pinned)\n })\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.Canvas.ToggleSelected.Pin',\n icon: 'pi pi-pin',\n label: 'Pin/Unpin Selected Items',\n versionAdded: '1.3.33',\n function: () => {\n for (const item of app.canvas.selectedItems) {\n if (item instanceof LGraphNode || item instanceof LGraphGroup) {\n item.pin(!item.pinned)\n }\n }\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.Canvas.Resize',\n icon: 'pi pi-minus',\n label: 'Resize Selected Nodes',\n versionAdded: '',\n function: () => {\n getSelectedNodes().forEach((node) => {\n const optimalSize = node.computeSize()\n node.setSize([optimalSize[0], optimalSize[1]])\n })\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.Canvas.ToggleSelectedNodes.Collapse',\n icon: 'pi pi-minus',\n label: 'Collapse/Expand Selected Nodes',\n versionAdded: '1.3.11',\n function: () => {\n getSelectedNodes().forEach((node) => {\n node.collapse()\n })\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.ToggleTheme',\n icon: 'pi pi-moon',\n label: 'Toggle Theme (Dark/Light)',\n versionAdded: '1.3.12',\n function: (() => {\n let previousDarkTheme: string = DEFAULT_DARK_COLOR_PALETTE.id\n let previousLightTheme: string = DEFAULT_LIGHT_COLOR_PALETTE.id\n\n return async () => {\n const settingStore = useSettingStore()\n const theme = colorPaletteStore.completedActivePalette\n if (theme.light_theme) {\n previousLightTheme = theme.id\n await settingStore.set('Comfy.ColorPalette', previousDarkTheme)\n } else {\n previousDarkTheme = theme.id\n await settingStore.set('Comfy.ColorPalette', previousLightTheme)\n }\n }\n })()\n },\n {\n id: 'Workspace.ToggleBottomPanel',\n icon: 'pi pi-list',\n label: 'Toggle Bottom Panel',\n menubarLabel: 'Bottom Panel',\n versionAdded: '1.3.22',\n category: 'view-controls' as const,\n function: () => {\n bottomPanelStore.toggleBottomPanel()\n },\n active: () => bottomPanelStore.bottomPanelVisible\n },\n {\n id: 'Workspace.ToggleFocusMode',\n icon: 'pi pi-eye',\n label: 'Toggle Focus Mode',\n menubarLabel: 'Focus Mode',\n versionAdded: '1.3.27',\n category: 'view-controls' as const,\n function: () => {\n useWorkspaceStore().toggleFocusMode()\n },\n active: () => useWorkspaceStore().focusMode\n },\n {\n id: 'Comfy.Graph.FitGroupToContents',\n icon: 'pi pi-expand',\n label: 'Fit Group To Contents',\n versionAdded: '1.4.9',\n function: () => {\n for (const group of app.canvas.selectedItems) {\n if (group instanceof LGraphGroup) {\n group.recomputeInsideNodes()\n const padding = useSettingStore().get(\n 'Comfy.GroupSelectedNodes.Padding'\n )\n group.resizeTo(group.children, padding)\n app.canvas.setDirty(false, true)\n }\n }\n }\n },\n {\n id: 'Comfy.Help.OpenComfyUIIssues',\n icon: 'pi pi-github',\n label: 'Open ComfyUI Issues',\n menubarLabel: 'ComfyUI Issues',\n versionAdded: '1.5.5',\n function: () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'github',\n is_external: true,\n source: 'menu'\n })\n window.open(staticUrls.githubIssues, '_blank')\n }\n },\n {\n id: 'Comfy.Help.OpenComfyUIDocs',\n icon: 'pi pi-info-circle',\n label: 'Open ComfyUI Docs',\n menubarLabel: 'ComfyUI Docs',\n versionAdded: '1.5.5',\n function: () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'docs',\n is_external: true,\n source: 'menu'\n })\n window.open(buildDocsUrl('/', { includeLocale: true }), '_blank')\n }\n },\n {\n id: 'Comfy.Help.OpenComfyOrgDiscord',\n icon: 'pi pi-discord',\n label: 'Open Comfy-Org Discord',\n menubarLabel: 'Comfy-Org Discord',\n versionAdded: '1.5.5',\n function: () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'discord',\n is_external: true,\n source: 'menu'\n })\n window.open(staticUrls.discord, '_blank')\n }\n },\n {\n id: 'Workspace.SearchBox.Toggle',\n icon: 'pi pi-search',\n label: 'Toggle Search Box',\n versionAdded: '1.5.7',\n function: () => {\n useSearchBoxStore().toggleVisible()\n }\n },\n {\n id: 'Comfy.Help.AboutComfyUI',\n icon: 'pi pi-info-circle',\n label: 'Open About ComfyUI',\n menubarLabel: 'About ComfyUI',\n versionAdded: '1.6.4',\n function: () => {\n dialogService.showSettingsDialog('about')\n }\n },\n {\n id: 'Comfy.DuplicateWorkflow',\n icon: 'pi pi-clone',\n label: 'Duplicate Current Workflow',\n versionAdded: '1.6.15',\n function: async () => {\n await workflowService.duplicateWorkflow(workflowStore.activeWorkflow!)\n }\n },\n {\n id: 'Workspace.CloseWorkflow',\n icon: 'pi pi-times',\n label: 'Close Current Workflow',\n versionAdded: '1.7.3',\n function: async () => {\n if (workflowStore.activeWorkflow)\n await workflowService.closeWorkflow(workflowStore.activeWorkflow)\n }\n },\n {\n id: 'Comfy.ContactSupport',\n icon: 'pi pi-question',\n label: 'Contact Support',\n versionAdded: '1.17.8',\n function: () => {\n const { userEmail, resolvedUserInfo } = useCurrentUser()\n const supportUrl = buildSupportUrl({\n userEmail: userEmail.value,\n userId: resolvedUserInfo.value?.id\n })\n window.open(supportUrl, '_blank')\n }\n },\n {\n id: 'Comfy.Help.OpenComfyUIForum',\n icon: 'pi pi-comments',\n label: 'Open ComfyUI Forum',\n menubarLabel: 'ComfyUI Forum',\n versionAdded: '1.8.2',\n function: () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'help_feedback',\n is_external: true,\n source: 'menu'\n })\n window.open(staticUrls.forum, '_blank')\n }\n },\n {\n id: 'Comfy.Canvas.DeleteSelectedItems',\n icon: 'pi pi-trash',\n label: 'Delete Selected Items',\n versionAdded: '1.10.5',\n function: () => {\n app.canvas.deleteSelected()\n app.canvas.setDirty(true, true)\n }\n },\n {\n id: 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu',\n icon: 'pi pi-puzzle',\n label: 'Custom Nodes Manager',\n versionAdded: '1.12.10',\n function: async () => {\n await useManagerState().openManager({\n showToastOnLegacyError: true\n })\n }\n },\n {\n id: 'Comfy.Manager.ShowUpdateAvailablePacks',\n icon: 'pi pi-sync',\n label: 'Check for Custom Node Updates',\n versionAdded: '1.17.0',\n function: async () => {\n const managerState = useManagerState()\n const state = managerState.managerUIState.value\n\n // For DISABLED state, show error toast instead of opening settings\n if (state === ManagerUIState.DISABLED) {\n toastStore.add({\n severity: 'error',\n summary: t('g.error'),\n detail: t('manager.notAvailable'),\n life: 3000\n })\n return\n }\n\n await managerState.openManager({\n initialTab: ManagerTab.UpdateAvailable,\n showToastOnLegacyError: false\n })\n }\n },\n {\n id: 'Comfy.Manager.ShowMissingPacks',\n icon: 'pi pi-exclamation-circle',\n label: 'Install Missing Custom Nodes',\n versionAdded: '1.17.0',\n function: async () => {\n await useManagerState().openManager({\n initialTab: ManagerTab.Missing,\n showToastOnLegacyError: false\n })\n }\n },\n {\n id: 'Comfy.User.OpenSignInDialog',\n icon: 'pi pi-user',\n label: 'Open Sign In Dialog',\n versionAdded: '1.17.6',\n function: async () => {\n await dialogService.showSignInDialog()\n }\n },\n {\n id: 'Comfy.User.SignOut',\n icon: 'pi pi-sign-out',\n label: 'Sign Out',\n versionAdded: '1.18.1',\n function: async () => {\n await firebaseAuthActions.logout()\n }\n },\n {\n id: 'Comfy.Canvas.MoveSelectedNodes.Up',\n icon: 'pi pi-arrow-up',\n label: 'Move Selected Nodes Up',\n versionAdded: moveSelectedNodesVersionAdded,\n function: () => moveSelectedNodes(([x, y], gridSize) => [x, y - gridSize])\n },\n {\n id: 'Comfy.Canvas.MoveSelectedNodes.Down',\n icon: 'pi pi-arrow-down',\n label: 'Move Selected Nodes Down',\n versionAdded: moveSelectedNodesVersionAdded,\n function: () => moveSelectedNodes(([x, y], gridSize) => [x, y + gridSize])\n },\n {\n id: 'Comfy.Canvas.MoveSelectedNodes.Left',\n icon: 'pi pi-arrow-left',\n label: 'Move Selected Nodes Left',\n versionAdded: moveSelectedNodesVersionAdded,\n function: () => moveSelectedNodes(([x, y], gridSize) => [x - gridSize, y])\n },\n {\n id: 'Comfy.Canvas.MoveSelectedNodes.Right',\n icon: 'pi pi-arrow-right',\n label: 'Move Selected Nodes Right',\n versionAdded: moveSelectedNodesVersionAdded,\n function: () => moveSelectedNodes(([x, y], gridSize) => [x + gridSize, y])\n },\n {\n id: 'Comfy.Graph.ConvertToSubgraph',\n icon: 'icon-[lucide--shrink]',\n label: 'Convert Selection to Subgraph',\n versionAdded: '1.20.1',\n category: 'essentials' as const,\n function: () => {\n const canvas = canvasStore.getCanvas()\n const graph = canvas.subgraph ?? canvas.graph\n if (!graph) throw new TypeError('Canvas has no graph or subgraph set.')\n\n const res = graph.convertToSubgraph(canvas.selectedItems)\n if (!res) {\n toastStore.add({\n severity: 'error',\n summary: t('toastMessages.cannotCreateSubgraph'),\n detail: t('toastMessages.failedToConvertToSubgraph'),\n life: 3000\n })\n return\n }\n\n const { node } = res\n canvas.select(node)\n canvasStore.updateSelectedItems()\n }\n },\n {\n id: 'Comfy.Graph.UnpackSubgraph',\n icon: 'icon-[lucide--expand]',\n label: 'Unpack the selected Subgraph',\n versionAdded: '1.26.3',\n function: () => {\n const { unpackSubgraph } = useSubgraphOperations()\n unpackSubgraph()\n }\n },\n {\n id: 'Comfy.Graph.EditSubgraphWidgets',\n label: 'Edit Subgraph Widgets',\n icon: 'icon-[lucide--settings-2]',\n versionAdded: '1.28.5',\n function: () => {\n useRightSidePanelStore().openPanel('subgraph')\n }\n },\n {\n id: 'Comfy.Graph.ToggleWidgetPromotion',\n icon: 'icon-[lucide--arrow-left-right]',\n label: 'Toggle promotion of hovered widget',\n versionAdded: '1.30.1',\n function: tryToggleWidgetPromotion\n },\n {\n id: 'Comfy.OpenManagerDialog',\n icon: 'mdi mdi-puzzle-outline',\n label: 'Manager',\n function: async () => {\n await useManagerState().openManager({\n initialTab: ManagerTab.All,\n showToastOnLegacyError: false\n })\n }\n },\n {\n id: 'Comfy.ToggleHelpCenter',\n icon: 'pi pi-question-circle',\n label: 'Help Center',\n function: () => {\n useHelpCenterStore().toggle()\n },\n active: () => useHelpCenterStore().isVisible\n },\n {\n id: 'Comfy.ToggleCanvasInfo',\n icon: 'pi pi-info-circle',\n label: 'Canvas Performance',\n function: async () => {\n const settingStore = useSettingStore()\n const currentValue = settingStore.get('Comfy.Graph.CanvasInfo')\n await settingStore.set('Comfy.Graph.CanvasInfo', !currentValue)\n },\n active: () => useSettingStore().get('Comfy.Graph.CanvasInfo')\n },\n {\n id: 'Workspace.ToggleBottomPanel.Shortcuts',\n icon: 'pi pi-key',\n label: 'Show Keybindings Dialog',\n versionAdded: '1.24.1',\n category: 'view-controls' as const,\n function: () => {\n bottomPanelStore.togglePanel('shortcuts')\n }\n },\n {\n id: 'Comfy.Graph.ExitSubgraph',\n icon: 'pi pi-arrow-up',\n label: 'Exit Subgraph',\n versionAdded: '1.20.1',\n function: () => {\n const canvas = useCanvasStore().getCanvas()\n const navigationStore = useSubgraphNavigationStore()\n if (!canvas.graph) return\n\n canvas.setGraph(\n navigationStore.navigationStack.at(-2) ?? canvas.graph.rootGraph\n )\n }\n },\n {\n id: 'Comfy.Dev.ShowModelSelector',\n icon: 'pi pi-box',\n label: 'Show Model Selector (Dev)',\n versionAdded: '1.26.2',\n category: 'view-controls' as const,\n function: () => {\n const modelSelectorDialog = useModelSelectorDialog()\n modelSelectorDialog.show()\n }\n },\n {\n id: 'Comfy.Manager.CustomNodesManager.ShowLegacyCustomNodesMenu',\n icon: 'pi pi-bars',\n label: 'Custom Nodes (Legacy)',\n versionAdded: '1.16.4',\n function: async () => {\n await useManagerState().openManager({\n legacyCommand: 'Comfy.Manager.CustomNodesManager.ToggleVisibility',\n showToastOnLegacyError: true,\n isLegacyOnly: true\n })\n }\n },\n {\n id: 'Comfy.Manager.ShowLegacyManagerMenu',\n icon: 'mdi mdi-puzzle',\n label: 'Manager Menu (Legacy)',\n versionAdded: '1.16.4',\n function: async () => {\n await useManagerState().openManager({\n showToastOnLegacyError: true,\n isLegacyOnly: true\n })\n }\n },\n {\n id: 'Comfy.Memory.UnloadModels',\n icon: 'mdi mdi-vacuum-outline',\n label: 'Unload Models',\n versionAdded: '1.16.4',\n function: async () => {\n if (!useSettingStore().get('Comfy.Memory.AllowManualUnload')) {\n useToastStore().add({\n severity: 'error',\n summary: t('g.error'),\n detail: t('g.commandProhibited', {\n command: 'Comfy.Memory.UnloadModels'\n }),\n life: 3000\n })\n return\n }\n await api.freeMemory({ freeExecutionCache: false })\n }\n },\n {\n id: 'Comfy.Memory.UnloadModelsAndExecutionCache',\n icon: 'mdi mdi-vacuum-outline',\n label: 'Unload Models and Execution Cache',\n versionAdded: '1.16.4',\n function: async () => {\n if (!useSettingStore().get('Comfy.Memory.AllowManualUnload')) {\n useToastStore().add({\n severity: 'error',\n summary: t('g.error'),\n detail: t('g.commandProhibited', {\n command: 'Comfy.Memory.UnloadModelsAndExecutionCache'\n }),\n life: 3000\n })\n return\n }\n await api.freeMemory({ freeExecutionCache: true })\n }\n },\n {\n id: 'Comfy.BrowseModelAssets',\n icon: 'pi pi-folder-open',\n label: 'Experimental: Browse Model Assets',\n versionAdded: '1.28.3',\n function: async () => {\n if (!useSettingStore().get('Comfy.Assets.UseAssetAPI')) {\n const confirmed = await dialogService.confirm({\n title: 'Enable Asset API',\n message:\n 'The Asset API is currently disabled. Would you like to enable it?',\n type: 'default'\n })\n\n if (!confirmed) return\n\n const settingStore = useSettingStore()\n await settingStore.set('Comfy.Assets.UseAssetAPI', true)\n await workflowService.reloadCurrentWorkflow()\n }\n const assetBrowserDialog = useAssetBrowserDialog()\n await assetBrowserDialog.browse({\n assetType: 'models',\n title: t('sideToolbar.modelLibrary'),\n onAssetSelected: (asset) => {\n const result = createModelNodeFromAsset(asset)\n if (!result.success) {\n toastStore.add({\n severity: 'error',\n summary: t('g.error'),\n detail: t('assetBrowser.failedToCreateNode')\n })\n console.error('Node creation failed:', result.error)\n }\n }\n })\n }\n },\n {\n id: 'Comfy.ToggleAssetAPI',\n icon: 'pi pi-database',\n label: () =>\n `Experimental: ${\n useSettingStore().get('Comfy.Assets.UseAssetAPI')\n ? 'Disable'\n : 'Enable'\n } AssetAPI`,\n function: async () => {\n const settingStore = useSettingStore()\n const current = settingStore.get('Comfy.Assets.UseAssetAPI') ?? false\n await settingStore.set('Comfy.Assets.UseAssetAPI', !current)\n await useWorkflowService().reloadCurrentWorkflow() // ensure changes take effect immediately\n }\n },\n {\n id: 'Comfy.ToggleQPOV2',\n icon: 'pi pi-list',\n label: 'Toggle Queue Panel V2',\n function: toggleQueuePanelV2\n },\n {\n id: 'Comfy.ToggleLinear',\n icon: 'pi pi-database',\n label: 'toggle linear mode',\n function: () => (canvasStore.linearMode = !canvasStore.linearMode)\n }\n ]\n\n return commands.map((command) => ({ ...command, source: 'System' }))\n}\n","import { useFavicon } from '@vueuse/core'\nimport { watch } from 'vue'\n\nimport { useExecutionStore } from '@/stores/executionStore'\n\nexport const useProgressFavicon = () => {\n const defaultFavicon = '/assets/images/favicon_progress_16x16/frame_9.png'\n const favicon = useFavicon(defaultFavicon)\n const executionStore = useExecutionStore()\n const totalFrames = 10\n\n watch(\n [() => executionStore.executionProgress, () => executionStore.isIdle],\n ([progress, isIdle]) => {\n if (isIdle) {\n favicon.value = defaultFavicon\n } else {\n const frame = Math.min(\n Math.max(0, Math.floor(progress * totalFrames)),\n totalFrames - 1\n )\n favicon.value = `/assets/images/favicon_progress_16x16/frame_${frame}.png`\n }\n }\n )\n}\n","export enum LatentPreviewMethod {\n NoPreviews = 'none',\n Auto = 'auto',\n Latent2RGB = 'latent2rgb',\n TAESD = 'taesd'\n}\n\nexport enum LogLevel {\n DEBUG = 'DEBUG',\n INFO = 'INFO',\n WARNING = 'WARNING',\n ERROR = 'ERROR',\n CRITICAL = 'CRITICAL'\n}\n\nexport enum HashFunction {\n MD5 = 'md5',\n SHA1 = 'sha1',\n SHA256 = 'sha256',\n SHA512 = 'sha512'\n}\n\nexport enum CudaMalloc {\n // Let server decide whether to use CUDA malloc based on the current environment\n Auto = 'auto',\n // Disable CUDA malloc\n Disable = 'disable',\n // Enable CUDA malloc\n Enable = 'enable'\n}\n\nexport enum FloatingPointPrecision {\n AUTO = 'auto',\n FP64 = 'fp64',\n FP32 = 'fp32',\n FP16 = 'fp16',\n BF16 = 'bf16',\n FP8E4M3FN = 'fp8_e4m3fn',\n FP8E5M2 = 'fp8_e5m2'\n}\n\nexport enum CrossAttentionMethod {\n Auto = 'auto',\n Split = 'split',\n Quad = 'quad',\n Pytorch = 'pytorch'\n}\n\nexport enum VramManagement {\n Auto = 'auto',\n GPUOnly = 'gpu-only',\n HighVram = 'highvram',\n NormalVram = 'normalvram',\n LowVram = 'lowvram',\n NoVram = 'novram',\n CPU = 'cpu'\n}\n","import type { FormItem } from '@/platform/settings/types'\nimport {\n CrossAttentionMethod,\n CudaMalloc,\n FloatingPointPrecision,\n HashFunction,\n LatentPreviewMethod,\n LogLevel,\n VramManagement\n} from '@/types/serverArgs'\n\nexport type ServerConfigValue = string | number | true | null | undefined\n\nexport interface ServerConfig<T> extends FormItem {\n id: string\n defaultValue: T\n category?: string[]\n // Override the default value getter with a custom function.\n getValue?: (value: T) => Record<string, ServerConfigValue>\n}\n\nexport const SERVER_CONFIG_ITEMS: ServerConfig<any>[] = [\n // Network settings\n {\n id: 'listen',\n name: 'Host: The IP address to listen on',\n category: ['Network'],\n type: 'text',\n defaultValue: '127.0.0.1'\n },\n {\n id: 'port',\n name: 'Port: The port to listen on',\n category: ['Network'],\n type: 'number',\n // The default launch port for desktop app is 8000 instead of 8188.\n defaultValue: 8000\n },\n {\n id: 'tls-keyfile',\n name: 'TLS Key File: Path to TLS key file for HTTPS',\n category: ['Network'],\n type: 'text',\n defaultValue: ''\n },\n {\n id: 'tls-certfile',\n name: 'TLS Certificate File: Path to TLS certificate file for HTTPS',\n category: ['Network'],\n type: 'text',\n defaultValue: ''\n },\n {\n id: 'enable-cors-header',\n name: 'Enable CORS header: Use \"*\" for all origins or specify domain',\n category: ['Network'],\n type: 'text',\n defaultValue: ''\n },\n {\n id: 'max-upload-size',\n name: 'Maximum upload size (MB)',\n category: ['Network'],\n type: 'number',\n defaultValue: 100\n },\n\n // CUDA settings\n {\n id: 'cuda-device',\n name: 'CUDA device index to use',\n category: ['CUDA'],\n type: 'number',\n defaultValue: null\n },\n {\n id: 'cuda-malloc',\n name: 'Use CUDA malloc for memory allocation',\n category: ['CUDA'],\n type: 'combo',\n options: Object.values(CudaMalloc),\n defaultValue: CudaMalloc.Auto,\n getValue: (value: CudaMalloc) => {\n switch (value) {\n case CudaMalloc.Auto:\n return {}\n case CudaMalloc.Enable:\n return {\n ['cuda-malloc']: true\n }\n case CudaMalloc.Disable:\n return {\n ['disable-cuda-malloc']: true\n }\n }\n }\n },\n\n // Precision settings\n {\n id: 'global-precision',\n name: 'Global floating point precision',\n category: ['Inference'],\n type: 'combo',\n options: [\n FloatingPointPrecision.AUTO,\n FloatingPointPrecision.FP32,\n FloatingPointPrecision.FP16\n ],\n defaultValue: FloatingPointPrecision.AUTO,\n tooltip: 'Global floating point precision',\n getValue: (value: FloatingPointPrecision) => {\n switch (value) {\n case FloatingPointPrecision.AUTO:\n return {}\n case FloatingPointPrecision.FP32:\n return {\n ['force-fp32']: true\n }\n case FloatingPointPrecision.FP16:\n return {\n ['force-fp16']: true\n }\n default:\n return {}\n }\n }\n },\n\n // UNET precision\n {\n id: 'unet-precision',\n name: 'UNET precision',\n category: ['Inference'],\n type: 'combo',\n options: [\n FloatingPointPrecision.AUTO,\n FloatingPointPrecision.FP64,\n FloatingPointPrecision.FP32,\n FloatingPointPrecision.FP16,\n FloatingPointPrecision.BF16,\n FloatingPointPrecision.FP8E4M3FN,\n FloatingPointPrecision.FP8E5M2\n ],\n defaultValue: FloatingPointPrecision.AUTO,\n tooltip: 'UNET precision',\n getValue: (value: FloatingPointPrecision) => {\n switch (value) {\n case FloatingPointPrecision.AUTO:\n return {}\n default:\n return {\n [`${value.toLowerCase()}-unet`]: true\n }\n }\n }\n },\n\n // VAE settings\n {\n id: 'vae-precision',\n name: 'VAE precision',\n category: ['Inference'],\n type: 'combo',\n options: [\n FloatingPointPrecision.AUTO,\n FloatingPointPrecision.FP16,\n FloatingPointPrecision.FP32,\n FloatingPointPrecision.BF16\n ],\n defaultValue: FloatingPointPrecision.AUTO,\n tooltip: 'VAE precision',\n getValue: (value: FloatingPointPrecision) => {\n switch (value) {\n case FloatingPointPrecision.AUTO:\n return {}\n default:\n return {\n [`${value.toLowerCase()}-vae`]: true\n }\n }\n }\n },\n {\n id: 'cpu-vae',\n name: 'Run VAE on CPU',\n category: ['Inference'],\n type: 'boolean',\n defaultValue: false\n },\n\n // Text Encoder settings\n {\n id: 'text-encoder-precision',\n name: 'Text Encoder precision',\n category: ['Inference'],\n type: 'combo',\n options: [\n FloatingPointPrecision.AUTO,\n FloatingPointPrecision.FP8E4M3FN,\n FloatingPointPrecision.FP8E5M2,\n FloatingPointPrecision.FP16,\n FloatingPointPrecision.FP32\n ],\n defaultValue: FloatingPointPrecision.AUTO,\n tooltip: 'Text Encoder precision',\n getValue: (value: FloatingPointPrecision) => {\n switch (value) {\n case FloatingPointPrecision.AUTO:\n return {}\n default:\n return {\n [`${value.toLowerCase()}-text-enc`]: true\n }\n }\n }\n },\n\n // Memory and performance settings\n {\n id: 'force-channels-last',\n name: 'Force channels-last memory format',\n category: ['Memory'],\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'directml',\n name: 'DirectML device index',\n category: ['Memory'],\n type: 'number',\n defaultValue: null\n },\n {\n id: 'disable-ipex-optimize',\n name: 'Disable IPEX optimization',\n category: ['Memory'],\n type: 'boolean',\n defaultValue: false\n },\n\n // Preview settings\n {\n id: 'preview-method',\n name: 'Method used for latent previews',\n category: ['Preview'],\n type: 'combo',\n options: Object.values(LatentPreviewMethod),\n defaultValue: LatentPreviewMethod.NoPreviews\n },\n {\n id: 'preview-size',\n name: 'Size of preview images',\n category: ['Preview'],\n type: 'slider',\n defaultValue: 512,\n attrs: {\n min: 128,\n max: 2048,\n step: 128\n }\n },\n\n // Cache settings\n {\n id: 'cache-classic',\n name: 'Use classic cache system',\n category: ['Cache'],\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'cache-lru',\n name: 'Use LRU caching with a maximum of N node results cached.',\n category: ['Cache'],\n type: 'number',\n defaultValue: null,\n tooltip: 'May use more RAM/VRAM.'\n },\n\n // Attention settings\n {\n id: 'cross-attention-method',\n name: 'Cross attention method',\n category: ['Attention'],\n type: 'combo',\n options: Object.values(CrossAttentionMethod),\n defaultValue: CrossAttentionMethod.Auto,\n getValue: (value: CrossAttentionMethod) => {\n switch (value) {\n case CrossAttentionMethod.Auto:\n return {}\n default:\n return {\n [`use-${value.toLowerCase()}-cross-attention`]: true\n }\n }\n }\n },\n {\n id: 'disable-xformers',\n name: 'Disable xFormers optimization',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'force-upcast-attention',\n name: 'Force attention upcast',\n category: ['Attention'],\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'dont-upcast-attention',\n name: 'Prevent attention upcast',\n category: ['Attention'],\n type: 'boolean',\n defaultValue: false\n },\n\n // VRAM management\n {\n id: 'vram-management',\n name: 'VRAM management mode',\n category: ['Memory'],\n type: 'combo',\n options: Object.values(VramManagement),\n defaultValue: VramManagement.Auto,\n getValue: (value: VramManagement) => {\n switch (value) {\n case VramManagement.Auto:\n return {}\n default:\n return {\n [value]: true\n }\n }\n }\n },\n {\n id: 'reserve-vram',\n name: 'Reserved VRAM (GB)',\n category: ['Memory'],\n type: 'number',\n defaultValue: null,\n tooltip:\n 'Set the amount of vram in GB you want to reserve for use by your OS/other software. By default some amount is reserved depending on your OS.'\n },\n\n // Misc settings\n {\n id: 'default-hashing-function',\n name: 'Default hashing function for model files',\n type: 'combo',\n options: Object.values(HashFunction),\n defaultValue: HashFunction.SHA256\n },\n {\n id: 'disable-smart-memory',\n name: 'Disable smart memory management',\n tooltip:\n 'Force ComfyUI to aggressively offload to regular ram instead of keeping models in vram when it can.',\n category: ['Memory'],\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'deterministic',\n name: 'Make pytorch use slower deterministic algorithms when it can.',\n type: 'boolean',\n defaultValue: false,\n tooltip: 'Note that this might not make images deterministic in all cases.'\n },\n {\n id: 'fast',\n name: 'Enable some untested and potentially quality deteriorating optimizations.',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'dont-print-server',\n name: \"Don't print server output to console.\",\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'disable-metadata',\n name: 'Disable saving prompt metadata in files.',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'enable-manager-legacy-ui',\n name: 'Use legacy Manager UI',\n tooltip: 'Uses the legacy ComfyUI-Manager UI instead of the new UI.',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'disable-all-custom-nodes',\n name: 'Disable loading all custom nodes.',\n type: 'boolean',\n defaultValue: false\n },\n {\n id: 'log-level',\n name: 'Logging verbosity level',\n type: 'combo',\n options: Object.values(LogLevel),\n defaultValue: LogLevel.INFO,\n getValue: (value: LogLevel) => {\n return {\n verbose: value\n }\n }\n },\n // Directories\n {\n id: 'input-directory',\n name: 'Input directory',\n category: ['Directories'],\n type: 'text',\n defaultValue: ''\n },\n {\n id: 'output-directory',\n name: 'Output directory',\n category: ['Directories'],\n type: 'text',\n defaultValue: ''\n }\n]\n","<script setup lang=\"ts\">\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { visible } = defineProps<{\n visible: boolean\n}>()\n\nconst isExpanded = defineModel<boolean>('expanded', { default: false })\n\nfunction toggle() {\n isExpanded.value = !isExpanded.value\n}\n</script>\n\n<template>\n <Teleport to=\"body\">\n <Transition\n enter-active-class=\"transition-all duration-300 ease-out\"\n enter-from-class=\"translate-y-full opacity-0\"\n enter-to-class=\"translate-y-0 opacity-100\"\n leave-active-class=\"transition-all duration-200 ease-in\"\n leave-from-class=\"translate-y-0 opacity-100\"\n leave-to-class=\"translate-y-full opacity-0\"\n >\n <div\n v-if=\"visible\"\n role=\"status\"\n aria-live=\"polite\"\n class=\"fixed inset-x-0 bottom-6 z-9999 mx-auto w-4/5 max-w-3xl overflow-hidden rounded-lg border border-border-default bg-base-background shadow-lg\"\n >\n <div\n :class=\"\n cn(\n 'overflow-hidden transition-all duration-300',\n isExpanded ? 'max-h-[400px]' : 'max-h-0'\n )\n \"\n >\n <slot :is-expanded />\n </div>\n\n <slot name=\"footer\" :is-expanded :toggle />\n </div>\n </Transition>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\ntype Severity = 'default' | 'secondary' | 'warn' | 'danger' | 'contrast'\n\nconst { label, severity = 'default' } = defineProps<{\n label: string\n severity?: Severity\n}>()\n\nfunction badgeClasses(sev: Severity): string {\n const baseClasses =\n 'inline-flex h-3.5 items-center justify-center rounded-full px-1 text-xxxs font-semibold uppercase'\n\n switch (sev) {\n case 'danger':\n return `${baseClasses} bg-destructive-background text-white`\n case 'contrast':\n return `${baseClasses} bg-base-foreground text-base-background`\n case 'warn':\n return `${baseClasses} bg-warning-background text-base-background`\n case 'secondary':\n return `${baseClasses} bg-secondary-background text-base-foreground`\n default:\n return `${baseClasses} bg-primary-background text-base-foreground`\n }\n}\n</script>\n\n<template>\n <span :class=\"badgeClasses(severity)\">{{ label }}</span>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport StatusBadge from '@/components/common/StatusBadge.vue'\nimport type { AssetDownload } from '@/stores/assetDownloadStore'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { job } = defineProps<{\n job: AssetDownload\n}>()\n\nconst { t } = useI18n()\n\nconst progressPercent = computed(() => Math.round(job.progress * 100))\nconst isCompleted = computed(() => job.status === 'completed')\nconst isFailed = computed(() => job.status === 'failed')\nconst isRunning = computed(() => job.status === 'running')\nconst isPending = computed(() => job.status === 'created')\n</script>\n\n<template>\n <div\n :class=\"\n cn(\n 'flex items-center justify-between rounded-lg bg-modal-card-background px-4 py-3',\n isCompleted && 'opacity-50'\n )\n \"\n >\n <div class=\"min-w-0 flex-1\">\n <span class=\"block truncate text-sm text-base-foreground\">{{\n job.assetName\n }}</span>\n </div>\n\n <div class=\"flex flex-shrink-0 items-center gap-2\">\n <template v-if=\"isFailed\">\n <i\n class=\"icon-[lucide--circle-alert] size-4 text-destructive-background\"\n />\n <StatusBadge :label=\"t('progressToast.failed')\" severity=\"danger\" />\n </template>\n\n <template v-else-if=\"isCompleted\">\n <StatusBadge :label=\"t('progressToast.finished')\" severity=\"contrast\" />\n </template>\n\n <template v-else-if=\"isRunning\">\n <i\n class=\"icon-[lucide--loader-circle] size-4 animate-spin text-base-foreground\"\n />\n <span class=\"text-xs text-base-foreground\">\n {{ progressPercent }}%\n </span>\n </template>\n\n <template v-else-if=\"isPending\">\n <span class=\"text-xs text-muted-foreground\">\n {{ t('progressToast.pending') }}\n </span>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { whenever } from '@vueuse/core'\nimport Popover from 'primevue/popover'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport HoneyToast from '@/components/honeyToast/HoneyToast.vue'\nimport ProgressToastItem from '@/components/toast/ProgressToastItem.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useAssetDownloadStore } from '@/stores/assetDownloadStore'\nimport { cn } from '@/utils/tailwindUtil'\n\nconst { t } = useI18n()\nconst assetDownloadStore = useAssetDownloadStore()\n\nconst visible = computed(() => assetDownloadStore.hasDownloads)\n\nconst isExpanded = ref(false)\nconst activeFilter = ref<'all' | 'completed' | 'failed'>('all')\nconst filterPopoverRef = ref<InstanceType<typeof Popover> | null>(null)\n\nwhenever(\n () => !isExpanded.value,\n () => filterPopoverRef.value?.hide()\n)\n\nconst filterOptions = [\n { value: 'all', label: 'all' },\n { value: 'completed', label: 'completed' },\n { value: 'failed', label: 'failed' }\n] as const\n\nfunction onFilterClick(event: Event) {\n filterPopoverRef.value?.toggle(event)\n}\n\nfunction setFilter(filter: typeof activeFilter.value) {\n activeFilter.value = filter\n filterPopoverRef.value?.hide()\n}\n\nconst downloadJobs = computed(() => assetDownloadStore.downloadList)\nconst completedJobs = computed(() =>\n assetDownloadStore.finishedDownloads.filter((d) => d.status === 'completed')\n)\nconst failedJobs = computed(() =>\n assetDownloadStore.finishedDownloads.filter((d) => d.status === 'failed')\n)\n\nconst isInProgress = computed(() => assetDownloadStore.hasActiveDownloads)\nconst currentJobName = computed(() => {\n const activeJob = downloadJobs.value.find((job) => job.status === 'running')\n return activeJob?.assetName || t('progressToast.downloadingModel')\n})\n\nconst completedCount = computed(\n () => completedJobs.value.length + failedJobs.value.length\n)\nconst totalCount = computed(() => downloadJobs.value.length)\n\nconst filteredJobs = computed(() => {\n switch (activeFilter.value) {\n case 'completed':\n return completedJobs.value\n case 'failed':\n return failedJobs.value\n default:\n return downloadJobs.value\n }\n})\n\nconst activeFilterLabel = computed(() => {\n const option = filterOptions.find((f) => f.value === activeFilter.value)\n return option\n ? t(`progressToast.filter.${option.label}`)\n : t('progressToast.filter.all')\n})\n\nfunction closeDialog() {\n assetDownloadStore.clearFinishedDownloads()\n isExpanded.value = false\n}\n</script>\n\n<template>\n <HoneyToast v-model:expanded=\"isExpanded\" :visible>\n <template #default>\n <div\n class=\"flex h-12 items-center justify-between border-b border-border-default px-4\"\n >\n <h3 class=\"text-sm font-bold text-base-foreground\">\n {{ t('progressToast.importingModels') }}\n </h3>\n <div class=\"flex items-center gap-2\">\n <Button\n variant=\"secondary\"\n size=\"md\"\n class=\"gap-1.5 px-2\"\n @click=\"onFilterClick\"\n >\n <i class=\"icon-[lucide--list-filter] size-4\" />\n <span>{{ activeFilterLabel }}</span>\n <i class=\"icon-[lucide--chevron-down] size-3\" />\n </Button>\n <Popover\n ref=\"filterPopoverRef\"\n :dismissable=\"true\"\n :close-on-escape=\"true\"\n unstyled\n :base-z-index=\"9999\"\n :pt=\"{\n root: { class: 'absolute z-50' },\n content: {\n class:\n 'bg-transparent border-none p-0 pt-2 rounded-lg shadow-lg'\n }\n }\"\n >\n <div\n class=\"flex min-w-30 flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3\"\n >\n <Button\n v-for=\"option in filterOptions\"\n :key=\"option.value\"\n variant=\"textonly\"\n size=\"sm\"\n :class=\"\n cn(\n 'w-full justify-start bg-transparent',\n activeFilter === option.value &&\n 'bg-secondary-background-selected'\n )\n \"\n @click=\"setFilter(option.value)\"\n >\n {{ t(`progressToast.filter.${option.label}`) }}\n </Button>\n </div>\n </Popover>\n </div>\n </div>\n\n <div class=\"relative max-h-75 overflow-y-auto px-4 py-4\">\n <div\n v-if=\"filteredJobs.length > 3\"\n class=\"absolute right-1 top-4 h-12 w-1 rounded-full bg-muted-foreground\"\n />\n\n <div class=\"flex flex-col gap-2\">\n <ProgressToastItem\n v-for=\"job in filteredJobs\"\n :key=\"job.taskId\"\n :job=\"job\"\n />\n </div>\n\n <div\n v-if=\"filteredJobs.length === 0\"\n class=\"flex flex-col items-center justify-center py-6 text-center\"\n >\n <span class=\"text-sm text-muted-foreground\">\n {{\n t('progressToast.noImportsInQueue', {\n filter: activeFilterLabel\n })\n }}\n </span>\n </div>\n </div>\n </template>\n\n <template #footer=\"{ toggle }\">\n <div\n class=\"flex h-12 items-center justify-between gap-2 border-t border-border-default px-4\"\n >\n <div class=\"flex min-w-0 flex-1 items-center gap-2 text-sm\">\n <template v-if=\"isInProgress\">\n <i\n class=\"icon-[lucide--loader-circle] size-4 flex-shrink-0 animate-spin text-muted-foreground\"\n />\n <span\n class=\"min-w-0 flex-1 truncate font-bold text-base-foreground\"\n >\n {{ currentJobName }}\n </span>\n </template>\n <template v-else-if=\"failedJobs.length > 0\">\n <i\n class=\"icon-[lucide--circle-alert] size-4 flex-shrink-0 text-destructive-background\"\n />\n <span class=\"min-w-0 truncate font-bold text-base-foreground\">\n {{\n t('progressToast.downloadsFailed', {\n count: failedJobs.length\n })\n }}\n </span>\n </template>\n <template v-else>\n <i\n class=\"icon-[lucide--check-circle] size-4 flex-shrink-0 text-jade-600\"\n />\n <span class=\"min-w-0 truncate font-bold text-base-foreground\">\n {{ t('progressToast.allDownloadsCompleted') }}\n </span>\n </template>\n </div>\n\n <div class=\"flex flex-shrink-0 items-center gap-2\">\n <span\n v-if=\"isInProgress\"\n class=\"whitespace-nowrap text-sm text-muted-foreground\"\n >\n {{\n t('progressToast.progressCount', {\n completed: completedCount,\n total: totalCount\n })\n }}\n </span>\n\n <div class=\"flex items-center\">\n <Button\n variant=\"muted-textonly\"\n size=\"icon\"\n :aria-label=\"\n isExpanded ? t('contextMenu.Collapse') : t('contextMenu.Expand')\n \"\n @click.stop=\"toggle\"\n >\n <i\n :class=\"\n cn(\n 'size-4',\n isExpanded\n ? 'icon-[lucide--chevron-down]'\n : 'icon-[lucide--chevron-up]'\n )\n \"\n />\n </Button>\n\n <Button\n v-if=\"!isInProgress\"\n variant=\"muted-textonly\"\n size=\"icon\"\n :aria-label=\"t('g.close')\"\n @click.stop=\"closeDialog\"\n >\n <i class=\"icon-[lucide--x] size-4\" />\n </Button>\n </div>\n </div>\n </div>\n </template>\n </HoneyToast>\n</template>\n","import { until, useStorage } from '@vueuse/core'\nimport { defineStore } from 'pinia'\nimport { gt, valid } from 'semver'\nimport { computed } from 'vue'\n\nimport config from '@/config'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useSystemStatsStore } from '@/stores/systemStatsStore'\n\nconst DISMISSAL_DURATION_MS = 7 * 24 * 60 * 60 * 1000 // 7 days\n\nexport const useVersionCompatibilityStore = defineStore(\n 'versionCompatibility',\n () => {\n const systemStatsStore = useSystemStatsStore()\n const settingStore = useSettingStore()\n\n const frontendVersion = computed(() => config.app_version)\n const backendVersion = computed(\n () => systemStatsStore.systemStats?.system?.comfyui_version ?? ''\n )\n const requiredFrontendVersion = computed(\n () =>\n systemStatsStore.systemStats?.system?.required_frontend_version ?? ''\n )\n\n const isFrontendOutdated = computed(() => {\n if (\n !frontendVersion.value ||\n !requiredFrontendVersion.value ||\n !valid(frontendVersion.value) ||\n !valid(requiredFrontendVersion.value)\n ) {\n return false\n }\n // Returns true if required version is greater than frontend version\n return gt(requiredFrontendVersion.value, frontendVersion.value)\n })\n\n const isFrontendNewer = computed(() => {\n // We don't warn about frontend being newer than backend\n // Only warn when frontend is outdated (behind required version)\n return false\n })\n\n const hasVersionMismatch = computed(() => {\n return isFrontendOutdated.value\n })\n\n const versionKey = computed(() => {\n if (\n !frontendVersion.value ||\n !backendVersion.value ||\n !requiredFrontendVersion.value\n ) {\n return null\n }\n return `${frontendVersion.value}-${backendVersion.value}-${requiredFrontendVersion.value}`\n })\n\n // Use reactive storage for dismissals - creates a reactive ref that syncs with localStorage\n // All version mismatch dismissals are stored in a single object for clean localStorage organization\n const dismissalStorage = useStorage(\n 'comfy.versionMismatch.dismissals',\n {} as Record<string, number>,\n localStorage,\n {\n serializer: {\n read: (value: string) => {\n try {\n return JSON.parse(value)\n } catch {\n return {}\n }\n },\n write: (value: Record<string, number>) => JSON.stringify(value)\n }\n }\n )\n\n const isDismissed = computed(() => {\n if (!versionKey.value) return false\n\n const dismissedUntil = dismissalStorage.value[versionKey.value]\n if (!dismissedUntil) return false\n\n // Check if dismissal has expired\n return Date.now() < dismissedUntil\n })\n\n const warningsDisabled = computed(() =>\n settingStore.get('Comfy.VersionCompatibility.DisableWarnings')\n )\n\n const shouldShowWarning = computed(() => {\n return (\n hasVersionMismatch.value &&\n !isDismissed.value &&\n !warningsDisabled.value\n )\n })\n\n const warningMessage = computed(() => {\n if (isFrontendOutdated.value) {\n return {\n type: 'outdated' as const,\n frontendVersion: frontendVersion.value,\n requiredVersion: requiredFrontendVersion.value\n }\n }\n return null\n })\n\n async function checkVersionCompatibility() {\n if (!systemStatsStore.systemStats) {\n await until(systemStatsStore.isInitialized)\n }\n }\n\n function dismissWarning() {\n if (!versionKey.value) return\n\n const dismissUntil = Date.now() + DISMISSAL_DURATION_MS\n dismissalStorage.value = {\n ...dismissalStorage.value,\n [versionKey.value]: dismissUntil\n }\n }\n\n async function initialize() {\n await checkVersionCompatibility()\n }\n\n return {\n frontendVersion,\n backendVersion,\n requiredFrontendVersion,\n hasVersionMismatch,\n shouldShowWarning,\n warningMessage,\n isFrontendOutdated,\n isFrontendNewer,\n checkVersionCompatibility,\n dismissWarning,\n initialize\n }\n }\n)\n","import { whenever } from '@vueuse/core'\nimport { computed, nextTick, onMounted } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useToastStore } from './toastStore'\nimport { useVersionCompatibilityStore } from './versionCompatibilityStore'\n\ninterface UseFrontendVersionMismatchWarningOptions {\n immediate?: boolean\n}\n\n/**\n * Composable for handling frontend version mismatch warnings.\n *\n * Displays toast notifications when the frontend version is incompatible with the backend,\n * either because the frontend is outdated or newer than the backend expects.\n * Automatically dismisses warnings when shown and persists dismissal state for 7 days.\n *\n * @param options - Configuration options\n * @param options.immediate - If true, automatically shows warning when version mismatch is detected\n * @returns Object with methods and computed properties for managing version warnings\n *\n * @example\n * ```ts\n * // Show warning immediately when mismatch detected\n * const { showWarning, shouldShowWarning } = useFrontendVersionMismatchWarning({ immediate: true })\n *\n * // Manual control\n * const { showWarning } = useFrontendVersionMismatchWarning()\n * showWarning() // Call when needed\n * ```\n */\nexport function useFrontendVersionMismatchWarning(\n options: UseFrontendVersionMismatchWarningOptions = {}\n) {\n const { immediate = false } = options\n const { t } = useI18n()\n const toastStore = useToastStore()\n const versionCompatibilityStore = useVersionCompatibilityStore()\n\n // Track if we've already shown the warning\n let hasShownWarning = false\n\n const showWarning = () => {\n // Prevent showing the warning multiple times\n if (hasShownWarning) return\n\n const message = versionCompatibilityStore.warningMessage\n if (!message) return\n\n const detailMessage = t('g.frontendOutdated', {\n frontendVersion: message.frontendVersion,\n requiredVersion: message.requiredVersion\n })\n\n const fullMessage = t('g.versionMismatchWarningMessage', {\n warning: t('g.versionMismatchWarning'),\n detail: detailMessage\n })\n\n toastStore.addAlert(fullMessage)\n hasShownWarning = true\n\n // Automatically dismiss the warning so it won't show again for 7 days\n versionCompatibilityStore.dismissWarning()\n }\n\n onMounted(async () => {\n // Only set up the watcher if immediate is true\n if (immediate) {\n // Wait for next tick to ensure reactive updates from settings load have propagated\n await nextTick()\n\n whenever(\n () => versionCompatibilityStore.shouldShowWarning,\n () => {\n showWarning()\n },\n {\n immediate: true,\n once: true\n }\n )\n }\n })\n\n return {\n showWarning,\n shouldShowWarning: computed(\n () => versionCompatibilityStore.shouldShowWarning\n ),\n dismissWarning: versionCompatibilityStore.dismissWarning,\n hasVersionMismatch: computed(\n () => versionCompatibilityStore.hasVersionMismatch\n )\n }\n}\n","import { api } from '@/scripts/api'\nimport { app } from '@/scripts/app'\nimport {\n useQueuePendingTaskCountStore,\n useQueueSettingsStore\n} from '@/stores/queueStore'\n\nexport function setupAutoQueueHandler() {\n const queueCountStore = useQueuePendingTaskCountStore()\n const queueSettingsStore = useQueueSettingsStore()\n\n let graphHasChanged = false\n let internalCount = 0 // Use an internal counter here so it is instantly updated when re-queuing\n api.addEventListener('graphChanged', () => {\n if (queueSettingsStore.mode === 'change') {\n if (internalCount) {\n graphHasChanged = true\n } else {\n graphHasChanged = false\n // Queue the prompt in the background\n void app.queuePrompt(0, queueSettingsStore.batchCount)\n internalCount++\n }\n }\n })\n\n queueCountStore.$subscribe(\n async () => {\n internalCount = queueCountStore.count\n if (!internalCount && !app.lastExecutionError) {\n if (\n queueSettingsStore.mode === 'instant' ||\n (queueSettingsStore.mode === 'change' && graphHasChanged)\n ) {\n graphHasChanged = false\n await app.queuePrompt(0, queueSettingsStore.batchCount)\n }\n }\n },\n { detached: true }\n )\n}\n","<script setup lang=\"ts\">\nimport { storeToRefs } from 'pinia'\nimport Splitter from 'primevue/splitter'\nimport SplitterPanel from 'primevue/splitterpanel'\nimport { computed } from 'vue'\n\nimport ExtensionSlot from '@/components/common/ExtensionSlot.vue'\nimport CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'\nimport LoginButton from '@/components/topbar/LoginButton.vue'\nimport TopbarBadges from '@/components/topbar/TopbarBadges.vue'\nimport WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useCurrentUser } from '@/composables/auth/useCurrentUser'\nimport {\n isValidWidgetValue,\n safeWidgetMapper\n} from '@/composables/graph/useGraphNodeManager'\nimport { useAssetsSidebarTab } from '@/composables/sidebarTabs/useAssetsSidebarTab'\nimport { t } from '@/i18n'\nimport type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue'\nimport WidgetInputNumberInput from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue'\nimport { app } from '@/scripts/app'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useNodeOutputStore } from '@/stores/imagePreviewStore'\nimport { useQueueSettingsStore } from '@/stores/queueStore'\nimport { isElectron } from '@/utils/envUtil'\n\nconst nodeOutputStore = useNodeOutputStore()\nconst commandStore = useCommandStore()\nconst nodeDatas = computed(() => {\n function nodeToNodeData(node: LGraphNode) {\n const mapper = safeWidgetMapper(node, new Map())\n const widgets =\n node.widgets?.map((widget) => {\n const safeWidget = mapper(widget)\n safeWidget.callback = function (value) {\n if (!isValidWidgetValue(value)) return\n widget.value = value ?? undefined\n return widget.callback?.(widget.value)\n }\n return safeWidget\n }) ?? []\n //Only widgets is actually used\n return {\n id: `${node.id}`,\n title: node.title,\n type: node.type,\n mode: 0,\n selected: false,\n executing: false,\n widgets\n }\n }\n return app.rootGraph.nodes\n .filter((node) => node.mode === 0 && node.widgets?.length)\n .map(nodeToNodeData)\n})\nconst { isLoggedIn } = useCurrentUser()\nconst isDesktop = isElectron()\n\nconst batchCountWidget = {\n options: { step2: 1, precision: 1, min: 1, max: 100 },\n value: 1,\n name: t('Number of generations'),\n type: 'number'\n}\n\nconst { batchCount } = storeToRefs(useQueueSettingsStore())\n\n//TODO: refactor out of this file.\n//code length is small, but changes should propagate\nasync function runButtonClick(e: Event) {\n const isShiftPressed = 'shiftKey' in e && e.shiftKey\n const commandId = isShiftPressed\n ? 'Comfy.QueuePromptFront'\n : 'Comfy.QueuePrompt'\n\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'queue_run_linear'\n })\n if (batchCount.value > 1) {\n useTelemetry()?.trackUiButtonClicked({\n button_id: 'queue_run_multiple_batches_submitted'\n })\n }\n await commandStore.execute(commandId, {\n metadata: {\n subscribe_to_run: false,\n trigger_source: 'button'\n }\n })\n}\nfunction openFeedback() {\n //TODO: Does not link to a linear specific feedback section\n window.open(\n 'https://support.comfy.org/hc/en-us/requests/new?ticket_form_id=40026345549204',\n '_blank',\n 'noopener,noreferrer'\n )\n}\n</script>\n<template>\n <div class=\"absolute w-full h-full\">\n <div class=\"workflow-tabs-container pointer-events-auto h-9.5 w-full\">\n <div class=\"flex h-full items-center\">\n <WorkflowTabs />\n <TopbarBadges />\n </div>\n </div>\n <Splitter\n class=\"h-[calc(100%-38px)] w-full bg-comfy-menu-secondary-bg\"\n :pt=\"{ gutter: { class: 'bg-transparent w-4 -mx-3' } }\"\n >\n <SplitterPanel :size=\"1\" class=\"min-w-min bg-comfy-menu-bg\">\n <div\n class=\"sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto border-r-1 border-node-component-border\"\n >\n <ExtensionSlot :extension=\"useAssetsSidebarTab()\" />\n </div>\n </SplitterPanel>\n <SplitterPanel\n :size=\"98\"\n class=\"flex flex-row overflow-y-auto flex-wrap min-w-min gap-4 m-4\"\n >\n <img\n v-for=\"previewUrl in nodeOutputStore.latestOutput\"\n :key=\"previewUrl\"\n class=\"pointer-events-none object-contain flex-1 max-h-full\"\n :src=\"previewUrl\"\n />\n <img\n v-if=\"nodeOutputStore.latestOutput.length === 0\"\n class=\"pointer-events-none object-contain flex-1 max-h-full brightness-50 opacity-10\"\n src=\"/assets/images/comfy-logo-mono.svg\"\n />\n </SplitterPanel>\n <SplitterPanel :size=\"1\" class=\"flex flex-col gap-1 p-1 min-w-min\">\n <div\n class=\"actionbar-container flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] p-2 gap-2 bg-comfy-menu-bg justify-end\"\n >\n <Button variant=\"secondary\" @click=\"openFeedback\">\n {{ t('g.feedback') }}\n </Button>\n <Button\n variant=\"secondary\"\n class=\"min-w-max\"\n @click=\"useCanvasStore().linearMode = false\"\n >\n {{ t('linearMode.openWorkflow') }}\n <i class=\"icon-[comfy--workflow]\" />\n </Button>\n <Button\n variant=\"inverted\"\n @click=\"useWorkflowService().exportWorkflow('workflow', 'workflow')\"\n >\n {{ t('linearMode.share') }}\n </Button>\n <CurrentUserButton v-if=\"isLoggedIn\" />\n <LoginButton v-else-if=\"isDesktop\" />\n </div>\n <div\n class=\"rounded-lg border p-2 gap-2 h-full border-[var(--interface-stroke)] bg-comfy-menu-bg flex flex-col\"\n >\n <div\n class=\"grow-1 flex justify-start flex-col overflow-y-auto contain-size *:max-h-100\"\n >\n <NodeWidgets\n v-for=\"nodeData of nodeDatas\"\n :key=\"nodeData.id\"\n :node-data\n class=\"border-b-1 border-node-component-border pt-1 pb-2 last:border-none\"\n />\n </div>\n <div class=\"p-4 pb-0 border-t border-node-component-border\">\n <WidgetInputNumberInput\n v-model=\"batchCount\"\n :widget=\"batchCountWidget\"\n class=\"*:[.min-w-56]:basis-0\"\n />\n <Button class=\"w-full mt-4\" @click=\"runButtonClick\">\n <i class=\"icon-[lucide--play]\" />\n {{ t('menu.run') }}\n </Button>\n </div>\n </div>\n </SplitterPanel>\n </Splitter>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { useEventListener, useScroll, whenever } from '@vueuse/core'\nimport Panel from 'primevue/panel'\nimport TabMenu from 'primevue/tabmenu'\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport DotSpinner from '@/components/common/DotSpinner.vue'\nimport HoneyToast from '@/components/honeyToast/HoneyToast.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useWorkflowService } from '@/platform/workflow/core/services/workflowService'\nimport { api } from '@/scripts/api'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'\nimport { useComfyManagerService } from '@/workbench/extensions/manager/services/comfyManagerService'\nimport { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'\n\nconst { t } = useI18n()\nconst comfyManagerStore = useComfyManagerStore()\nconst settingStore = useSettingStore()\nconst { runFullConflictAnalysis } = useConflictDetection()\n\nconst isExpanded = ref(false)\nconst activeTabIndex = ref(0)\n\nconst tabs = computed(() => [\n { label: t('manager.installationQueue') },\n {\n label: t('manager.failed', {\n count: comfyManagerStore.failedTasksIds.length\n })\n }\n])\n\nconst focusedLogs = computed(() => {\n if (activeTabIndex.value === 0) {\n return comfyManagerStore.succeededTasksLogs\n }\n return comfyManagerStore.failedTasksLogs\n})\n\nconst visible = computed(() => comfyManagerStore.taskLogs.length > 0)\n\nconst isRestarting = ref(false)\nconst isRestartCompleted = ref(false)\n\nconst isInProgress = computed(\n () => comfyManagerStore.isProcessingTasks || isRestarting.value\n)\n\nconst isTaskInProgress = (index: number) => {\n const log = focusedLogs.value[index]\n if (!log) return false\n\n const taskQueue = comfyManagerStore.taskQueue\n if (!taskQueue) return false\n\n const allQueueTasks = [\n ...(taskQueue.running_queue || []),\n ...(taskQueue.pending_queue || [])\n ]\n\n return allQueueTasks.some((task) => task.ui_id === log.taskId)\n}\n\nconst completedTasksCount = computed(() => {\n return (\n comfyManagerStore.succeededTasksIds.length +\n comfyManagerStore.failedTasksIds.length\n )\n})\n\nconst totalTasksCount = computed(() => {\n const completedTasks = Object.keys(comfyManagerStore.taskHistory).length\n const taskQueue = comfyManagerStore.taskQueue\n const queuedTasks = taskQueue\n ? (taskQueue.running_queue?.length || 0) +\n (taskQueue.pending_queue?.length || 0)\n : 0\n return completedTasks + queuedTasks\n})\n\nconst currentTaskName = computed(() => {\n if (isRestarting.value) {\n return t('manager.restartingBackend')\n }\n if (isRestartCompleted.value) {\n return t('manager.extensionsSuccessfullyInstalled')\n }\n if (!comfyManagerStore.taskLogs.length)\n return t('manager.installingDependencies')\n const task = comfyManagerStore.taskLogs.at(-1)\n return task?.taskName ?? t('manager.installingDependencies')\n})\n\nconst collapsedPanels = ref<Record<number, boolean>>({})\nfunction togglePanel(index: number) {\n collapsedPanels.value[index] = !collapsedPanels.value[index]\n}\n\nconst sectionsContainerRef = ref<HTMLElement | null>(null)\nconst { y: scrollY } = useScroll(sectionsContainerRef, {\n eventListenerOptions: { passive: true }\n})\n\nconst lastPanelRef = ref<HTMLElement | null>(null)\nconst isUserScrolling = ref(false)\nconst lastPanelLogs = computed(() => focusedLogs.value?.at(-1)?.logs)\n\nfunction isAtBottom(el: HTMLElement | null) {\n if (!el) return false\n const threshold = 20\n return Math.abs(el.scrollHeight - el.scrollTop - el.clientHeight) < threshold\n}\n\nfunction scrollLastPanelToBottom() {\n if (!lastPanelRef.value || isUserScrolling.value) return\n lastPanelRef.value.scrollTop = lastPanelRef.value.scrollHeight\n}\n\nfunction scrollContentToBottom() {\n scrollY.value = sectionsContainerRef.value?.scrollHeight ?? 0\n}\n\nfunction resetUserScrolling() {\n isUserScrolling.value = false\n}\n\nfunction handleScroll(e: Event) {\n const target = e.target as HTMLElement\n if (target !== lastPanelRef.value) return\n isUserScrolling.value = !isAtBottom(target)\n}\n\nfunction onLogsAdded() {\n if (isUserScrolling.value) return\n scrollLastPanelToBottom()\n}\n\nwhenever(lastPanelLogs, onLogsAdded, { flush: 'post', deep: true })\nwhenever(() => isExpanded.value, scrollContentToBottom)\nwhenever(() => !isExpanded.value, resetUserScrolling)\n\nfunction closeToast() {\n comfyManagerStore.resetTaskState()\n isExpanded.value = false\n}\n\nasync function handleRestart() {\n const originalToastSetting = settingStore.get(\n 'Comfy.Toast.DisableReconnectingToast'\n )\n\n try {\n await settingStore.set('Comfy.Toast.DisableReconnectingToast', true)\n\n isRestarting.value = true\n\n const onReconnect = async () => {\n try {\n comfyManagerStore.setStale()\n await useCommandStore().execute('Comfy.RefreshNodeDefinitions')\n await useWorkflowService().reloadCurrentWorkflow()\n void runFullConflictAnalysis()\n } finally {\n await settingStore.set(\n 'Comfy.Toast.DisableReconnectingToast',\n originalToastSetting\n )\n isRestarting.value = false\n isRestartCompleted.value = true\n\n setTimeout(() => {\n closeToast()\n }, 3000)\n }\n }\n\n useEventListener(api, 'reconnected', onReconnect, { once: true })\n\n await useComfyManagerService().rebootComfyUI()\n } catch (error) {\n await settingStore.set(\n 'Comfy.Toast.DisableReconnectingToast',\n originalToastSetting\n )\n isRestarting.value = false\n isRestartCompleted.value = false\n closeToast()\n throw error\n }\n}\n\nonMounted(() => {\n scrollContentToBottom()\n})\n\nonBeforeUnmount(() => {\n isExpanded.value = false\n})\n</script>\n\n<template>\n <HoneyToast v-model:expanded=\"isExpanded\" :visible>\n <template #default>\n <div v-if=\"isExpanded\" class=\"flex items-center px-4 py-2\">\n <TabMenu\n v-model:active-index=\"activeTabIndex\"\n :model=\"tabs\"\n class=\"w-full border-none\"\n :pt=\"{\n menu: { class: 'border-none' },\n menuitem: { class: 'font-medium' },\n action: { class: 'px-4 py-2' }\n }\"\n />\n </div>\n\n <div\n ref=\"sectionsContainerRef\"\n class=\"scroll-container max-h-[450px] overflow-y-auto px-6 py-4\"\n :style=\"{\n scrollbarWidth: 'thin',\n scrollbarColor: 'rgba(156, 163, 175, 0.5) transparent'\n }\"\n >\n <div v-for=\"(log, index) in focusedLogs\" :key=\"index\">\n <Panel\n :expanded=\"collapsedPanels[index] === true\"\n toggleable\n class=\"shadow-elevation-1 mt-2 rounded-lg\"\n >\n <template #header>\n <div class=\"flex w-full items-center justify-between py-2\">\n <div class=\"flex flex-col text-sm leading-normal font-medium\">\n <span>{{ log.taskName }}</span>\n <span class=\"text-muted\">\n {{\n isTaskInProgress(index)\n ? t('g.inProgress')\n : t('g.completed') + ' ✓'\n }}\n </span>\n </div>\n </div>\n </template>\n <template #toggleicon>\n <Button\n variant=\"textonly\"\n class=\"text-neutral-300\"\n @click=\"togglePanel(index)\"\n >\n <i\n :class=\"\n collapsedPanels[index]\n ? 'pi pi-chevron-right'\n : 'pi pi-chevron-down'\n \"\n />\n </Button>\n </template>\n <div\n :ref=\"\n index === focusedLogs.length - 1\n ? (el) => (lastPanelRef = el as HTMLElement)\n : undefined\n \"\n class=\"h-64 overflow-y-auto rounded-lg bg-black\"\n :class=\"{\n 'h-64': index !== focusedLogs.length - 1,\n grow: index === focusedLogs.length - 1\n }\"\n @scroll=\"handleScroll\"\n >\n <div class=\"h-full\">\n <div\n v-for=\"(logLine, logIndex) in log.logs\"\n :key=\"logIndex\"\n class=\"text-muted\"\n >\n <pre class=\"break-words whitespace-pre-wrap\">{{\n logLine\n }}</pre>\n </div>\n </div>\n </div>\n </Panel>\n </div>\n </div>\n </template>\n\n <template #footer=\"{ toggle }\">\n <div class=\"flex w-full items-center justify-between px-6 py-2 shadow-lg\">\n <div class=\"flex items-center text-base leading-none\">\n <div class=\"flex items-center\">\n <template v-if=\"isInProgress\">\n <DotSpinner duration=\"1s\" class=\"mr-2\" />\n <span>{{ currentTaskName }}</span>\n </template>\n <template v-else-if=\"isRestartCompleted\">\n <span class=\"mr-2\">🎉</span>\n <span>{{ currentTaskName }}</span>\n </template>\n <template v-else>\n <span class=\"mr-2\">✅</span>\n <span>{{ t('manager.restartToApplyChanges') }}</span>\n </template>\n </div>\n </div>\n <div class=\"flex items-center gap-4\">\n <span v-if=\"isInProgress\" class=\"text-sm text-muted-foreground\">\n {{ completedTasksCount }} {{ t('g.progressCountOf') }}\n {{ totalTasksCount }}\n </span>\n <div class=\"flex items-center\">\n <Button\n v-if=\"!isInProgress && !isRestartCompleted\"\n variant=\"secondary\"\n class=\"mr-4 rounded-full border-2 border-base-foreground px-3 text-base-foreground hover:bg-secondary-background-hover\"\n @click=\"handleRestart\"\n >\n {{ t('manager.applyChanges') }}\n </Button>\n <Button\n v-else-if=\"!isRestartCompleted\"\n variant=\"muted-textonly\"\n size=\"sm\"\n class=\"rounded-full font-bold\"\n :aria-label=\"\n t(isExpanded ? 'contextMenu.Collapse' : 'contextMenu.Expand')\n \"\n @click.stop=\"toggle\"\n >\n <i\n :class=\"isExpanded ? 'pi pi-chevron-up' : 'pi pi-chevron-down'\"\n />\n </Button>\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n class=\"rounded-full font-bold\"\n :aria-label=\"t('g.close')\"\n @click.stop=\"closeToast\"\n >\n <i class=\"pi pi-times\" />\n </Button>\n </div>\n </div>\n </div>\n </template>\n </HoneyToast>\n</template>\n","<template>\n <div class=\"comfyui-body grid h-full w-full overflow-hidden\">\n <div id=\"comfyui-body-top\" class=\"comfyui-body-top\" />\n <div id=\"comfyui-body-bottom\" class=\"comfyui-body-bottom\" />\n <div id=\"comfyui-body-left\" class=\"comfyui-body-left\" />\n <div id=\"comfyui-body-right\" class=\"comfyui-body-right\" />\n <div\n v-show=\"!linearMode\"\n id=\"graph-canvas-container\"\n ref=\"graphCanvasContainerRef\"\n class=\"graph-canvas-container\"\n >\n <GraphCanvas @ready=\"onGraphReady\" />\n </div>\n <LinearView v-if=\"linearMode\" />\n </div>\n\n <GlobalToast />\n <RerouteMigrationToast />\n <ModelImportProgressDialog />\n <ManagerProgressToast />\n <UnloadWindowConfirmDialog v-if=\"!isElectron()\" />\n <MenuHamburger />\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport { storeToRefs } from 'pinia'\nimport type { ToastMessageOptions } from 'primevue/toast'\nimport { useToast } from 'primevue/usetoast'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n watch,\n watchEffect\n} from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { runWhenGlobalIdle } from '@/base/common/async'\nimport MenuHamburger from '@/components/MenuHamburger.vue'\nimport UnloadWindowConfirmDialog from '@/components/dialog/UnloadWindowConfirmDialog.vue'\nimport GraphCanvas from '@/components/graph/GraphCanvas.vue'\nimport GlobalToast from '@/components/toast/GlobalToast.vue'\nimport RerouteMigrationToast from '@/components/toast/RerouteMigrationToast.vue'\nimport { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'\nimport { useCoreCommands } from '@/composables/useCoreCommands'\nimport { useErrorHandling } from '@/composables/useErrorHandling'\nimport { useProgressFavicon } from '@/composables/useProgressFavicon'\nimport { SERVER_CONFIG_ITEMS } from '@/constants/serverConfig'\nimport { i18n, loadLocale } from '@/i18n'\nimport ModelImportProgressDialog from '@/platform/assets/components/ModelImportProgressDialog.vue'\nimport { isCloud } from '@/platform/distribution/types'\nimport { useSettingStore } from '@/platform/settings/settingStore'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useFrontendVersionMismatchWarning } from '@/platform/updates/common/useFrontendVersionMismatchWarning'\nimport { useVersionCompatibilityStore } from '@/platform/updates/common/versionCompatibilityStore'\nimport { useCanvasStore } from '@/renderer/core/canvas/canvasStore'\nimport type { StatusWsMessageStatus } from '@/schemas/apiSchema'\nimport { api } from '@/scripts/api'\nimport { app } from '@/scripts/app'\nimport { setupAutoQueueHandler } from '@/services/autoQueueService'\nimport { useKeybindingService } from '@/services/keybindingService'\nimport { useAssetsStore } from '@/stores/assetsStore'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useExecutionStore } from '@/stores/executionStore'\nimport { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'\nimport { useMenuItemStore } from '@/stores/menuItemStore'\nimport { useModelStore } from '@/stores/modelStore'\nimport { useNodeDefStore, useNodeFrequencyStore } from '@/stores/nodeDefStore'\nimport {\n useQueuePendingTaskCountStore,\n useQueueStore\n} from '@/stores/queueStore'\nimport { useServerConfigStore } from '@/stores/serverConfigStore'\nimport { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'\nimport { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'\nimport { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'\nimport { useWorkspaceStore } from '@/stores/workspaceStore'\nimport { electronAPI, isElectron } from '@/utils/envUtil'\nimport LinearView from '@/views/LinearView.vue'\nimport ManagerProgressToast from '@/workbench/extensions/manager/components/ManagerProgressToast.vue'\n\nsetupAutoQueueHandler()\nuseProgressFavicon()\nuseBrowserTabTitle()\n\nconst { t } = useI18n()\nconst toast = useToast()\nconst settingStore = useSettingStore()\nconst executionStore = useExecutionStore()\nconst colorPaletteStore = useColorPaletteStore()\nconst queueStore = useQueueStore()\nconst assetsStore = useAssetsStore()\nconst versionCompatibilityStore = useVersionCompatibilityStore()\nconst graphCanvasContainerRef = ref<HTMLDivElement | null>(null)\nconst { linearMode } = storeToRefs(useCanvasStore())\n\nconst telemetry = useTelemetry()\nconst firebaseAuthStore = useFirebaseAuthStore()\nlet hasTrackedLogin = false\nlet visibilityListener: (() => void) | null = null\nlet tabCountInterval: number | null = null\nlet tabCountChannel: BroadcastChannel | null = null\n\nwatch(\n () => colorPaletteStore.completedActivePalette,\n (newTheme) => {\n const DARK_THEME_CLASS = 'dark-theme'\n if (newTheme.light_theme) {\n document.body.classList.remove(DARK_THEME_CLASS)\n } else {\n document.body.classList.add(DARK_THEME_CLASS)\n }\n\n if (isElectron()) {\n electronAPI().changeTheme({\n color: 'rgba(0, 0, 0, 0)',\n symbolColor: newTheme.colors.comfy_base['input-text']\n })\n }\n },\n { immediate: true }\n)\n\nif (isElectron()) {\n watch(\n () => queueStore.tasks,\n (newTasks, oldTasks) => {\n // Report tasks that previously running but are now completed (i.e. in history)\n const oldRunningTaskIds = new Set(\n oldTasks.filter((task) => task.isRunning).map((task) => task.promptId)\n )\n newTasks\n .filter(\n (task) => oldRunningTaskIds.has(task.promptId) && task.isHistory\n )\n .forEach((task) => {\n electronAPI().Events.incrementUserProperty(\n `execution:${task.displayStatus.toLowerCase()}`,\n 1\n )\n electronAPI().Events.trackEvent('execution', {\n status: task.displayStatus.toLowerCase()\n })\n })\n },\n { deep: true }\n )\n}\n\nwatchEffect(() => {\n const fontSize = settingStore.get('Comfy.TextareaWidget.FontSize')\n document.documentElement.style.setProperty(\n '--comfy-textarea-font-size',\n `${fontSize}px`\n )\n})\n\nwatchEffect(() => {\n const padding = settingStore.get('Comfy.TreeExplorer.ItemPadding')\n document.documentElement.style.setProperty(\n '--comfy-tree-explorer-item-padding',\n `${padding}px`\n )\n})\n\nwatchEffect(async () => {\n const locale = settingStore.get('Comfy.Locale')\n if (locale) {\n // Load the locale dynamically if not already loaded\n try {\n await loadLocale(locale)\n // Type assertion is safe here as loadLocale validates the locale exists\n i18n.global.locale.value = locale as typeof i18n.global.locale.value\n } catch (error) {\n console.error(`Failed to switch to locale \"${locale}\":`, error)\n }\n }\n})\n\nconst useNewMenu = computed(() => {\n return settingStore.get('Comfy.UseNewMenu')\n})\nwatchEffect(() => {\n if (useNewMenu.value === 'Disabled') {\n app.ui.menuContainer.style.setProperty('display', 'block')\n app.ui.restoreMenuPosition()\n } else {\n app.ui.menuContainer.style.setProperty('display', 'none')\n }\n})\n\nwatchEffect(() => {\n queueStore.maxHistoryItems = settingStore.get('Comfy.Queue.MaxHistoryItems')\n})\n\nconst init = () => {\n const coreCommands = useCoreCommands()\n useCommandStore().registerCommands(coreCommands)\n useMenuItemStore().registerCoreMenuCommands()\n useKeybindingService().registerCoreKeybindings()\n useSidebarTabStore().registerCoreSidebarTabs()\n useBottomPanelStore().registerCoreBottomPanelTabs()\n app.extensionManager = useWorkspaceStore()\n}\n\nconst queuePendingTaskCountStore = useQueuePendingTaskCountStore()\nconst sidebarTabStore = useSidebarTabStore()\n\nconst onStatus = async (e: CustomEvent<StatusWsMessageStatus>) => {\n queuePendingTaskCountStore.update(e)\n await queueStore.update()\n // Only update assets if the assets sidebar is currently open\n // When sidebar is closed, AssetsSidebarTab.vue will refresh on mount\n if (sidebarTabStore.activeSidebarTabId === 'assets') {\n await assetsStore.updateHistory()\n }\n}\n\nconst onExecutionSuccess = async () => {\n await queueStore.update()\n // Only update assets if the assets sidebar is currently open\n // When sidebar is closed, AssetsSidebarTab.vue will refresh on mount\n if (sidebarTabStore.activeSidebarTabId === 'assets') {\n await assetsStore.updateHistory()\n }\n}\n\nconst reconnectingMessage: ToastMessageOptions = {\n severity: 'error',\n summary: t('g.reconnecting')\n}\n\nconst onReconnecting = () => {\n if (!settingStore.get('Comfy.Toast.DisableReconnectingToast')) {\n toast.remove(reconnectingMessage)\n toast.add(reconnectingMessage)\n }\n}\n\nconst onReconnected = () => {\n if (!settingStore.get('Comfy.Toast.DisableReconnectingToast')) {\n toast.remove(reconnectingMessage)\n toast.add({\n severity: 'success',\n summary: t('g.reconnected'),\n life: 2000\n })\n }\n}\n\nonMounted(() => {\n api.addEventListener('status', onStatus)\n api.addEventListener('execution_success', onExecutionSuccess)\n api.addEventListener('reconnecting', onReconnecting)\n api.addEventListener('reconnected', onReconnected)\n executionStore.bindExecutionEvents()\n\n try {\n init()\n // Relocate the legacy menu container to the graph canvas container so it is below other elements\n graphCanvasContainerRef.value?.prepend(app.ui.menuContainer)\n } catch (e) {\n console.error('Failed to init ComfyUI frontend', e)\n }\n})\n\nonBeforeUnmount(() => {\n api.removeEventListener('status', onStatus)\n api.removeEventListener('execution_success', onExecutionSuccess)\n api.removeEventListener('reconnecting', onReconnecting)\n api.removeEventListener('reconnected', onReconnected)\n executionStore.unbindExecutionEvents()\n\n // Clean up page visibility listener\n if (visibilityListener) {\n document.removeEventListener('visibilitychange', visibilityListener)\n visibilityListener = null\n }\n\n // Clean up tab count tracking\n if (tabCountInterval) {\n window.clearInterval(tabCountInterval)\n tabCountInterval = null\n }\n if (tabCountChannel) {\n tabCountChannel.close()\n tabCountChannel = null\n }\n})\n\nuseEventListener(window, 'keydown', useKeybindingService().keybindHandler)\n\nconst { wrapWithErrorHandling, wrapWithErrorHandlingAsync } = useErrorHandling()\n\n// Initialize version mismatch warning in setup context\n// It will be triggered automatically when the store is ready\nuseFrontendVersionMismatchWarning({ immediate: true })\n\nvoid nextTick(() => {\n versionCompatibilityStore.initialize().catch((error) => {\n console.warn('Version compatibility check failed:', error)\n })\n})\n\nconst onGraphReady = () => {\n runWhenGlobalIdle(() => {\n // Track user login when app is ready in graph view (cloud only)\n if (isCloud && firebaseAuthStore.isAuthenticated && !hasTrackedLogin) {\n telemetry?.trackUserLoggedIn()\n hasTrackedLogin = true\n }\n\n // Set up page visibility tracking (cloud only)\n if (isCloud && telemetry && !visibilityListener) {\n visibilityListener = () => {\n telemetry.trackPageVisibilityChanged({\n visibility_state: document.visibilityState as 'visible' | 'hidden'\n })\n }\n document.addEventListener('visibilitychange', visibilityListener)\n }\n\n // Set up tab count tracking (cloud only)\n if (isCloud && telemetry && !tabCountInterval) {\n tabCountChannel = new BroadcastChannel('comfyui-tab-count')\n const activeTabs = new Map<string, number>()\n const currentTabId = crypto.randomUUID()\n\n // Listen for heartbeats from other tabs\n tabCountChannel.onmessage = (event) => {\n if (\n event.data.type === 'heartbeat' &&\n event.data.tabId !== currentTabId\n ) {\n activeTabs.set(event.data.tabId, Date.now())\n }\n }\n\n // 5-minute heartbeat interval\n tabCountInterval = window.setInterval(() => {\n const now = Date.now()\n\n // Clean up stale tabs (no heartbeat for 45 seconds)\n activeTabs.forEach((lastHeartbeat, tabId) => {\n if (now - lastHeartbeat > 45000) {\n activeTabs.delete(tabId)\n }\n })\n\n // Broadcast our heartbeat\n tabCountChannel?.postMessage({ type: 'heartbeat', tabId: currentTabId })\n\n // Track tab count (include current tab)\n const tabCount = activeTabs.size + 1\n telemetry.trackTabCount({ tab_count: tabCount })\n }, 60000 * 5)\n\n // Send initial heartbeat\n tabCountChannel.postMessage({ type: 'heartbeat', tabId: currentTabId })\n }\n\n // Setting values now available after comfyApp.setup.\n // Load keybindings.\n wrapWithErrorHandling(useKeybindingService().registerUserKeybindings)()\n\n // Load server config\n wrapWithErrorHandling(useServerConfigStore().loadServerConfig)(\n SERVER_CONFIG_ITEMS,\n settingStore.get('Comfy.Server.ServerConfigValues')\n )\n\n // Load model folders\n void wrapWithErrorHandlingAsync(useModelStore().loadModelFolders)()\n\n // Non-blocking load of node frequencies\n void wrapWithErrorHandlingAsync(\n useNodeFrequencyStore().loadNodeFrequencies\n )()\n\n // Node defs now available after comfyApp.setup.\n // Explicitly initialize nodeSearchService to avoid indexing delay when\n // node search is triggered\n useNodeDefStore().nodeSearchService.searchNode('')\n }, 1000)\n}\n</script>\n\n<style scoped>\n.comfyui-body {\n grid-template-columns: auto 1fr auto;\n grid-template-rows: auto 1fr auto;\n}\n\n/**\n +------------------+------------------+------------------+\n | |\n | .comfyui-body- |\n | top |\n | (spans all cols) |\n | |\n +------------------+------------------+------------------+\n | | | |\n | .comfyui-body- | #graph-canvas | .comfyui-body- |\n | left | | right |\n | | | |\n | | | |\n +------------------+------------------+------------------+\n | |\n | .comfyui-body- |\n | bottom |\n | (spans all cols) |\n | |\n +------------------+------------------+------------------+\n*/\n\n.comfyui-body-top {\n order: -5;\n /* Span across all columns */\n grid-column: 1/-1;\n /* Position at the first row */\n grid-row: 1;\n /* Top menu bar dropdown needs to be above of graph canvas splitter overlay which is z-index: 999 */\n /* Top menu bar z-index needs to be higher than bottom menu bar z-index as by default\n pysssss's image feed is located at body-bottom, and it can overlap with the queue button, which\n is located in body-top. */\n z-index: 1001;\n display: flex;\n flex-direction: column;\n}\n\n.comfyui-body-left {\n order: -4;\n /* Position in the first column */\n grid-column: 1;\n /* Position below the top element */\n grid-row: 2;\n z-index: 10;\n display: flex;\n}\n\n.graph-canvas-container {\n width: 100%;\n height: 100%;\n order: -3;\n grid-column: 2;\n grid-row: 2;\n position: relative;\n overflow: clip;\n}\n\n.comfyui-body-right {\n order: -2;\n z-index: 10;\n grid-column: 3;\n grid-row: 2;\n}\n\n.comfyui-body-bottom {\n order: 4;\n /* Span across all columns */\n grid-column: 1/-1;\n grid-row: 3;\n /* Bottom menu bar dropdown needs to be above of graph canvas splitter overlay which is z-index: 999 */\n z-index: 1000;\n display: flex;\n flex-direction: column;\n}\n</style>\n"],"file":"GraphView-0U0T8pwU.js"}