comfyui-frontend-package 1.37.1__py3-none-any.whl → 1.37.2__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 (99) hide show
  1. comfyui_frontend_package/static/assets/{AboutPanel-CTGWwQqp.js → AboutPanel-DLChcmDc.js} +2 -2
  2. comfyui_frontend_package/static/assets/{AboutPanel-CTGWwQqp.js.map → AboutPanel-DLChcmDc.js.map} +1 -1
  3. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-DDwwIMxr.js → AudioPreviewPlayer-B8RfUAL6.js} +2 -2
  4. comfyui_frontend_package/static/assets/{AudioPreviewPlayer-DDwwIMxr.js.map → AudioPreviewPlayer-B8RfUAL6.js.map} +1 -1
  5. comfyui_frontend_package/static/assets/{ComfyQueueButton-BDRXGOWF.js → ComfyQueueButton-DRgA2g3T.js} +2 -2
  6. comfyui_frontend_package/static/assets/{ComfyQueueButton-BDRXGOWF.js.map → ComfyQueueButton-DRgA2g3T.js.map} +1 -1
  7. comfyui_frontend_package/static/assets/{ExtensionPanel-Dxe7qElb.js → ExtensionPanel-CpMR9h6B.js} +2 -2
  8. comfyui_frontend_package/static/assets/{ExtensionPanel-Dxe7qElb.js.map → ExtensionPanel-CpMR9h6B.js.map} +1 -1
  9. comfyui_frontend_package/static/assets/{GraphView-Cx4wYzNo.js → GraphView-xK7kCVVH.js} +5 -5
  10. comfyui_frontend_package/static/assets/{GraphView-Cx4wYzNo.js.map → GraphView-xK7kCVVH.js.map} +1 -1
  11. comfyui_frontend_package/static/assets/{KeybindingPanel-pmyMPZHn.js → KeybindingPanel-CM_PwegN.js} +2 -2
  12. comfyui_frontend_package/static/assets/{KeybindingPanel-pmyMPZHn.js.map → KeybindingPanel-CM_PwegN.js.map} +1 -1
  13. comfyui_frontend_package/static/assets/{LazyImage.vue_vue_type_script_setup_true_lang-C3ynRbso.js → LazyImage.vue_vue_type_script_setup_true_lang-Dza6G4hd.js} +2 -2
  14. comfyui_frontend_package/static/assets/{LazyImage.vue_vue_type_script_setup_true_lang-C3ynRbso.js.map → LazyImage.vue_vue_type_script_setup_true_lang-Dza6G4hd.js.map} +1 -1
  15. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-DSos04Q3.js → LegacyCreditsPanel-BujZmWgC.js} +2 -2
  16. comfyui_frontend_package/static/assets/{LegacyCreditsPanel-DSos04Q3.js.map → LegacyCreditsPanel-BujZmWgC.js.map} +1 -1
  17. comfyui_frontend_package/static/assets/{Load3D-Cu5zG2Cw.js → Load3D-DWafZUud.js} +2 -2
  18. comfyui_frontend_package/static/assets/Load3D-DWafZUud.js.map +1 -0
  19. comfyui_frontend_package/static/assets/{Load3D.vue_vue_type_script_setup_true_lang-BJ0IqFdp.js → Load3D.vue_vue_type_script_setup_true_lang-_XswHXHt.js} +2 -2
  20. comfyui_frontend_package/static/assets/{Load3D.vue_vue_type_script_setup_true_lang-BJ0IqFdp.js.map → Load3D.vue_vue_type_script_setup_true_lang-_XswHXHt.js.map} +1 -1
  21. comfyui_frontend_package/static/assets/{Media3DBottom-DsMr4v-s.js → Media3DBottom-Z9nkVIUT.js} +2 -2
  22. comfyui_frontend_package/static/assets/{Media3DBottom-DsMr4v-s.js.map → Media3DBottom-Z9nkVIUT.js.map} +1 -1
  23. comfyui_frontend_package/static/assets/{Media3DTop-DQ18QW3M.js → Media3DTop-DKaMli8A.js} +2 -2
  24. comfyui_frontend_package/static/assets/{Media3DTop-DQ18QW3M.js.map → Media3DTop-DKaMli8A.js.map} +1 -1
  25. comfyui_frontend_package/static/assets/{MediaAudioBottom-99GB7NqQ.js → MediaAudioBottom-B62n8OnR.js} +2 -2
  26. comfyui_frontend_package/static/assets/{MediaAudioBottom-99GB7NqQ.js.map → MediaAudioBottom-B62n8OnR.js.map} +1 -1
  27. comfyui_frontend_package/static/assets/{MediaImageBottom-C6Bpu_d9.js → MediaImageBottom-DChcPA0h.js} +2 -2
  28. comfyui_frontend_package/static/assets/{MediaImageBottom-C6Bpu_d9.js.map → MediaImageBottom-DChcPA0h.js.map} +1 -1
  29. comfyui_frontend_package/static/assets/{MediaTitle.vue_vue_type_script_setup_true_lang-BFgOqxto.js → MediaTitle.vue_vue_type_script_setup_true_lang-CFkifLSX.js} +2 -2
  30. comfyui_frontend_package/static/assets/{MediaTitle.vue_vue_type_script_setup_true_lang-BFgOqxto.js.map → MediaTitle.vue_vue_type_script_setup_true_lang-CFkifLSX.js.map} +1 -1
  31. comfyui_frontend_package/static/assets/{MediaVideoBottom-DgdszioB.js → MediaVideoBottom-fWb8qHcu.js} +2 -2
  32. comfyui_frontend_package/static/assets/{MediaVideoBottom-DgdszioB.js.map → MediaVideoBottom-fWb8qHcu.js.map} +1 -1
  33. comfyui_frontend_package/static/assets/{ServerConfigPanel-BlC22rDX.js → ServerConfigPanel-umnHESaV.js} +2 -2
  34. comfyui_frontend_package/static/assets/{ServerConfigPanel-BlC22rDX.js.map → ServerConfigPanel-umnHESaV.js.map} +1 -1
  35. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-BdL9OjAj.js +2 -0
  36. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-BdL9OjAj.js.map +1 -0
  37. comfyui_frontend_package/static/assets/{UserPanel-Ccmz94Hx.js → UserPanel-CwkZrKjy.js} +2 -2
  38. comfyui_frontend_package/static/assets/{UserPanel-Ccmz94Hx.js.map → UserPanel-CwkZrKjy.js.map} +1 -1
  39. comfyui_frontend_package/static/assets/{UserSelectView-DVFY57_D.js → UserSelectView-TeLhcKfi.js} +2 -2
  40. comfyui_frontend_package/static/assets/{UserSelectView-DVFY57_D.js.map → UserSelectView-TeLhcKfi.js.map} +1 -1
  41. comfyui_frontend_package/static/assets/{ValueControlPopover-BLxzZY7u.js → ValueControlPopover-CSvGimoz.js} +2 -2
  42. comfyui_frontend_package/static/assets/{ValueControlPopover-BLxzZY7u.js.map → ValueControlPopover-CSvGimoz.js.map} +1 -1
  43. comfyui_frontend_package/static/assets/{WidgetAudioUI-BtX1BUnO.js → WidgetAudioUI-CfZQ32fr.js} +2 -2
  44. comfyui_frontend_package/static/assets/{WidgetAudioUI-BtX1BUnO.js.map → WidgetAudioUI-CfZQ32fr.js.map} +1 -1
  45. comfyui_frontend_package/static/assets/{WidgetButton-CyIVF8iQ.js → WidgetButton-Bo9rWiUg.js} +2 -2
  46. comfyui_frontend_package/static/assets/{WidgetButton-CyIVF8iQ.js.map → WidgetButton-Bo9rWiUg.js.map} +1 -1
  47. comfyui_frontend_package/static/assets/{WidgetColorPicker-BcGFRyIl.js → WidgetColorPicker-CyNNmSC9.js} +2 -2
  48. comfyui_frontend_package/static/assets/{WidgetColorPicker-BcGFRyIl.js.map → WidgetColorPicker-CyNNmSC9.js.map} +1 -1
  49. comfyui_frontend_package/static/assets/{WidgetGalleria-CfluZbRG.js → WidgetGalleria-X2v42F2a.js} +2 -2
  50. comfyui_frontend_package/static/assets/{WidgetGalleria-CfluZbRG.js.map → WidgetGalleria-X2v42F2a.js.map} +1 -1
  51. comfyui_frontend_package/static/assets/WidgetInputNumber-BL6P8as9.js +2 -0
  52. comfyui_frontend_package/static/assets/WidgetInputNumber-BL6P8as9.js.map +1 -0
  53. comfyui_frontend_package/static/assets/{WidgetInputNumber.vue_vue_type_script_setup_true_lang-Bmxd6b-H.js → WidgetInputNumber.vue_vue_type_script_setup_true_lang-DPaOKLY2.js} +2 -2
  54. comfyui_frontend_package/static/assets/{WidgetInputNumber.vue_vue_type_script_setup_true_lang-Bmxd6b-H.js.map → WidgetInputNumber.vue_vue_type_script_setup_true_lang-DPaOKLY2.js.map} +1 -1
  55. comfyui_frontend_package/static/assets/{WidgetInputText-CecuaK0n.js → WidgetInputText-LR89p-m1.js} +2 -2
  56. comfyui_frontend_package/static/assets/{WidgetInputText-CecuaK0n.js.map → WidgetInputText-LR89p-m1.js.map} +1 -1
  57. comfyui_frontend_package/static/assets/{WidgetLayoutField.vue_vue_type_script_setup_true_lang-CbjST6rI.js → WidgetLayoutField.vue_vue_type_script_setup_true_lang-C5dBI8au.js} +2 -2
  58. comfyui_frontend_package/static/assets/{WidgetLayoutField.vue_vue_type_script_setup_true_lang-CbjST6rI.js.map → WidgetLayoutField.vue_vue_type_script_setup_true_lang-C5dBI8au.js.map} +1 -1
  59. comfyui_frontend_package/static/assets/{WidgetLegacy-B8P9bOsC.js → WidgetLegacy-CJpsLPyI.js} +2 -2
  60. comfyui_frontend_package/static/assets/WidgetLegacy-CJpsLPyI.js.map +1 -0
  61. comfyui_frontend_package/static/assets/{WidgetMarkdown-nXPgrFZi.js → WidgetMarkdown-DrH35WSE.js} +2 -2
  62. comfyui_frontend_package/static/assets/{WidgetMarkdown-nXPgrFZi.js.map → WidgetMarkdown-DrH35WSE.js.map} +1 -1
  63. comfyui_frontend_package/static/assets/{WidgetRecordAudio-C6urheNU.js → WidgetRecordAudio-DL4kMpXw.js} +2 -2
  64. comfyui_frontend_package/static/assets/{WidgetRecordAudio-C6urheNU.js.map → WidgetRecordAudio-DL4kMpXw.js.map} +1 -1
  65. comfyui_frontend_package/static/assets/WidgetSelect-Bk32AcQe.js +2 -0
  66. comfyui_frontend_package/static/assets/WidgetSelect-Bk32AcQe.js.map +1 -0
  67. comfyui_frontend_package/static/assets/{WidgetSelect.vue_vue_type_script_setup_true_lang-DTcBUlhn.js → WidgetSelect.vue_vue_type_script_setup_true_lang-CHjOuVyH.js} +2 -2
  68. comfyui_frontend_package/static/assets/{WidgetSelect.vue_vue_type_script_setup_true_lang-DTcBUlhn.js.map → WidgetSelect.vue_vue_type_script_setup_true_lang-CHjOuVyH.js.map} +1 -1
  69. comfyui_frontend_package/static/assets/{WidgetTextarea-DuXhEnsH.js → WidgetTextarea-orpeeVx4.js} +2 -2
  70. comfyui_frontend_package/static/assets/{WidgetTextarea-DuXhEnsH.js.map → WidgetTextarea-orpeeVx4.js.map} +1 -1
  71. comfyui_frontend_package/static/assets/{WidgetToggleSwitch-BTeDQHuf.js → WidgetToggleSwitch-CSN313_V.js} +2 -2
  72. comfyui_frontend_package/static/assets/{WidgetToggleSwitch-BTeDQHuf.js.map → WidgetToggleSwitch-CSN313_V.js.map} +1 -1
  73. comfyui_frontend_package/static/assets/{WidgetWithControl.vue_vue_type_script_setup_true_lang-DFvQ9Mdf.js → WidgetWithControl.vue_vue_type_script_setup_true_lang-CU_cmbAA.js} +3 -3
  74. comfyui_frontend_package/static/assets/{WidgetWithControl.vue_vue_type_script_setup_true_lang-DFvQ9Mdf.js.map → WidgetWithControl.vue_vue_type_script_setup_true_lang-CU_cmbAA.js.map} +1 -1
  75. comfyui_frontend_package/static/assets/{audioService-B43wn81n.js → audioService-DvqSt3rU.js} +2 -2
  76. comfyui_frontend_package/static/assets/{audioService-B43wn81n.js.map → audioService-DvqSt3rU.js.map} +1 -1
  77. comfyui_frontend_package/static/assets/{audioUtils-iqnmct8N.js → audioUtils-bxVioEE3.js} +2 -2
  78. comfyui_frontend_package/static/assets/{audioUtils-iqnmct8N.js.map → audioUtils-bxVioEE3.js.map} +1 -1
  79. comfyui_frontend_package/static/assets/{index-FoJ8Eu4a.js → index-BODInyrK.js} +21 -21
  80. comfyui_frontend_package/static/assets/{index-FoJ8Eu4a.js.map → index-BODInyrK.js.map} +1 -1
  81. comfyui_frontend_package/static/assets/{index-BbFKUlCt.js → index-CyS0uSKu.js} +2 -2
  82. comfyui_frontend_package/static/assets/{index-BbFKUlCt.js.map → index-CyS0uSKu.js.map} +1 -1
  83. comfyui_frontend_package/static/assets/{index-B2FA5n0E.js → index-fwtcK193.js} +2 -2
  84. comfyui_frontend_package/static/assets/{index-B2FA5n0E.js.map → index-fwtcK193.js.map} +1 -1
  85. comfyui_frontend_package/static/assets/{keybindingService-D_lPZZ7u.js → keybindingService-DR_nPGKY.js} +2 -2
  86. comfyui_frontend_package/static/assets/{keybindingService-D_lPZZ7u.js.map → keybindingService-DR_nPGKY.js.map} +1 -1
  87. comfyui_frontend_package/static/index.html +1 -1
  88. {comfyui_frontend_package-1.37.1.dist-info → comfyui_frontend_package-1.37.2.dist-info}/METADATA +1 -1
  89. {comfyui_frontend_package-1.37.1.dist-info → comfyui_frontend_package-1.37.2.dist-info}/RECORD +91 -91
  90. comfyui_frontend_package/static/assets/Load3D-Cu5zG2Cw.js.map +0 -1
  91. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-CY-BY6v5.js +0 -2
  92. comfyui_frontend_package/static/assets/SubscriptionRequiredDialogContent-CY-BY6v5.js.map +0 -1
  93. comfyui_frontend_package/static/assets/WidgetInputNumber-BTFa3T0H.js +0 -2
  94. comfyui_frontend_package/static/assets/WidgetInputNumber-BTFa3T0H.js.map +0 -1
  95. comfyui_frontend_package/static/assets/WidgetLegacy-B8P9bOsC.js.map +0 -1
  96. comfyui_frontend_package/static/assets/WidgetSelect-VznDfSeg.js +0 -2
  97. comfyui_frontend_package/static/assets/WidgetSelect-VznDfSeg.js.map +0 -1
  98. {comfyui_frontend_package-1.37.1.dist-info → comfyui_frontend_package-1.37.2.dist-info}/WHEEL +0 -0
  99. {comfyui_frontend_package-1.37.1.dist-info → comfyui_frontend_package-1.37.2.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"LazyImage.vue_vue_type_script_setup_true_lang-C3ynRbso.js","sources":["../../src/composables/useFeatureFlags.ts","../../src/components/input/SingleSelect.vue","../../src/platform/assets/composables/useModelTypes.ts","../../src/platform/assets/components/UploadModelConfirmation.vue","../../src/platform/assets/components/VideoHelpDialog.vue","../../src/platform/assets/components/UploadModelFooter.vue","../../src/platform/assets/components/UploadModelUrlInput.vue","../../src/platform/assets/components/UploadModelUrlInputCivitai.vue","../../src/platform/assets/importSources/civitaiImportSource.ts","../../src/platform/assets/importSources/huggingfaceImportSource.ts","../../src/platform/assets/utils/importSourceUtil.ts","../../src/platform/assets/composables/useUploadModelWizard.ts","../../src/platform/assets/components/UploadModelDialog.vue","../../../../../../../assets/images/civitai.svg","../../src/platform/assets/components/UploadModelDialogHeader.vue","../../src/platform/assets/components/UploadModelUpgradeModalBody.vue","../../src/platform/assets/components/UploadModelUpgradeModalFooter.vue","../../src/platform/assets/components/UploadModelUpgradeModal.vue","../../src/platform/assets/components/UploadModelUpgradeModalHeader.vue","../../src/platform/assets/composables/useModelUpload.ts","../../../../../../../assets/images/default-template.png","../../src/composables/useIntersectionObserver.ts","../../src/services/mediaCacheService.ts","../../src/components/common/LazyImage.vue"],"sourcesContent":["import { computed, reactive, readonly } from 'vue'\n\nimport { remoteConfig } from '@/platform/remoteConfig/remoteConfig'\nimport { api } from '@/scripts/api'\n\n/**\n * Known server feature flags (top-level, not extensions)\n */\nexport enum ServerFeatureFlag {\n SUPPORTS_PREVIEW_METADATA = 'supports_preview_metadata',\n MAX_UPLOAD_SIZE = 'max_upload_size',\n MANAGER_SUPPORTS_V4 = 'extension.manager.supports_v4',\n MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled',\n ASSET_UPDATE_OPTIONS_ENABLED = 'asset_update_options_enabled',\n PRIVATE_MODELS_ENABLED = 'private_models_enabled',\n ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled',\n HUGGINGFACE_MODEL_IMPORT_ENABLED = 'huggingface_model_import_enabled'\n}\n\n/**\n * Composable for reactive access to server-side feature flags\n */\nexport function useFeatureFlags() {\n const flags = reactive({\n get supportsPreviewMetadata() {\n return api.getServerFeature(ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA)\n },\n get maxUploadSize() {\n return api.getServerFeature(ServerFeatureFlag.MAX_UPLOAD_SIZE)\n },\n get supportsManagerV4() {\n return api.getServerFeature(ServerFeatureFlag.MANAGER_SUPPORTS_V4)\n },\n get modelUploadButtonEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.model_upload_button_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.MODEL_UPLOAD_BUTTON_ENABLED,\n false\n )\n )\n },\n get assetUpdateOptionsEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.asset_update_options_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.ASSET_UPDATE_OPTIONS_ENABLED,\n false\n )\n )\n },\n get privateModelsEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.private_models_enabled ??\n api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false)\n )\n },\n get onboardingSurveyEnabled() {\n return (\n remoteConfig.value.onboarding_survey_enabled ??\n api.getServerFeature(ServerFeatureFlag.ONBOARDING_SURVEY_ENABLED, true)\n )\n },\n get huggingfaceModelImportEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.huggingface_model_import_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.HUGGINGFACE_MODEL_IMPORT_ENABLED,\n false\n )\n )\n }\n })\n\n const featureFlag = <T = unknown>(featurePath: string, defaultValue?: T) =>\n computed(() => api.getServerFeature(featurePath, defaultValue))\n\n return {\n flags: readonly(flags),\n featureFlag\n }\n}\n","<template>\n <!--\n Note: We explicitly pass options here (not just via $attrs) because:\n 1. Our custom value template needs options to look up labels from values\n 2. PrimeVue's value slot only provides 'value' and 'placeholder', not the selected item's label\n 3. We need to maintain the icon slot functionality in the value template\n option-label=\"name\" is required because our option template directly accesses option.name\n -->\n <Select\n v-model=\"selectedItem\"\n v-bind=\"$attrs\"\n :options=\"options\"\n option-label=\"name\"\n option-value=\"value\"\n unstyled\n :pt=\"{\n root: ({ props }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: [\n // container\n 'h-10 relative inline-flex cursor-pointer select-none items-center',\n // trigger surface\n 'rounded-lg',\n 'bg-secondary-background text-base-foreground',\n 'border-[2.5px] border-solid border-transparent',\n 'transition-all duration-200 ease-in-out',\n 'focus-within:border-node-component-border',\n // disabled\n { 'opacity-60 cursor-default': props.disabled }\n ]\n }),\n label: {\n class:\n // Align with MultiSelect labelContainer spacing\n 'flex-1 flex items-center whitespace-nowrap pl-4 py-2 outline-hidden'\n },\n dropdown: {\n class:\n // Right chevron touch area\n 'flex shrink-0 items-center justify-center px-3 py-2'\n },\n overlay: {\n class: cn(\n 'mt-2 p-2 rounded-lg',\n 'bg-base-background text-base-foreground',\n 'border border-solid border-border-default'\n )\n },\n listContainer: () => ({\n style: `max-height: min(${listMaxHeight}, 50vh)`,\n class: 'scrollbar-custom'\n }),\n list: {\n class:\n // Same list tone/size as MultiSelect\n 'flex flex-col gap-0 p-0 m-0 list-none border-none text-sm'\n },\n option: ({ context }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: cn(\n // Row layout\n 'flex items-center justify-between gap-3 px-2 py-3 rounded',\n 'hover:bg-secondary-background-hover',\n // Add focus state for keyboard navigation\n context.focused && 'bg-secondary-background-hover',\n // Selected state + check icon\n context.selected &&\n 'bg-secondary-background-selected hover:bg-secondary-background-selected'\n )\n }),\n optionLabel: {\n class: 'truncate'\n },\n optionGroupLabel: {\n class: 'px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground'\n },\n emptyMessage: {\n class: 'px-3 py-2 text-sm text-muted-foreground'\n }\n }\"\n :aria-label=\"label || t('g.singleSelectDropdown')\"\n role=\"combobox\"\n :aria-expanded=\"false\"\n aria-haspopup=\"listbox\"\n :tabindex=\"0\"\n >\n <!-- Trigger value -->\n <template #value=\"slotProps\">\n <div class=\"flex items-center gap-2 text-sm\">\n <slot name=\"icon\" />\n <span\n v-if=\"slotProps.value !== null && slotProps.value !== undefined\"\n class=\"text-base-foreground\"\n >\n {{ getLabel(slotProps.value) }}\n </span>\n <span v-else class=\"text-base-foreground\">\n {{ label }}\n </span>\n </div>\n </template>\n\n <!-- Trigger caret -->\n <template #dropdownicon>\n <i class=\"icon-[lucide--chevron-down] text-muted-foreground\" />\n </template>\n\n <!-- Option row -->\n <template #option=\"{ option, selected }\">\n <div\n class=\"flex w-full items-center justify-between gap-3\"\n :style=\"optionStyle\"\n >\n <span class=\"truncate\">{{ option.name }}</span>\n <i v-if=\"selected\" class=\"icon-[lucide--check] text-base-foreground\" />\n </div>\n </template>\n </Select>\n</template>\n\n<script setup lang=\"ts\">\nimport type { SelectPassThroughMethodOptions } from 'primevue/select'\nimport Select from 'primevue/select'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nimport type { SelectOption } from './types'\n\ndefineOptions({\n inheritAttrs: false\n})\n\nconst {\n label,\n options,\n listMaxHeight = '28rem',\n popoverMinWidth,\n popoverMaxWidth\n} = defineProps<{\n label?: string\n /**\n * Required for displaying the selected item's label.\n * Cannot rely on $attrs alone because we need to access options\n * in getLabel() to map values to their display names.\n */\n options?: SelectOption[]\n /** Maximum height of the dropdown panel (default: 28rem) */\n listMaxHeight?: string\n /** Minimum width of the popover (default: auto) */\n popoverMinWidth?: string\n /** Maximum width of the popover (default: auto) */\n popoverMaxWidth?: string\n}>()\n\nconst selectedItem = defineModel<string | undefined>({ required: true })\n\nconst { t } = useI18n()\n\n/**\n * Maps a value to its display label.\n * Necessary because PrimeVue's value slot doesn't provide the selected item's label,\n * only the raw value. We need this to show the correct text when an item is selected.\n */\nconst getLabel = (val: string | null | undefined) => {\n if (val == null) return label ?? ''\n if (!options) return label ?? ''\n const found = options.find((o) => o.value === val)\n return found ? found.name : (label ?? '')\n}\n\n// Extract complex style logic from template\nconst optionStyle = computed(() => {\n if (!popoverMinWidth && !popoverMaxWidth) return undefined\n\n const styles: string[] = []\n if (popoverMinWidth) styles.push(`min-width: ${popoverMinWidth}`)\n if (popoverMaxWidth) styles.push(`max-width: ${popoverMaxWidth}`)\n\n return styles.join('; ')\n})\n</script>\n","import { createSharedComposable, useAsyncState } from '@vueuse/core'\n\nimport { api } from '@/scripts/api'\n\n/**\n * Format folder name to display name\n * Converts \"upscale_models\" -> \"Upscale Model\"\n * Converts \"loras\" -> \"LoRA\"\n */\nfunction formatDisplayName(folderName: string): string {\n // Special cases for acronyms and proper nouns\n const specialCases: Record<string, string> = {\n loras: 'LoRA',\n ipadapter: 'IP-Adapter',\n sams: 'SAM',\n clip_vision: 'CLIP Vision',\n animatediff_motion_lora: 'AnimateDiff Motion LoRA',\n animatediff_models: 'AnimateDiff Model',\n vae: 'VAE',\n sam2: 'SAM 2',\n controlnet: 'ControlNet',\n gligen: 'GLIGEN'\n }\n\n if (specialCases[folderName]) {\n return specialCases[folderName]\n }\n\n return folderName\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\ninterface ModelTypeOption {\n name: string // Display name\n value: string // Actual tag value\n}\n\nconst DISALLOWED_MODEL_TYPES = ['nlf'] as const\n\n/**\n * Composable for fetching and managing model types from the API\n * Uses shared state to ensure data is only fetched once\n */\nexport const useModelTypes = createSharedComposable(() => {\n const {\n state: modelTypes,\n isLoading,\n error,\n execute: fetchModelTypes\n } = useAsyncState(\n async (): Promise<ModelTypeOption[]> => {\n const response = await api.getModelFolders()\n return response\n .filter(\n (folder) =>\n !DISALLOWED_MODEL_TYPES.includes(\n folder.name as (typeof DISALLOWED_MODEL_TYPES)[number]\n )\n )\n .map((folder) => ({\n name: formatDisplayName(folder.name),\n value: folder.name\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n },\n [] as ModelTypeOption[],\n {\n immediate: false,\n onError: (err) => {\n console.error('Failed to fetch model types:', err)\n }\n }\n )\n\n return {\n modelTypes,\n isLoading,\n error,\n fetchModelTypes\n }\n})\n","<template>\n <div class=\"flex flex-col gap-4 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.modelAssociatedWithLink') }}\n </p>\n <div\n class=\"flex items-center gap-3 bg-secondary-background p-3 rounded-lg\"\n >\n <img\n v-if=\"previewImage\"\n :src=\"previewImage\"\n :alt=\"metadata?.filename || metadata?.name || 'Model preview'\"\n class=\"w-14 h-14 rounded object-cover flex-shrink-0\"\n />\n <p class=\"m-0 text-base-foreground\">\n {{ metadata?.filename || metadata?.name }}\n </p>\n </div>\n </div>\n\n <!-- Model Type Selection -->\n <div class=\"flex flex-col gap-2\">\n <label class=\"\">\n {{ $t('assetBrowser.modelTypeSelectorLabel') }}\n </label>\n <SingleSelect\n v-model=\"modelValue\"\n :label=\"\n isLoading\n ? $t('g.loading')\n : $t('assetBrowser.modelTypeSelectorPlaceholder')\n \"\n :options=\"modelTypes\"\n :disabled=\"isLoading\"\n data-attr=\"upload-model-step2-type-selector\"\n />\n <div class=\"flex items-center gap-2\">\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.notSureLeaveAsIs') }}</span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport SingleSelect from '@/components/input/SingleSelect.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\n\ndefineProps<{\n metadata?: AssetMetadata\n previewImage?: string\n}>()\n\nconst modelValue = defineModel<string | undefined>()\n\nconst { modelTypes, isLoading } = useModelTypes()\n</script>\n","<template>\n <Dialog\n v-model:visible=\"isVisible\"\n modal\n :closable=\"false\"\n :close-on-escape=\"false\"\n :dismissable-mask=\"true\"\n :pt=\"{\n root: { class: 'video-help-dialog' },\n header: { class: '!hidden' },\n content: { class: '!p-0' },\n mask: { class: '!bg-black/70' }\n }\"\n :style=\"{ width: '90vw', maxWidth: '800px' }\"\n >\n <div class=\"relative\">\n <Button\n variant=\"textonly\"\n size=\"icon\"\n class=\"absolute top-4 right-6 z-10\"\n :aria-label=\"$t('g.close')\"\n @click=\"isVisible = false\"\n >\n <i class=\"pi pi-times text-sm\" />\n </Button>\n <video\n autoplay\n muted\n loop\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-lg\"\n :src=\"videoUrl\"\n >\n {{ $t('g.videoFailedToLoad') }}\n </video>\n </div>\n </Dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport Dialog from 'primevue/dialog'\nimport { onWatcherCleanup, watch } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\n\nconst isVisible = defineModel<boolean>({ required: true })\n\nconst { videoUrl, ariaLabel = 'Help video' } = defineProps<{\n videoUrl: string\n ariaLabel?: string\n}>()\n\nconst handleEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.stopImmediatePropagation()\n event.stopPropagation()\n event.preventDefault()\n isVisible.value = false\n }\n}\n\n// Add listener with capture phase to intercept before parent dialogs\n// Only active when dialog is visible\nwatch(\n isVisible,\n (visible) => {\n if (visible) {\n const stop = useEventListener(document, 'keydown', handleEscapeKey, {\n capture: true\n })\n onWatcherCleanup(stop)\n }\n },\n { immediate: true }\n)\n</script>\n","<template>\n <div class=\"flex justify-end gap-2 w-full\">\n <div\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n class=\"mr-auto flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--circle-question-mark] text-muted-foreground\" />\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-civitai\"\n @click=\"showCivitaiHelp = true\"\n >\n {{ $t('assetBrowser.providerCivitai') }}\n </Button>\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-huggingface\"\n @click=\"showHuggingFaceHelp = true\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </Button>\n </div>\n <Button\n v-else-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n class=\"mr-auto underline\"\n data-attr=\"upload-model-step1-help-link\"\n @click=\"showCivitaiHelp = true\"\n >\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.uploadModelHowDoIFindThis') }}</span>\n </Button>\n <Button\n v-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n data-attr=\"upload-model-step1-cancel-button\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('close')\"\n >\n {{ $t('g.cancel') }}\n </Button>\n <Button\n v-if=\"currentStep !== 1 && currentStep !== 3\"\n variant=\"muted-textonly\"\n size=\"lg\"\n :data-attr=\"`upload-model-step${currentStep}-back-button`\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('back')\"\n >\n {{ $t('g.back') }}\n </Button>\n <span v-else />\n\n <Button\n v-if=\"currentStep === 1\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step1-continue-button\"\n :disabled=\"!canFetchMetadata || isFetchingMetadata\"\n @click=\"emit('fetchMetadata')\"\n >\n <i\n v-if=\"isFetchingMetadata\"\n class=\"icon-[lucide--loader-circle] animate-spin\"\n />\n <span>{{ $t('g.continue') }}</span>\n </Button>\n <Button\n v-else-if=\"currentStep === 2\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step2-confirm-button\"\n :disabled=\"!canUploadModel || isUploading\"\n @click=\"emit('upload')\"\n >\n <i v-if=\"isUploading\" class=\"icon-[lucide--loader-circle] animate-spin\" />\n <span>{{ $t('assetBrowser.upload') }}</span>\n </Button>\n <Button\n v-else-if=\"currentStep === 3 && uploadStatus === 'success'\"\n variant=\"secondary\"\n data-attr=\"upload-model-step3-finish-button\"\n @click=\"emit('close')\"\n >\n {{ $t('assetBrowser.finish') }}\n </Button>\n <VideoHelpDialog\n v-model=\"showCivitaiHelp\"\n video-url=\"https://media.comfy.org/compressed_768/civitai_howto.webm\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n <VideoHelpDialog\n v-model=\"showHuggingFaceHelp\"\n video-url=\"https://media.comfy.org/byom/huggingfacehowto.mp4\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport VideoHelpDialog from '@/platform/assets/components/VideoHelpDialog.vue'\n\nconst { flags } = useFeatureFlags()\n\nconst showCivitaiHelp = ref(false)\nconst showHuggingFaceHelp = ref(false)\n\ndefineProps<{\n currentStep: number\n isFetchingMetadata: boolean\n isUploading: boolean\n canFetchMetadata: boolean\n canUploadModel: boolean\n uploadStatus: 'idle' | 'uploading' | 'success' | 'error'\n}>()\n\nconst emit = defineEmits<{\n (e: 'back'): void\n (e: 'fetchMetadata'): void\n (e: 'upload'): void\n (e: 'close'): void\n}>()\n</script>\n","<template>\n <div class=\"flex flex-col justify-between h-full gap-6 text-sm\">\n <div class=\"flex flex-col gap-6\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0 text-foreground\">\n {{ $t('assetBrowser.uploadModelDescription1Generic') }}\n </p>\n <div class=\"m-0\">\n <p class=\"m-0 text-muted-foreground\">\n {{ $t('assetBrowser.uploadModelDescription2Generic') }}\n </p>\n <span class=\"inline-flex items-center gap-1 flex-wrap mt-2\">\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"civitaiIcon\"\n :alt=\"$t('assetBrowser.providerCivitai')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"civitaiUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerCivitai') }}</a\n ><span>,</span>\n </span>\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"huggingFaceIcon\"\n :alt=\"$t('assetBrowser.providerHuggingFace')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"huggingFaceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </a>\n </span>\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.genericLinkPlaceholder')\"\n class=\"w-full bg-secondary-background border-0 p-4\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <p v-else class=\"text-foreground\">\n <i18n-t keypath=\"assetBrowser.maxFileSize\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </p>\n </div>\n </div>\n\n <div class=\"text-sm text-muted\">\n {{ $t('assetBrowser.uploadModelHelpFooterText') }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n modelValue: string\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst url = computed({\n get: () => props.modelValue,\n set: (value: string) => emit('update:modelValue', value)\n})\n\nconst civitaiIcon = '/assets/images/civitai.svg'\nconst civitaiUrl = 'https://civitai.com/models'\nconst huggingFaceIcon = '/assets/images/hf-logo.svg'\nconst huggingFaceUrl = 'https://huggingface.co'\n</script>\n","<template>\n <div class=\"flex flex-col gap-6 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.uploadModelDescription1') }}\n </p>\n <ul class=\"list-disc space-y-1 pl-5 mt-0\">\n <li>\n <i18n-t keypath=\"assetBrowser.uploadModelDescription2\" tag=\"span\">\n <template #link>\n <a\n href=\"https://civitai.com/models\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.uploadModelDescription2Link') }}\n </a>\n </template>\n </i18n-t>\n </li>\n <li>\n <i18n-t keypath=\"assetBrowser.uploadModelDescription3\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </li>\n </ul>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <i18n-t keypath=\"assetBrowser.civitaiLinkLabel\" tag=\"label\" class=\"mb-0\">\n <template #download>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.civitaiLinkLabelDownload')\n }}</span>\n </template>\n </i18n-t>\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.civitaiLinkPlaceholder')\"\n class=\"w-full bg-secondary-background border-0 p-4\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <i18n-t\n v-else\n keypath=\"assetBrowser.civitaiLinkExample\"\n tag=\"p\"\n class=\"text-xs\"\n >\n <template #example>\n <strong>{{ $t('assetBrowser.civitaiLinkExampleStrong') }}</strong>\n </template>\n <template #link>\n <a\n href=\"https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.civitaiLinkExampleUrl') }}\n </a>\n </template>\n </i18n-t>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\n\ndefineProps<{\n error?: string\n}>()\n\nconst url = defineModel<string>({ required: true })\n</script>\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Civitai model import source configuration\n */\nexport const civitaiImportSource: ImportSource = {\n type: 'civitai',\n name: 'Civitai',\n hostnames: ['civitai.com']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Hugging Face model import source configuration\n */\nexport const huggingfaceImportSource: ImportSource = {\n type: 'huggingface',\n name: 'Hugging Face',\n hostnames: ['huggingface.co']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Check if a URL belongs to a specific import source\n */\nexport function validateSourceUrl(url: string, source: ImportSource): boolean {\n try {\n const hostname = new URL(url).hostname.toLowerCase()\n return source.hostnames.some(\n (h) => hostname === h || hostname.endsWith(`.${h}`)\n )\n } catch {\n return false\n }\n}\n","import type { Ref } from 'vue'\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { st } from '@/i18n'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { huggingfaceImportSource } from '@/platform/assets/importSources/huggingfaceImportSource'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\nimport { assetService } from '@/platform/assets/services/assetService'\nimport type { ImportSource } from '@/platform/assets/types/importSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\nimport { useAssetsStore } from '@/stores/assetsStore'\nimport { useModelToNodeStore } from '@/stores/modelToNodeStore'\n\ninterface WizardData {\n url: string\n metadata?: AssetMetadata\n name: string\n tags: string[]\n previewImage?: string\n}\n\ninterface ModelTypeOption {\n name: string\n value: string\n}\n\nexport function useUploadModelWizard(modelTypes: Ref<ModelTypeOption[]>) {\n const { t } = useI18n()\n const assetsStore = useAssetsStore()\n const modelToNodeStore = useModelToNodeStore()\n const { flags } = useFeatureFlags()\n const currentStep = ref(1)\n const isFetchingMetadata = ref(false)\n const isUploading = ref(false)\n const uploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle')\n const uploadError = ref('')\n\n const wizardData = ref<WizardData>({\n url: '',\n name: '',\n tags: []\n })\n\n const selectedModelType = ref<string>()\n\n // Available import sources\n const importSources: ImportSource[] = flags.huggingfaceModelImportEnabled\n ? [civitaiImportSource, huggingfaceImportSource]\n : [civitaiImportSource]\n\n // Detected import source based on URL\n const detectedSource = computed(() => {\n const url = wizardData.value.url.trim()\n if (!url) return null\n return (\n importSources.find((source) => validateSourceUrl(url, source)) ?? null\n )\n })\n\n // Clear error when URL changes\n watch(\n () => wizardData.value.url,\n () => {\n uploadError.value = ''\n }\n )\n\n // Validation\n const canFetchMetadata = computed(() => {\n return wizardData.value.url.trim().length > 0\n })\n\n const canUploadModel = computed(() => {\n return !!selectedModelType.value\n })\n\n async function fetchMetadata() {\n if (!canFetchMetadata.value) return\n\n // Clean and normalize URL\n let cleanedUrl = wizardData.value.url.trim()\n try {\n cleanedUrl = new URL(encodeURI(cleanedUrl)).toString()\n } catch {\n // If URL parsing fails, just use the trimmed input\n }\n wizardData.value.url = cleanedUrl\n\n // Validate URL belongs to a supported import source\n const source = detectedSource.value\n if (!source) {\n const supportedSources = importSources.map((s) => s.name).join(', ')\n uploadError.value = t('assetBrowser.unsupportedUrlSource', {\n sources: supportedSources\n })\n return\n }\n\n isFetchingMetadata.value = true\n try {\n const metadata = await assetService.getAssetMetadata(wizardData.value.url)\n\n // Decode URL-encoded filenames (e.g., Chinese characters)\n if (metadata.filename) {\n try {\n metadata.filename = decodeURIComponent(metadata.filename)\n } catch {\n // Keep original if decoding fails\n }\n }\n if (metadata.name) {\n try {\n metadata.name = decodeURIComponent(metadata.name)\n } catch {\n // Keep original if decoding fails\n }\n }\n\n wizardData.value.metadata = metadata\n\n // Pre-fill name from metadata\n wizardData.value.name = metadata.filename || metadata.name || ''\n\n // Store preview image if available\n wizardData.value.previewImage = metadata.preview_image\n\n // Pre-fill model type from metadata tags if available\n if (metadata.tags && metadata.tags.length > 0) {\n wizardData.value.tags = metadata.tags\n // Try to detect model type from tags\n const typeTag = metadata.tags.find((tag) =>\n modelTypes.value.some((type) => type.value === tag)\n )\n if (typeTag) {\n selectedModelType.value = typeTag\n }\n }\n\n currentStep.value = 2\n } catch (error) {\n console.error('Failed to retrieve metadata:', error)\n uploadError.value =\n error instanceof Error\n ? error.message\n : st(\n 'assetBrowser.uploadModelFailedToRetrieveMetadata',\n 'Failed to retrieve metadata. Please check the link and try again.'\n )\n currentStep.value = 1\n } finally {\n isFetchingMetadata.value = false\n }\n }\n\n async function uploadModel() {\n if (!canUploadModel.value) return\n\n // Defensive check: detectedSource should be valid after fetchMetadata validation,\n // but guard against edge cases (e.g., URL modified between steps)\n const source = detectedSource.value\n if (!source) {\n uploadError.value = t('assetBrowser.noValidSourceDetected')\n return false\n }\n\n isUploading.value = true\n uploadStatus.value = 'uploading'\n\n try {\n const tags = selectedModelType.value\n ? ['models', selectedModelType.value]\n : ['models']\n const filename =\n wizardData.value.metadata?.filename ||\n wizardData.value.metadata?.name ||\n 'model'\n\n let previewId: string | undefined\n\n // Upload preview image first if available\n if (wizardData.value.previewImage) {\n try {\n const baseFilename = filename.split('.')[0]\n\n // Extract extension from data URL MIME type\n let extension = 'png'\n const mimeMatch = wizardData.value.previewImage.match(\n /^data:image\\/([^;]+);/\n )\n if (mimeMatch) {\n extension = mimeMatch[1] === 'jpeg' ? 'jpg' : mimeMatch[1]\n }\n\n const previewAsset = await assetService.uploadAssetFromBase64({\n data: wizardData.value.previewImage,\n name: `${baseFilename}_preview.${extension}`,\n tags: ['preview']\n })\n previewId = previewAsset.id\n } catch (error) {\n console.error('Failed to upload preview image:', error)\n // Continue with model upload even if preview fails\n }\n }\n\n await assetService.uploadAssetFromUrl({\n url: wizardData.value.url,\n name: filename,\n tags,\n user_metadata: {\n source: source.type,\n source_url: wizardData.value.url,\n model_type: selectedModelType.value\n },\n preview_id: previewId\n })\n\n uploadStatus.value = 'success'\n currentStep.value = 3\n\n // Refresh model caches for all node types that use this model category\n if (selectedModelType.value) {\n const providers = modelToNodeStore.getAllNodeProviders(\n selectedModelType.value\n )\n await Promise.all(\n providers.map((provider) =>\n assetsStore.updateModelsForNodeType(provider.nodeDef.name)\n )\n )\n }\n\n return true\n } catch (error) {\n console.error('Failed to upload asset:', error)\n uploadStatus.value = 'error'\n uploadError.value =\n error instanceof Error ? error.message : 'Failed to upload model'\n currentStep.value = 3\n return false\n } finally {\n isUploading.value = false\n }\n }\n\n function goToPreviousStep() {\n if (currentStep.value > 1) {\n currentStep.value = currentStep.value - 1\n }\n }\n\n return {\n // State\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n\n // Computed\n canFetchMetadata,\n canUploadModel,\n detectedSource,\n\n // Actions\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n }\n}\n","<template>\n <div\n class=\"upload-model-dialog flex flex-col justify-between gap-6 p-4 pt-6 border-t border-border-default\"\n >\n <!-- Step 1: Enter URL -->\n <UploadModelUrlInput\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n class=\"flex-1\"\n />\n <UploadModelUrlInputCivitai\n v-else-if=\"currentStep === 1\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n />\n\n <!-- Step 2: Confirm Metadata -->\n <UploadModelConfirmation\n v-else-if=\"currentStep === 2\"\n v-model=\"selectedModelType\"\n :metadata=\"wizardData.metadata\"\n :preview-image=\"wizardData.previewImage\"\n />\n\n <!-- Step 3: Upload Progress -->\n <UploadModelProgress\n v-else-if=\"currentStep === 3\"\n :status=\"uploadStatus\"\n :error=\"uploadError\"\n :metadata=\"wizardData.metadata\"\n :model-type=\"selectedModelType\"\n :preview-image=\"wizardData.previewImage\"\n />\n\n <!-- Navigation Footer -->\n <UploadModelFooter\n :current-step=\"currentStep\"\n :is-fetching-metadata=\"isFetchingMetadata\"\n :is-uploading=\"isUploading\"\n :can-fetch-metadata=\"canFetchMetadata\"\n :can-upload-model=\"canUploadModel\"\n :upload-status=\"uploadStatus\"\n @back=\"goToPreviousStep\"\n @fetch-metadata=\"handleFetchMetadata\"\n @upload=\"handleUploadModel\"\n @close=\"handleClose\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelConfirmation from '@/platform/assets/components/UploadModelConfirmation.vue'\nimport UploadModelFooter from '@/platform/assets/components/UploadModelFooter.vue'\nimport UploadModelProgress from '@/platform/assets/components/UploadModelProgress.vue'\nimport UploadModelUrlInput from '@/platform/assets/components/UploadModelUrlInput.vue'\nimport UploadModelUrlInputCivitai from '@/platform/assets/components/UploadModelUrlInputCivitai.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport { useUploadModelWizard } from '@/platform/assets/composables/useUploadModelWizard'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst { flags } = useFeatureFlags()\nconst dialogStore = useDialogStore()\nconst { modelTypes, fetchModelTypes } = useModelTypes()\n\nconst emit = defineEmits<{\n 'upload-success': []\n}>()\n\nconst {\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n canFetchMetadata,\n canUploadModel,\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n} = useUploadModelWizard(modelTypes)\n\nasync function handleFetchMetadata() {\n await fetchMetadata()\n}\n\nasync function handleUploadModel() {\n const success = await uploadModel()\n if (success) {\n emit('upload-success')\n }\n}\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model' })\n}\n\nonMounted(() => {\n fetchModelTypes()\n})\n</script>\n\n<style scoped>\n.upload-model-dialog {\n width: 90vw;\n max-width: 800px;\n min-height: 400px;\n}\n\n@media (min-width: 640px) {\n .upload-model-dialog {\n width: auto;\n min-width: 600px;\n }\n}\n</style>\n","export default \"__VITE_PUBLIC_ASSET__53f47e52__\"","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <img\n v-if=\"!flags.huggingfaceModelImportEnabled\"\n src=\"/assets/images/civitai.svg\"\n class=\"size-4\"\n />\n <span>{{ $t(titleKey) }}</span>\n <span\n class=\"rounded-full bg-white px-1.5 py-0 text-xxs font-inter font-semibold uppercase text-black\"\n >\n {{ $t('g.beta') }}\n </span>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\n\nconst { flags } = useFeatureFlags()\n\nconst titleKey = computed(() => {\n return flags.huggingfaceModelImportEnabled\n ? 'assetBrowser.uploadModelGeneric'\n : 'assetBrowser.uploadModelFromCivitai'\n})\n</script>\n","<template>\n <div\n class=\"flex flex-1 flex-col items-center justify-center text-base text-muted-foreground\"\n >\n <p class=\"m-0 max-w-md\">\n {{ $t('assetBrowser.upgradeFeatureDescription') }}\n </p>\n </div>\n</template>\n","<template>\n <div class=\"flex flex-wrap justify-end gap-2 w-full\">\n <a\n href=\"https://blog.comfy.org/p/comfy-cloud-new-features-and-pricing\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted-foreground mr-auto underline flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--external-link]\" />\n <span>{{ $t('g.learnMore') }}</span>\n </a>\n <Button variant=\"textonly\" @click=\"emit('close')\">{{\n $t('g.close')\n }}</Button>\n <Button variant=\"secondary\" @click=\"emit('subscribe')\">\n {{ $t('subscription.required.subscribe') }}\n </Button>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\n\nconst emit = defineEmits<{\n close: []\n subscribe: []\n}>()\n</script>\n","<template>\n <div\n class=\"flex flex-col justify-between gap-10 p-4 border-t border-border-default w-auto max-w-[min(500px,90vw)]\"\n >\n <UploadModelUpgradeModalBody />\n\n <UploadModelUpgradeModalFooter\n @close=\"handleClose\"\n @subscribe=\"handleSubscribe\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport UploadModelUpgradeModalBody from '@/platform/assets/components/UploadModelUpgradeModalBody.vue'\nimport UploadModelUpgradeModalFooter from '@/platform/assets/components/UploadModelUpgradeModalFooter.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst dialogStore = useDialogStore()\nconst { showSubscriptionDialog } = useSubscription()\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model-upgrade' })\n}\n\nfunction handleSubscribe() {\n showSubscriptionDialog()\n}\n</script>\n","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <span>{{ $t('assetBrowser.upgradeToUnlockFeature') }}</span>\n </div>\n</template>\n","import { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelDialog from '@/platform/assets/components/UploadModelDialog.vue'\nimport UploadModelDialogHeader from '@/platform/assets/components/UploadModelDialogHeader.vue'\nimport UploadModelUpgradeModal from '@/platform/assets/components/UploadModelUpgradeModal.vue'\nimport UploadModelUpgradeModalHeader from '@/platform/assets/components/UploadModelUpgradeModalHeader.vue'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport type { UseAsyncStateReturn } from '@vueuse/core'\nimport { computed } from 'vue'\n\nexport function useModelUpload(\n execute?: UseAsyncStateReturn<AssetItem[], [], true>['execute']\n) {\n const dialogStore = useDialogStore()\n const { flags } = useFeatureFlags()\n const isUploadButtonEnabled = computed(() => flags.modelUploadButtonEnabled)\n\n function showUploadDialog() {\n if (!flags.privateModelsEnabled) {\n // Show upgrade modal if private models are disabled\n dialogStore.showDialog({\n key: 'upload-model-upgrade',\n headerComponent: UploadModelUpgradeModalHeader,\n component: UploadModelUpgradeModal,\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0!'\n }\n }\n })\n } else {\n // Show regular upload modal\n dialogStore.showDialog({\n key: 'upload-model',\n headerComponent: UploadModelDialogHeader,\n component: UploadModelDialog,\n props: {\n onUploadSuccess: async () => {\n await execute?.()\n }\n },\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0!'\n }\n }\n })\n }\n }\n return { isUploadButtonEnabled, showUploadDialog }\n}\n","export default \"__VITE_PUBLIC_ASSET__c8cdd13f__\"","import { onBeforeUnmount, ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\ninterface UseIntersectionObserverOptions extends IntersectionObserverInit {\n immediate?: boolean\n}\n\nexport function useIntersectionObserver(\n target: Ref<Element | null>,\n callback: IntersectionObserverCallback,\n options: UseIntersectionObserverOptions = {}\n) {\n const { immediate = true, ...observerOptions } = options\n\n const isSupported =\n typeof window !== 'undefined' && 'IntersectionObserver' in window\n const isIntersecting = ref(false)\n\n let observer: IntersectionObserver | null = null\n\n const cleanup = () => {\n if (observer) {\n observer.disconnect()\n observer = null\n }\n }\n\n const observe = () => {\n cleanup()\n\n if (!isSupported || !target.value) return\n\n observer = new IntersectionObserver((entries) => {\n isIntersecting.value = entries.some((entry) => entry.isIntersecting)\n callback(entries, observer!)\n }, observerOptions)\n\n observer.observe(target.value)\n }\n\n const unobserve = () => {\n if (observer && target.value) {\n observer.unobserve(target.value)\n }\n }\n\n if (immediate) {\n watch(target, observe, { immediate: true, flush: 'post' })\n }\n\n onBeforeUnmount(cleanup)\n\n return {\n isSupported,\n isIntersecting,\n observe,\n unobserve,\n cleanup\n }\n}\n","import { reactive } from 'vue'\n\ninterface CachedMedia {\n src: string\n blob?: Blob\n objectUrl?: string\n error?: boolean\n isLoading: boolean\n lastAccessed: number\n}\n\ninterface MediaCacheOptions {\n maxSize?: number\n maxAge?: number // in milliseconds\n preloadDistance?: number // pixels from viewport\n}\n\nclass MediaCacheService {\n public cache = reactive(new Map<string, CachedMedia>())\n private readonly maxSize: number\n private readonly maxAge: number\n private cleanupInterval: number | null = null\n private urlRefCount = new Map<string, number>()\n\n constructor(options: MediaCacheOptions = {}) {\n this.maxSize = options.maxSize ?? 100\n this.maxAge = options.maxAge ?? 30 * 60 * 1000 // 30 minutes\n\n // Start cleanup interval\n this.startCleanupInterval()\n }\n\n private startCleanupInterval() {\n // Clean up every 5 minutes\n this.cleanupInterval = window.setInterval(\n () => {\n this.cleanup()\n },\n 5 * 60 * 1000\n )\n }\n\n private cleanup() {\n const now = Date.now()\n const keysToDelete: string[] = []\n\n // Find expired entries\n for (const [key, entry] of Array.from(this.cache.entries())) {\n if (now - entry.lastAccessed > this.maxAge) {\n // Only revoke object URL if no components are using it\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n keysToDelete.push(key)\n }\n // Don't delete cache entry if URL is still in use\n } else {\n keysToDelete.push(key)\n }\n }\n }\n\n // Remove expired entries\n keysToDelete.forEach((key) => this.cache.delete(key))\n\n // If still over size limit, remove oldest entries that aren't in use\n if (this.cache.size > this.maxSize) {\n const entries = Array.from(this.cache.entries())\n entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)\n\n let removedCount = 0\n const targetRemoveCount = this.cache.size - this.maxSize\n\n for (const [key, entry] of entries) {\n if (removedCount >= targetRemoveCount) break\n\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n this.cache.delete(key)\n removedCount++\n }\n } else {\n this.cache.delete(key)\n removedCount++\n }\n }\n }\n }\n\n async getCachedMedia(src: string): Promise<CachedMedia> {\n let entry = this.cache.get(src)\n\n if (entry) {\n // Update last accessed time\n entry.lastAccessed = Date.now()\n return entry\n }\n\n // Create new entry\n entry = {\n src,\n isLoading: true,\n lastAccessed: Date.now()\n }\n\n // Update cache with loading entry\n this.cache.set(src, entry)\n\n try {\n // Fetch the media\n const response = await fetch(src, { cache: 'force-cache' })\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.status}`)\n }\n\n const blob = await response.blob()\n const objectUrl = URL.createObjectURL(blob)\n\n // Update entry with successful result\n const updatedEntry: CachedMedia = {\n src,\n blob,\n objectUrl,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, updatedEntry)\n return updatedEntry\n } catch (error) {\n console.warn('Failed to cache media:', src, error)\n\n // Update entry with error\n const errorEntry: CachedMedia = {\n src,\n error: true,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, errorEntry)\n return errorEntry\n }\n }\n\n acquireUrl(src: string): string | undefined {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const currentCount = this.urlRefCount.get(entry.objectUrl) || 0\n this.urlRefCount.set(entry.objectUrl, currentCount + 1)\n return entry.objectUrl\n }\n return undefined\n }\n\n releaseUrl(src: string): void {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const count = (this.urlRefCount.get(entry.objectUrl) || 1) - 1\n if (count <= 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n // Remove from cache as well\n this.cache.delete(src)\n } else {\n this.urlRefCount.set(entry.objectUrl, count)\n }\n }\n }\n\n clearCache() {\n // Revoke all object URLs\n for (const entry of Array.from(this.cache.values())) {\n if (entry.objectUrl) {\n URL.revokeObjectURL(entry.objectUrl)\n }\n }\n this.cache.clear()\n this.urlRefCount.clear()\n }\n\n destroy() {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval)\n this.cleanupInterval = null\n }\n this.clearCache()\n }\n}\n\n// Global instance\nlet mediaCacheInstance: MediaCacheService | null = null\n\nexport function useMediaCache(options?: MediaCacheOptions) {\n if (!mediaCacheInstance) {\n mediaCacheInstance = new MediaCacheService(options)\n }\n\n const getCachedMedia = (src: string) =>\n mediaCacheInstance!.getCachedMedia(src)\n const clearCache = () => mediaCacheInstance!.clearCache()\n const acquireUrl = (src: string) => mediaCacheInstance!.acquireUrl(src)\n const releaseUrl = (src: string) => mediaCacheInstance!.releaseUrl(src)\n\n return {\n getCachedMedia,\n clearCache,\n acquireUrl,\n releaseUrl,\n cache: mediaCacheInstance.cache\n }\n}\n\n// Cleanup on page unload\nif (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', () => {\n if (mediaCacheInstance) {\n mediaCacheInstance.destroy()\n }\n })\n}\n","<template>\n <div\n ref=\"containerRef\"\n class=\"relative flex h-full w-full items-center justify-center overflow-hidden\"\n :class=\"containerClass\"\n >\n <Skeleton\n v-if=\"!isImageLoaded\"\n width=\"100%\"\n height=\"100%\"\n class=\"absolute inset-0\"\n />\n <img\n v-if=\"cachedSrc\"\n :src=\"cachedSrc\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n @load=\"onImageLoad\"\n @error=\"onImageError\"\n />\n <div\n v-if=\"hasError\"\n class=\"absolute inset-0 flex items-center justify-center\"\n >\n <img\n src=\"/assets/images/default-template.png\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\nimport { computed, onUnmounted, ref, watch } from 'vue'\n\nimport { useIntersectionObserver } from '@/composables/useIntersectionObserver'\nimport { useMediaCache } from '@/services/mediaCacheService'\nimport type { ClassValue } from '@/utils/tailwindUtil'\n\nconst {\n src,\n alt = '',\n containerClass = '',\n imageClass = '',\n imageStyle,\n rootMargin = '300px'\n} = defineProps<{\n src: string\n alt?: string\n containerClass?: ClassValue\n imageClass?: ClassValue\n imageStyle?: Record<string, any>\n rootMargin?: string\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nconst isIntersecting = ref(false)\nconst isImageLoaded = ref(false)\nconst hasError = ref(false)\nconst cachedSrc = ref<string | undefined>(undefined)\n\nconst { getCachedMedia, acquireUrl, releaseUrl } = useMediaCache()\n\n// Use intersection observer to detect when the image container comes into view\nuseIntersectionObserver(\n containerRef,\n (entries) => {\n const entry = entries[0]\n isIntersecting.value = entry?.isIntersecting ?? false\n },\n {\n rootMargin,\n threshold: 0.1\n }\n)\n\n// Only start loading the image when it's in view\nconst shouldLoad = computed(() => isIntersecting.value)\n\nwatch(\n shouldLoad,\n async (shouldLoadVal) => {\n if (shouldLoadVal && src && !cachedSrc.value && !hasError.value) {\n try {\n const cachedMedia = await getCachedMedia(src)\n if (cachedMedia.error) {\n hasError.value = true\n } else if (cachedMedia.objectUrl) {\n const acquiredUrl = acquireUrl(src)\n cachedSrc.value = acquiredUrl || cachedMedia.objectUrl\n } else {\n cachedSrc.value = src\n }\n } catch (error) {\n console.warn('Failed to load cached media:', error)\n cachedSrc.value = src\n }\n } else if (!shouldLoadVal) {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n // Hide image when out of view\n isImageLoaded.value = false\n cachedSrc.value = undefined\n hasError.value = false\n }\n },\n { immediate: true }\n)\n\nconst onImageLoad = () => {\n isImageLoaded.value = true\n hasError.value = false\n}\n\nconst onImageError = () => {\n hasError.value = true\n isImageLoaded.value = false\n}\n\nonUnmounted(() => {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n})\n</script>\n"],"names":["useFeatureFlags","flags","reactive","api","remoteConfig","featureFlag","__name","featurePath","defaultValue","computed","readonly","selectedItem","_useModel","__props","t","useI18n","getLabel","val","found","o","optionStyle","styles","formatDisplayName","folderName","specialCases","word","DISALLOWED_MODEL_TYPES","useModelTypes","createSharedComposable","modelTypes","isLoading","error","fetchModelTypes","useAsyncState","folder","a","b","err","modelValue","isVisible","handleEscapeKey","event","watch","visible","stop","useEventListener","onWatcherCleanup","showCivitaiHelp","ref","showHuggingFaceHelp","emit","__emit","civitaiIcon","civitaiUrl","huggingFaceIcon","huggingFaceUrl","props","url","value","civitaiImportSource","huggingfaceImportSource","validateSourceUrl","source","hostname","h","useUploadModelWizard","assetsStore","useAssetsStore","modelToNodeStore","useModelToNodeStore","currentStep","isFetchingMetadata","isUploading","uploadStatus","uploadError","wizardData","selectedModelType","importSources","detectedSource","canFetchMetadata","canUploadModel","fetchMetadata","cleanedUrl","supportedSources","s","metadata","assetService","typeTag","tag","type","st","uploadModel","tags","filename","previewId","baseFilename","extension","mimeMatch","providers","provider","goToPreviousStep","dialogStore","useDialogStore","handleFetchMetadata","handleUploadModel","handleClose","onMounted","_imports_0$1","titleKey","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_ctx","showSubscriptionDialog","useSubscription","handleSubscribe","useModelUpload","execute","isUploadButtonEnabled","showUploadDialog","UploadModelDialogHeader","UploadModelDialog","UploadModelUpgradeModalHeader","UploadModelUpgradeModal","_imports_0","useIntersectionObserver","target","callback","options","immediate","observerOptions","isSupported","isIntersecting","observer","cleanup","observe","entries","entry","unobserve","onBeforeUnmount","MediaCacheService","now","keysToDelete","key","removedCount","targetRemoveCount","src","response","blob","objectUrl","updatedEntry","errorEntry","currentCount","count","mediaCacheInstance","useMediaCache","containerRef","isImageLoaded","hasError","cachedSrc","getCachedMedia","acquireUrl","releaseUrl","shouldLoad","shouldLoadVal","cachedMedia","acquiredUrl","onImageLoad","onImageError","onUnmounted"],"mappings":"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"}
1
+ {"version":3,"file":"LazyImage.vue_vue_type_script_setup_true_lang-Dza6G4hd.js","sources":["../../src/composables/useFeatureFlags.ts","../../src/components/input/SingleSelect.vue","../../src/platform/assets/composables/useModelTypes.ts","../../src/platform/assets/components/UploadModelConfirmation.vue","../../src/platform/assets/components/VideoHelpDialog.vue","../../src/platform/assets/components/UploadModelFooter.vue","../../src/platform/assets/components/UploadModelUrlInput.vue","../../src/platform/assets/components/UploadModelUrlInputCivitai.vue","../../src/platform/assets/importSources/civitaiImportSource.ts","../../src/platform/assets/importSources/huggingfaceImportSource.ts","../../src/platform/assets/utils/importSourceUtil.ts","../../src/platform/assets/composables/useUploadModelWizard.ts","../../src/platform/assets/components/UploadModelDialog.vue","../../../../../../../assets/images/civitai.svg","../../src/platform/assets/components/UploadModelDialogHeader.vue","../../src/platform/assets/components/UploadModelUpgradeModalBody.vue","../../src/platform/assets/components/UploadModelUpgradeModalFooter.vue","../../src/platform/assets/components/UploadModelUpgradeModal.vue","../../src/platform/assets/components/UploadModelUpgradeModalHeader.vue","../../src/platform/assets/composables/useModelUpload.ts","../../../../../../../assets/images/default-template.png","../../src/composables/useIntersectionObserver.ts","../../src/services/mediaCacheService.ts","../../src/components/common/LazyImage.vue"],"sourcesContent":["import { computed, reactive, readonly } from 'vue'\n\nimport { remoteConfig } from '@/platform/remoteConfig/remoteConfig'\nimport { api } from '@/scripts/api'\n\n/**\n * Known server feature flags (top-level, not extensions)\n */\nexport enum ServerFeatureFlag {\n SUPPORTS_PREVIEW_METADATA = 'supports_preview_metadata',\n MAX_UPLOAD_SIZE = 'max_upload_size',\n MANAGER_SUPPORTS_V4 = 'extension.manager.supports_v4',\n MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled',\n ASSET_UPDATE_OPTIONS_ENABLED = 'asset_update_options_enabled',\n PRIVATE_MODELS_ENABLED = 'private_models_enabled',\n ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled',\n HUGGINGFACE_MODEL_IMPORT_ENABLED = 'huggingface_model_import_enabled'\n}\n\n/**\n * Composable for reactive access to server-side feature flags\n */\nexport function useFeatureFlags() {\n const flags = reactive({\n get supportsPreviewMetadata() {\n return api.getServerFeature(ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA)\n },\n get maxUploadSize() {\n return api.getServerFeature(ServerFeatureFlag.MAX_UPLOAD_SIZE)\n },\n get supportsManagerV4() {\n return api.getServerFeature(ServerFeatureFlag.MANAGER_SUPPORTS_V4)\n },\n get modelUploadButtonEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.model_upload_button_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.MODEL_UPLOAD_BUTTON_ENABLED,\n false\n )\n )\n },\n get assetUpdateOptionsEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.asset_update_options_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.ASSET_UPDATE_OPTIONS_ENABLED,\n false\n )\n )\n },\n get privateModelsEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.private_models_enabled ??\n api.getServerFeature(ServerFeatureFlag.PRIVATE_MODELS_ENABLED, false)\n )\n },\n get onboardingSurveyEnabled() {\n return (\n remoteConfig.value.onboarding_survey_enabled ??\n api.getServerFeature(ServerFeatureFlag.ONBOARDING_SURVEY_ENABLED, true)\n )\n },\n get huggingfaceModelImportEnabled() {\n // Check remote config first (from /api/features), fall back to websocket feature flags\n return (\n remoteConfig.value.huggingface_model_import_enabled ??\n api.getServerFeature(\n ServerFeatureFlag.HUGGINGFACE_MODEL_IMPORT_ENABLED,\n false\n )\n )\n }\n })\n\n const featureFlag = <T = unknown>(featurePath: string, defaultValue?: T) =>\n computed(() => api.getServerFeature(featurePath, defaultValue))\n\n return {\n flags: readonly(flags),\n featureFlag\n }\n}\n","<template>\n <!--\n Note: We explicitly pass options here (not just via $attrs) because:\n 1. Our custom value template needs options to look up labels from values\n 2. PrimeVue's value slot only provides 'value' and 'placeholder', not the selected item's label\n 3. We need to maintain the icon slot functionality in the value template\n option-label=\"name\" is required because our option template directly accesses option.name\n -->\n <Select\n v-model=\"selectedItem\"\n v-bind=\"$attrs\"\n :options=\"options\"\n option-label=\"name\"\n option-value=\"value\"\n unstyled\n :pt=\"{\n root: ({ props }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: [\n // container\n 'h-10 relative inline-flex cursor-pointer select-none items-center',\n // trigger surface\n 'rounded-lg',\n 'bg-secondary-background text-base-foreground',\n 'border-[2.5px] border-solid border-transparent',\n 'transition-all duration-200 ease-in-out',\n 'focus-within:border-node-component-border',\n // disabled\n { 'opacity-60 cursor-default': props.disabled }\n ]\n }),\n label: {\n class:\n // Align with MultiSelect labelContainer spacing\n 'flex-1 flex items-center whitespace-nowrap pl-4 py-2 outline-hidden'\n },\n dropdown: {\n class:\n // Right chevron touch area\n 'flex shrink-0 items-center justify-center px-3 py-2'\n },\n overlay: {\n class: cn(\n 'mt-2 p-2 rounded-lg',\n 'bg-base-background text-base-foreground',\n 'border border-solid border-border-default'\n )\n },\n listContainer: () => ({\n style: `max-height: min(${listMaxHeight}, 50vh)`,\n class: 'scrollbar-custom'\n }),\n list: {\n class:\n // Same list tone/size as MultiSelect\n 'flex flex-col gap-0 p-0 m-0 list-none border-none text-sm'\n },\n option: ({ context }: SelectPassThroughMethodOptions<SelectOption>) => ({\n class: cn(\n // Row layout\n 'flex items-center justify-between gap-3 px-2 py-3 rounded',\n 'hover:bg-secondary-background-hover',\n // Add focus state for keyboard navigation\n context.focused && 'bg-secondary-background-hover',\n // Selected state + check icon\n context.selected &&\n 'bg-secondary-background-selected hover:bg-secondary-background-selected'\n )\n }),\n optionLabel: {\n class: 'truncate'\n },\n optionGroupLabel: {\n class: 'px-3 py-2 text-xs uppercase tracking-wide text-muted-foreground'\n },\n emptyMessage: {\n class: 'px-3 py-2 text-sm text-muted-foreground'\n }\n }\"\n :aria-label=\"label || t('g.singleSelectDropdown')\"\n role=\"combobox\"\n :aria-expanded=\"false\"\n aria-haspopup=\"listbox\"\n :tabindex=\"0\"\n >\n <!-- Trigger value -->\n <template #value=\"slotProps\">\n <div class=\"flex items-center gap-2 text-sm\">\n <slot name=\"icon\" />\n <span\n v-if=\"slotProps.value !== null && slotProps.value !== undefined\"\n class=\"text-base-foreground\"\n >\n {{ getLabel(slotProps.value) }}\n </span>\n <span v-else class=\"text-base-foreground\">\n {{ label }}\n </span>\n </div>\n </template>\n\n <!-- Trigger caret -->\n <template #dropdownicon>\n <i class=\"icon-[lucide--chevron-down] text-muted-foreground\" />\n </template>\n\n <!-- Option row -->\n <template #option=\"{ option, selected }\">\n <div\n class=\"flex w-full items-center justify-between gap-3\"\n :style=\"optionStyle\"\n >\n <span class=\"truncate\">{{ option.name }}</span>\n <i v-if=\"selected\" class=\"icon-[lucide--check] text-base-foreground\" />\n </div>\n </template>\n </Select>\n</template>\n\n<script setup lang=\"ts\">\nimport type { SelectPassThroughMethodOptions } from 'primevue/select'\nimport Select from 'primevue/select'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { cn } from '@/utils/tailwindUtil'\n\nimport type { SelectOption } from './types'\n\ndefineOptions({\n inheritAttrs: false\n})\n\nconst {\n label,\n options,\n listMaxHeight = '28rem',\n popoverMinWidth,\n popoverMaxWidth\n} = defineProps<{\n label?: string\n /**\n * Required for displaying the selected item's label.\n * Cannot rely on $attrs alone because we need to access options\n * in getLabel() to map values to their display names.\n */\n options?: SelectOption[]\n /** Maximum height of the dropdown panel (default: 28rem) */\n listMaxHeight?: string\n /** Minimum width of the popover (default: auto) */\n popoverMinWidth?: string\n /** Maximum width of the popover (default: auto) */\n popoverMaxWidth?: string\n}>()\n\nconst selectedItem = defineModel<string | undefined>({ required: true })\n\nconst { t } = useI18n()\n\n/**\n * Maps a value to its display label.\n * Necessary because PrimeVue's value slot doesn't provide the selected item's label,\n * only the raw value. We need this to show the correct text when an item is selected.\n */\nconst getLabel = (val: string | null | undefined) => {\n if (val == null) return label ?? ''\n if (!options) return label ?? ''\n const found = options.find((o) => o.value === val)\n return found ? found.name : (label ?? '')\n}\n\n// Extract complex style logic from template\nconst optionStyle = computed(() => {\n if (!popoverMinWidth && !popoverMaxWidth) return undefined\n\n const styles: string[] = []\n if (popoverMinWidth) styles.push(`min-width: ${popoverMinWidth}`)\n if (popoverMaxWidth) styles.push(`max-width: ${popoverMaxWidth}`)\n\n return styles.join('; ')\n})\n</script>\n","import { createSharedComposable, useAsyncState } from '@vueuse/core'\n\nimport { api } from '@/scripts/api'\n\n/**\n * Format folder name to display name\n * Converts \"upscale_models\" -> \"Upscale Model\"\n * Converts \"loras\" -> \"LoRA\"\n */\nfunction formatDisplayName(folderName: string): string {\n // Special cases for acronyms and proper nouns\n const specialCases: Record<string, string> = {\n loras: 'LoRA',\n ipadapter: 'IP-Adapter',\n sams: 'SAM',\n clip_vision: 'CLIP Vision',\n animatediff_motion_lora: 'AnimateDiff Motion LoRA',\n animatediff_models: 'AnimateDiff Model',\n vae: 'VAE',\n sam2: 'SAM 2',\n controlnet: 'ControlNet',\n gligen: 'GLIGEN'\n }\n\n if (specialCases[folderName]) {\n return specialCases[folderName]\n }\n\n return folderName\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\ninterface ModelTypeOption {\n name: string // Display name\n value: string // Actual tag value\n}\n\nconst DISALLOWED_MODEL_TYPES = ['nlf'] as const\n\n/**\n * Composable for fetching and managing model types from the API\n * Uses shared state to ensure data is only fetched once\n */\nexport const useModelTypes = createSharedComposable(() => {\n const {\n state: modelTypes,\n isLoading,\n error,\n execute: fetchModelTypes\n } = useAsyncState(\n async (): Promise<ModelTypeOption[]> => {\n const response = await api.getModelFolders()\n return response\n .filter(\n (folder) =>\n !DISALLOWED_MODEL_TYPES.includes(\n folder.name as (typeof DISALLOWED_MODEL_TYPES)[number]\n )\n )\n .map((folder) => ({\n name: formatDisplayName(folder.name),\n value: folder.name\n }))\n .sort((a, b) => a.name.localeCompare(b.name))\n },\n [] as ModelTypeOption[],\n {\n immediate: false,\n onError: (err) => {\n console.error('Failed to fetch model types:', err)\n }\n }\n )\n\n return {\n modelTypes,\n isLoading,\n error,\n fetchModelTypes\n }\n})\n","<template>\n <div class=\"flex flex-col gap-4 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.modelAssociatedWithLink') }}\n </p>\n <div\n class=\"flex items-center gap-3 bg-secondary-background p-3 rounded-lg\"\n >\n <img\n v-if=\"previewImage\"\n :src=\"previewImage\"\n :alt=\"metadata?.filename || metadata?.name || 'Model preview'\"\n class=\"w-14 h-14 rounded object-cover flex-shrink-0\"\n />\n <p class=\"m-0 text-base-foreground\">\n {{ metadata?.filename || metadata?.name }}\n </p>\n </div>\n </div>\n\n <!-- Model Type Selection -->\n <div class=\"flex flex-col gap-2\">\n <label class=\"\">\n {{ $t('assetBrowser.modelTypeSelectorLabel') }}\n </label>\n <SingleSelect\n v-model=\"modelValue\"\n :label=\"\n isLoading\n ? $t('g.loading')\n : $t('assetBrowser.modelTypeSelectorPlaceholder')\n \"\n :options=\"modelTypes\"\n :disabled=\"isLoading\"\n data-attr=\"upload-model-step2-type-selector\"\n />\n <div class=\"flex items-center gap-2\">\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.notSureLeaveAsIs') }}</span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport SingleSelect from '@/components/input/SingleSelect.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\n\ndefineProps<{\n metadata?: AssetMetadata\n previewImage?: string\n}>()\n\nconst modelValue = defineModel<string | undefined>()\n\nconst { modelTypes, isLoading } = useModelTypes()\n</script>\n","<template>\n <Dialog\n v-model:visible=\"isVisible\"\n modal\n :closable=\"false\"\n :close-on-escape=\"false\"\n :dismissable-mask=\"true\"\n :pt=\"{\n root: { class: 'video-help-dialog' },\n header: { class: '!hidden' },\n content: { class: '!p-0' },\n mask: { class: '!bg-black/70' }\n }\"\n :style=\"{ width: '90vw', maxWidth: '800px' }\"\n >\n <div class=\"relative\">\n <Button\n variant=\"textonly\"\n size=\"icon\"\n class=\"absolute top-4 right-6 z-10\"\n :aria-label=\"$t('g.close')\"\n @click=\"isVisible = false\"\n >\n <i class=\"pi pi-times text-sm\" />\n </Button>\n <video\n autoplay\n muted\n loop\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-lg\"\n :src=\"videoUrl\"\n >\n {{ $t('g.videoFailedToLoad') }}\n </video>\n </div>\n </Dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { useEventListener } from '@vueuse/core'\nimport Dialog from 'primevue/dialog'\nimport { onWatcherCleanup, watch } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\n\nconst isVisible = defineModel<boolean>({ required: true })\n\nconst { videoUrl, ariaLabel = 'Help video' } = defineProps<{\n videoUrl: string\n ariaLabel?: string\n}>()\n\nconst handleEscapeKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.stopImmediatePropagation()\n event.stopPropagation()\n event.preventDefault()\n isVisible.value = false\n }\n}\n\n// Add listener with capture phase to intercept before parent dialogs\n// Only active when dialog is visible\nwatch(\n isVisible,\n (visible) => {\n if (visible) {\n const stop = useEventListener(document, 'keydown', handleEscapeKey, {\n capture: true\n })\n onWatcherCleanup(stop)\n }\n },\n { immediate: true }\n)\n</script>\n","<template>\n <div class=\"flex justify-end gap-2 w-full\">\n <div\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n class=\"mr-auto flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--circle-question-mark] text-muted-foreground\" />\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-civitai\"\n @click=\"showCivitaiHelp = true\"\n >\n {{ $t('assetBrowser.providerCivitai') }}\n </Button>\n <Button\n variant=\"muted-textonly\"\n size=\"sm\"\n data-attr=\"upload-model-step1-help-huggingface\"\n @click=\"showHuggingFaceHelp = true\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </Button>\n </div>\n <Button\n v-else-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n class=\"mr-auto underline\"\n data-attr=\"upload-model-step1-help-link\"\n @click=\"showCivitaiHelp = true\"\n >\n <i class=\"icon-[lucide--circle-question-mark]\" />\n <span>{{ $t('assetBrowser.uploadModelHowDoIFindThis') }}</span>\n </Button>\n <Button\n v-if=\"currentStep === 1\"\n variant=\"muted-textonly\"\n size=\"lg\"\n data-attr=\"upload-model-step1-cancel-button\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('close')\"\n >\n {{ $t('g.cancel') }}\n </Button>\n <Button\n v-if=\"currentStep !== 1 && currentStep !== 3\"\n variant=\"muted-textonly\"\n size=\"lg\"\n :data-attr=\"`upload-model-step${currentStep}-back-button`\"\n :disabled=\"isFetchingMetadata || isUploading\"\n @click=\"emit('back')\"\n >\n {{ $t('g.back') }}\n </Button>\n <span v-else />\n\n <Button\n v-if=\"currentStep === 1\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step1-continue-button\"\n :disabled=\"!canFetchMetadata || isFetchingMetadata\"\n @click=\"emit('fetchMetadata')\"\n >\n <i\n v-if=\"isFetchingMetadata\"\n class=\"icon-[lucide--loader-circle] animate-spin\"\n />\n <span>{{ $t('g.continue') }}</span>\n </Button>\n <Button\n v-else-if=\"currentStep === 2\"\n variant=\"secondary\"\n size=\"lg\"\n data-attr=\"upload-model-step2-confirm-button\"\n :disabled=\"!canUploadModel || isUploading\"\n @click=\"emit('upload')\"\n >\n <i v-if=\"isUploading\" class=\"icon-[lucide--loader-circle] animate-spin\" />\n <span>{{ $t('assetBrowser.upload') }}</span>\n </Button>\n <Button\n v-else-if=\"currentStep === 3 && uploadStatus === 'success'\"\n variant=\"secondary\"\n data-attr=\"upload-model-step3-finish-button\"\n @click=\"emit('close')\"\n >\n {{ $t('assetBrowser.finish') }}\n </Button>\n <VideoHelpDialog\n v-model=\"showCivitaiHelp\"\n video-url=\"https://media.comfy.org/compressed_768/civitai_howto.webm\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n <VideoHelpDialog\n v-model=\"showHuggingFaceHelp\"\n video-url=\"https://media.comfy.org/byom/huggingfacehowto.mp4\"\n :aria-label=\"$t('assetBrowser.uploadModelHelpVideo')\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport VideoHelpDialog from '@/platform/assets/components/VideoHelpDialog.vue'\n\nconst { flags } = useFeatureFlags()\n\nconst showCivitaiHelp = ref(false)\nconst showHuggingFaceHelp = ref(false)\n\ndefineProps<{\n currentStep: number\n isFetchingMetadata: boolean\n isUploading: boolean\n canFetchMetadata: boolean\n canUploadModel: boolean\n uploadStatus: 'idle' | 'uploading' | 'success' | 'error'\n}>()\n\nconst emit = defineEmits<{\n (e: 'back'): void\n (e: 'fetchMetadata'): void\n (e: 'upload'): void\n (e: 'close'): void\n}>()\n</script>\n","<template>\n <div class=\"flex flex-col justify-between h-full gap-6 text-sm\">\n <div class=\"flex flex-col gap-6\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0 text-foreground\">\n {{ $t('assetBrowser.uploadModelDescription1Generic') }}\n </p>\n <div class=\"m-0\">\n <p class=\"m-0 text-muted-foreground\">\n {{ $t('assetBrowser.uploadModelDescription2Generic') }}\n </p>\n <span class=\"inline-flex items-center gap-1 flex-wrap mt-2\">\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"civitaiIcon\"\n :alt=\"$t('assetBrowser.providerCivitai')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"civitaiUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerCivitai') }}</a\n ><span>,</span>\n </span>\n <span class=\"inline-flex items-center gap-1\">\n <img\n :src=\"huggingFaceIcon\"\n :alt=\"$t('assetBrowser.providerHuggingFace')\"\n class=\"w-4 h-4\"\n />\n <a\n :href=\"huggingFaceUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted underline\"\n >\n {{ $t('assetBrowser.providerHuggingFace') }}\n </a>\n </span>\n </span>\n </div>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.genericLinkPlaceholder')\"\n class=\"w-full bg-secondary-background border-0 p-4\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <p v-else class=\"text-foreground\">\n <i18n-t keypath=\"assetBrowser.maxFileSize\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </p>\n </div>\n </div>\n\n <div class=\"text-sm text-muted\">\n {{ $t('assetBrowser.uploadModelHelpFooterText') }}\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\nimport { computed } from 'vue'\n\nconst props = defineProps<{\n modelValue: string\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst url = computed({\n get: () => props.modelValue,\n set: (value: string) => emit('update:modelValue', value)\n})\n\nconst civitaiIcon = '/assets/images/civitai.svg'\nconst civitaiUrl = 'https://civitai.com/models'\nconst huggingFaceIcon = '/assets/images/hf-logo.svg'\nconst huggingFaceUrl = 'https://huggingface.co'\n</script>\n","<template>\n <div class=\"flex flex-col gap-6 text-sm text-muted-foreground\">\n <div class=\"flex flex-col gap-2\">\n <p class=\"m-0\">\n {{ $t('assetBrowser.uploadModelDescription1') }}\n </p>\n <ul class=\"list-disc space-y-1 pl-5 mt-0\">\n <li>\n <i18n-t keypath=\"assetBrowser.uploadModelDescription2\" tag=\"span\">\n <template #link>\n <a\n href=\"https://civitai.com/models\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.uploadModelDescription2Link') }}\n </a>\n </template>\n </i18n-t>\n </li>\n <li>\n <i18n-t keypath=\"assetBrowser.uploadModelDescription3\" tag=\"span\">\n <template #size>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.maxFileSizeValue')\n }}</span>\n </template>\n </i18n-t>\n </li>\n </ul>\n </div>\n\n <div class=\"flex flex-col gap-2\">\n <i18n-t keypath=\"assetBrowser.civitaiLinkLabel\" tag=\"label\" class=\"mb-0\">\n <template #download>\n <span class=\"font-bold italic\">{{\n $t('assetBrowser.civitaiLinkLabelDownload')\n }}</span>\n </template>\n </i18n-t>\n <InputText\n v-model=\"url\"\n autofocus\n :placeholder=\"$t('assetBrowser.civitaiLinkPlaceholder')\"\n class=\"w-full bg-secondary-background border-0 p-4\"\n data-attr=\"upload-model-step1-url-input\"\n />\n <p v-if=\"error\" class=\"text-xs text-error\">\n {{ error }}\n </p>\n <i18n-t\n v-else\n keypath=\"assetBrowser.civitaiLinkExample\"\n tag=\"p\"\n class=\"text-xs\"\n >\n <template #example>\n <strong>{{ $t('assetBrowser.civitaiLinkExampleStrong') }}</strong>\n </template>\n <template #link>\n <a\n href=\"https://civitai.com/models/10706/luisap-z-image-and-qwen-pixel-art-refiner?modelVersionId=2225295\"\n target=\"_blank\"\n class=\"text-muted-foreground\"\n >\n {{ $t('assetBrowser.civitaiLinkExampleUrl') }}\n </a>\n </template>\n </i18n-t>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport InputText from 'primevue/inputtext'\n\ndefineProps<{\n error?: string\n}>()\n\nconst url = defineModel<string>({ required: true })\n</script>\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Civitai model import source configuration\n */\nexport const civitaiImportSource: ImportSource = {\n type: 'civitai',\n name: 'Civitai',\n hostnames: ['civitai.com']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Hugging Face model import source configuration\n */\nexport const huggingfaceImportSource: ImportSource = {\n type: 'huggingface',\n name: 'Hugging Face',\n hostnames: ['huggingface.co']\n}\n","import type { ImportSource } from '@/platform/assets/types/importSource'\n\n/**\n * Check if a URL belongs to a specific import source\n */\nexport function validateSourceUrl(url: string, source: ImportSource): boolean {\n try {\n const hostname = new URL(url).hostname.toLowerCase()\n return source.hostnames.some(\n (h) => hostname === h || hostname.endsWith(`.${h}`)\n )\n } catch {\n return false\n }\n}\n","import type { Ref } from 'vue'\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport { st } from '@/i18n'\nimport { civitaiImportSource } from '@/platform/assets/importSources/civitaiImportSource'\nimport { huggingfaceImportSource } from '@/platform/assets/importSources/huggingfaceImportSource'\nimport type { AssetMetadata } from '@/platform/assets/schemas/assetSchema'\nimport { assetService } from '@/platform/assets/services/assetService'\nimport type { ImportSource } from '@/platform/assets/types/importSource'\nimport { validateSourceUrl } from '@/platform/assets/utils/importSourceUtil'\nimport { useAssetsStore } from '@/stores/assetsStore'\nimport { useModelToNodeStore } from '@/stores/modelToNodeStore'\n\ninterface WizardData {\n url: string\n metadata?: AssetMetadata\n name: string\n tags: string[]\n previewImage?: string\n}\n\ninterface ModelTypeOption {\n name: string\n value: string\n}\n\nexport function useUploadModelWizard(modelTypes: Ref<ModelTypeOption[]>) {\n const { t } = useI18n()\n const assetsStore = useAssetsStore()\n const modelToNodeStore = useModelToNodeStore()\n const { flags } = useFeatureFlags()\n const currentStep = ref(1)\n const isFetchingMetadata = ref(false)\n const isUploading = ref(false)\n const uploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle')\n const uploadError = ref('')\n\n const wizardData = ref<WizardData>({\n url: '',\n name: '',\n tags: []\n })\n\n const selectedModelType = ref<string>()\n\n // Available import sources\n const importSources: ImportSource[] = flags.huggingfaceModelImportEnabled\n ? [civitaiImportSource, huggingfaceImportSource]\n : [civitaiImportSource]\n\n // Detected import source based on URL\n const detectedSource = computed(() => {\n const url = wizardData.value.url.trim()\n if (!url) return null\n return (\n importSources.find((source) => validateSourceUrl(url, source)) ?? null\n )\n })\n\n // Clear error when URL changes\n watch(\n () => wizardData.value.url,\n () => {\n uploadError.value = ''\n }\n )\n\n // Validation\n const canFetchMetadata = computed(() => {\n return wizardData.value.url.trim().length > 0\n })\n\n const canUploadModel = computed(() => {\n return !!selectedModelType.value\n })\n\n async function fetchMetadata() {\n if (!canFetchMetadata.value) return\n\n // Clean and normalize URL\n let cleanedUrl = wizardData.value.url.trim()\n try {\n cleanedUrl = new URL(encodeURI(cleanedUrl)).toString()\n } catch {\n // If URL parsing fails, just use the trimmed input\n }\n wizardData.value.url = cleanedUrl\n\n // Validate URL belongs to a supported import source\n const source = detectedSource.value\n if (!source) {\n const supportedSources = importSources.map((s) => s.name).join(', ')\n uploadError.value = t('assetBrowser.unsupportedUrlSource', {\n sources: supportedSources\n })\n return\n }\n\n isFetchingMetadata.value = true\n try {\n const metadata = await assetService.getAssetMetadata(wizardData.value.url)\n\n // Decode URL-encoded filenames (e.g., Chinese characters)\n if (metadata.filename) {\n try {\n metadata.filename = decodeURIComponent(metadata.filename)\n } catch {\n // Keep original if decoding fails\n }\n }\n if (metadata.name) {\n try {\n metadata.name = decodeURIComponent(metadata.name)\n } catch {\n // Keep original if decoding fails\n }\n }\n\n wizardData.value.metadata = metadata\n\n // Pre-fill name from metadata\n wizardData.value.name = metadata.filename || metadata.name || ''\n\n // Store preview image if available\n wizardData.value.previewImage = metadata.preview_image\n\n // Pre-fill model type from metadata tags if available\n if (metadata.tags && metadata.tags.length > 0) {\n wizardData.value.tags = metadata.tags\n // Try to detect model type from tags\n const typeTag = metadata.tags.find((tag) =>\n modelTypes.value.some((type) => type.value === tag)\n )\n if (typeTag) {\n selectedModelType.value = typeTag\n }\n }\n\n currentStep.value = 2\n } catch (error) {\n console.error('Failed to retrieve metadata:', error)\n uploadError.value =\n error instanceof Error\n ? error.message\n : st(\n 'assetBrowser.uploadModelFailedToRetrieveMetadata',\n 'Failed to retrieve metadata. Please check the link and try again.'\n )\n currentStep.value = 1\n } finally {\n isFetchingMetadata.value = false\n }\n }\n\n async function uploadModel() {\n if (!canUploadModel.value) return\n\n // Defensive check: detectedSource should be valid after fetchMetadata validation,\n // but guard against edge cases (e.g., URL modified between steps)\n const source = detectedSource.value\n if (!source) {\n uploadError.value = t('assetBrowser.noValidSourceDetected')\n return false\n }\n\n isUploading.value = true\n uploadStatus.value = 'uploading'\n\n try {\n const tags = selectedModelType.value\n ? ['models', selectedModelType.value]\n : ['models']\n const filename =\n wizardData.value.metadata?.filename ||\n wizardData.value.metadata?.name ||\n 'model'\n\n let previewId: string | undefined\n\n // Upload preview image first if available\n if (wizardData.value.previewImage) {\n try {\n const baseFilename = filename.split('.')[0]\n\n // Extract extension from data URL MIME type\n let extension = 'png'\n const mimeMatch = wizardData.value.previewImage.match(\n /^data:image\\/([^;]+);/\n )\n if (mimeMatch) {\n extension = mimeMatch[1] === 'jpeg' ? 'jpg' : mimeMatch[1]\n }\n\n const previewAsset = await assetService.uploadAssetFromBase64({\n data: wizardData.value.previewImage,\n name: `${baseFilename}_preview.${extension}`,\n tags: ['preview']\n })\n previewId = previewAsset.id\n } catch (error) {\n console.error('Failed to upload preview image:', error)\n // Continue with model upload even if preview fails\n }\n }\n\n await assetService.uploadAssetFromUrl({\n url: wizardData.value.url,\n name: filename,\n tags,\n user_metadata: {\n source: source.type,\n source_url: wizardData.value.url,\n model_type: selectedModelType.value\n },\n preview_id: previewId\n })\n\n uploadStatus.value = 'success'\n currentStep.value = 3\n\n // Refresh model caches for all node types that use this model category\n if (selectedModelType.value) {\n const providers = modelToNodeStore.getAllNodeProviders(\n selectedModelType.value\n )\n await Promise.all(\n providers.map((provider) =>\n assetsStore.updateModelsForNodeType(provider.nodeDef.name)\n )\n )\n }\n\n return true\n } catch (error) {\n console.error('Failed to upload asset:', error)\n uploadStatus.value = 'error'\n uploadError.value =\n error instanceof Error ? error.message : 'Failed to upload model'\n currentStep.value = 3\n return false\n } finally {\n isUploading.value = false\n }\n }\n\n function goToPreviousStep() {\n if (currentStep.value > 1) {\n currentStep.value = currentStep.value - 1\n }\n }\n\n return {\n // State\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n\n // Computed\n canFetchMetadata,\n canUploadModel,\n detectedSource,\n\n // Actions\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n }\n}\n","<template>\n <div\n class=\"upload-model-dialog flex flex-col justify-between gap-6 p-4 pt-6 border-t border-border-default\"\n >\n <!-- Step 1: Enter URL -->\n <UploadModelUrlInput\n v-if=\"currentStep === 1 && flags.huggingfaceModelImportEnabled\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n class=\"flex-1\"\n />\n <UploadModelUrlInputCivitai\n v-else-if=\"currentStep === 1\"\n v-model=\"wizardData.url\"\n :error=\"uploadError\"\n />\n\n <!-- Step 2: Confirm Metadata -->\n <UploadModelConfirmation\n v-else-if=\"currentStep === 2\"\n v-model=\"selectedModelType\"\n :metadata=\"wizardData.metadata\"\n :preview-image=\"wizardData.previewImage\"\n />\n\n <!-- Step 3: Upload Progress -->\n <UploadModelProgress\n v-else-if=\"currentStep === 3\"\n :status=\"uploadStatus\"\n :error=\"uploadError\"\n :metadata=\"wizardData.metadata\"\n :model-type=\"selectedModelType\"\n :preview-image=\"wizardData.previewImage\"\n />\n\n <!-- Navigation Footer -->\n <UploadModelFooter\n :current-step=\"currentStep\"\n :is-fetching-metadata=\"isFetchingMetadata\"\n :is-uploading=\"isUploading\"\n :can-fetch-metadata=\"canFetchMetadata\"\n :can-upload-model=\"canUploadModel\"\n :upload-status=\"uploadStatus\"\n @back=\"goToPreviousStep\"\n @fetch-metadata=\"handleFetchMetadata\"\n @upload=\"handleUploadModel\"\n @close=\"handleClose\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelConfirmation from '@/platform/assets/components/UploadModelConfirmation.vue'\nimport UploadModelFooter from '@/platform/assets/components/UploadModelFooter.vue'\nimport UploadModelProgress from '@/platform/assets/components/UploadModelProgress.vue'\nimport UploadModelUrlInput from '@/platform/assets/components/UploadModelUrlInput.vue'\nimport UploadModelUrlInputCivitai from '@/platform/assets/components/UploadModelUrlInputCivitai.vue'\nimport { useModelTypes } from '@/platform/assets/composables/useModelTypes'\nimport { useUploadModelWizard } from '@/platform/assets/composables/useUploadModelWizard'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst { flags } = useFeatureFlags()\nconst dialogStore = useDialogStore()\nconst { modelTypes, fetchModelTypes } = useModelTypes()\n\nconst emit = defineEmits<{\n 'upload-success': []\n}>()\n\nconst {\n currentStep,\n isFetchingMetadata,\n isUploading,\n uploadStatus,\n uploadError,\n wizardData,\n selectedModelType,\n canFetchMetadata,\n canUploadModel,\n fetchMetadata,\n uploadModel,\n goToPreviousStep\n} = useUploadModelWizard(modelTypes)\n\nasync function handleFetchMetadata() {\n await fetchMetadata()\n}\n\nasync function handleUploadModel() {\n const success = await uploadModel()\n if (success) {\n emit('upload-success')\n }\n}\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model' })\n}\n\nonMounted(() => {\n fetchModelTypes()\n})\n</script>\n\n<style scoped>\n.upload-model-dialog {\n width: 90vw;\n max-width: 800px;\n min-height: 400px;\n}\n\n@media (min-width: 640px) {\n .upload-model-dialog {\n width: auto;\n min-width: 600px;\n }\n}\n</style>\n","export default \"__VITE_PUBLIC_ASSET__53f47e52__\"","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <img\n v-if=\"!flags.huggingfaceModelImportEnabled\"\n src=\"/assets/images/civitai.svg\"\n class=\"size-4\"\n />\n <span>{{ $t(titleKey) }}</span>\n <span\n class=\"rounded-full bg-white px-1.5 py-0 text-xxs font-inter font-semibold uppercase text-black\"\n >\n {{ $t('g.beta') }}\n </span>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useFeatureFlags } from '@/composables/useFeatureFlags'\n\nconst { flags } = useFeatureFlags()\n\nconst titleKey = computed(() => {\n return flags.huggingfaceModelImportEnabled\n ? 'assetBrowser.uploadModelGeneric'\n : 'assetBrowser.uploadModelFromCivitai'\n})\n</script>\n","<template>\n <div\n class=\"flex flex-1 flex-col items-center justify-center text-base text-muted-foreground\"\n >\n <p class=\"m-0 max-w-md\">\n {{ $t('assetBrowser.upgradeFeatureDescription') }}\n </p>\n </div>\n</template>\n","<template>\n <div class=\"flex flex-wrap justify-end gap-2 w-full\">\n <a\n href=\"https://blog.comfy.org/p/comfy-cloud-new-features-and-pricing\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"text-muted-foreground mr-auto underline flex items-center gap-2\"\n >\n <i class=\"icon-[lucide--external-link]\" />\n <span>{{ $t('g.learnMore') }}</span>\n </a>\n <Button variant=\"textonly\" @click=\"emit('close')\">{{\n $t('g.close')\n }}</Button>\n <Button variant=\"secondary\" @click=\"emit('subscribe')\">\n {{ $t('subscription.required.subscribe') }}\n </Button>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Button from '@/components/ui/button/Button.vue'\n\nconst emit = defineEmits<{\n close: []\n subscribe: []\n}>()\n</script>\n","<template>\n <div\n class=\"flex flex-col justify-between gap-10 p-4 border-t border-border-default w-auto max-w-[min(500px,90vw)]\"\n >\n <UploadModelUpgradeModalBody />\n\n <UploadModelUpgradeModalFooter\n @close=\"handleClose\"\n @subscribe=\"handleSubscribe\"\n />\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport UploadModelUpgradeModalBody from '@/platform/assets/components/UploadModelUpgradeModalBody.vue'\nimport UploadModelUpgradeModalFooter from '@/platform/assets/components/UploadModelUpgradeModalFooter.vue'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useDialogStore } from '@/stores/dialogStore'\n\nconst dialogStore = useDialogStore()\nconst { showSubscriptionDialog } = useSubscription()\n\nfunction handleClose() {\n dialogStore.closeDialog({ key: 'upload-model-upgrade' })\n}\n\nfunction handleSubscribe() {\n showSubscriptionDialog()\n}\n</script>\n","<template>\n <div class=\"flex items-center gap-2 p-4 font-bold\">\n <span>{{ $t('assetBrowser.upgradeToUnlockFeature') }}</span>\n </div>\n</template>\n","import { useFeatureFlags } from '@/composables/useFeatureFlags'\nimport UploadModelDialog from '@/platform/assets/components/UploadModelDialog.vue'\nimport UploadModelDialogHeader from '@/platform/assets/components/UploadModelDialogHeader.vue'\nimport UploadModelUpgradeModal from '@/platform/assets/components/UploadModelUpgradeModal.vue'\nimport UploadModelUpgradeModalHeader from '@/platform/assets/components/UploadModelUpgradeModalHeader.vue'\nimport type { AssetItem } from '@/platform/assets/schemas/assetSchema'\nimport { useDialogStore } from '@/stores/dialogStore'\nimport type { UseAsyncStateReturn } from '@vueuse/core'\nimport { computed } from 'vue'\n\nexport function useModelUpload(\n execute?: UseAsyncStateReturn<AssetItem[], [], true>['execute']\n) {\n const dialogStore = useDialogStore()\n const { flags } = useFeatureFlags()\n const isUploadButtonEnabled = computed(() => flags.modelUploadButtonEnabled)\n\n function showUploadDialog() {\n if (!flags.privateModelsEnabled) {\n // Show upgrade modal if private models are disabled\n dialogStore.showDialog({\n key: 'upload-model-upgrade',\n headerComponent: UploadModelUpgradeModalHeader,\n component: UploadModelUpgradeModal,\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0!'\n }\n }\n })\n } else {\n // Show regular upload modal\n dialogStore.showDialog({\n key: 'upload-model',\n headerComponent: UploadModelDialogHeader,\n component: UploadModelDialog,\n props: {\n onUploadSuccess: async () => {\n await execute?.()\n }\n },\n dialogComponentProps: {\n pt: {\n header: 'py-0! pl-0!',\n content: 'p-0!'\n }\n }\n })\n }\n }\n return { isUploadButtonEnabled, showUploadDialog }\n}\n","export default \"__VITE_PUBLIC_ASSET__c8cdd13f__\"","import { onBeforeUnmount, ref, watch } from 'vue'\nimport type { Ref } from 'vue'\n\ninterface UseIntersectionObserverOptions extends IntersectionObserverInit {\n immediate?: boolean\n}\n\nexport function useIntersectionObserver(\n target: Ref<Element | null>,\n callback: IntersectionObserverCallback,\n options: UseIntersectionObserverOptions = {}\n) {\n const { immediate = true, ...observerOptions } = options\n\n const isSupported =\n typeof window !== 'undefined' && 'IntersectionObserver' in window\n const isIntersecting = ref(false)\n\n let observer: IntersectionObserver | null = null\n\n const cleanup = () => {\n if (observer) {\n observer.disconnect()\n observer = null\n }\n }\n\n const observe = () => {\n cleanup()\n\n if (!isSupported || !target.value) return\n\n observer = new IntersectionObserver((entries) => {\n isIntersecting.value = entries.some((entry) => entry.isIntersecting)\n callback(entries, observer!)\n }, observerOptions)\n\n observer.observe(target.value)\n }\n\n const unobserve = () => {\n if (observer && target.value) {\n observer.unobserve(target.value)\n }\n }\n\n if (immediate) {\n watch(target, observe, { immediate: true, flush: 'post' })\n }\n\n onBeforeUnmount(cleanup)\n\n return {\n isSupported,\n isIntersecting,\n observe,\n unobserve,\n cleanup\n }\n}\n","import { reactive } from 'vue'\n\ninterface CachedMedia {\n src: string\n blob?: Blob\n objectUrl?: string\n error?: boolean\n isLoading: boolean\n lastAccessed: number\n}\n\ninterface MediaCacheOptions {\n maxSize?: number\n maxAge?: number // in milliseconds\n preloadDistance?: number // pixels from viewport\n}\n\nclass MediaCacheService {\n public cache = reactive(new Map<string, CachedMedia>())\n private readonly maxSize: number\n private readonly maxAge: number\n private cleanupInterval: number | null = null\n private urlRefCount = new Map<string, number>()\n\n constructor(options: MediaCacheOptions = {}) {\n this.maxSize = options.maxSize ?? 100\n this.maxAge = options.maxAge ?? 30 * 60 * 1000 // 30 minutes\n\n // Start cleanup interval\n this.startCleanupInterval()\n }\n\n private startCleanupInterval() {\n // Clean up every 5 minutes\n this.cleanupInterval = window.setInterval(\n () => {\n this.cleanup()\n },\n 5 * 60 * 1000\n )\n }\n\n private cleanup() {\n const now = Date.now()\n const keysToDelete: string[] = []\n\n // Find expired entries\n for (const [key, entry] of Array.from(this.cache.entries())) {\n if (now - entry.lastAccessed > this.maxAge) {\n // Only revoke object URL if no components are using it\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n keysToDelete.push(key)\n }\n // Don't delete cache entry if URL is still in use\n } else {\n keysToDelete.push(key)\n }\n }\n }\n\n // Remove expired entries\n keysToDelete.forEach((key) => this.cache.delete(key))\n\n // If still over size limit, remove oldest entries that aren't in use\n if (this.cache.size > this.maxSize) {\n const entries = Array.from(this.cache.entries())\n entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed)\n\n let removedCount = 0\n const targetRemoveCount = this.cache.size - this.maxSize\n\n for (const [key, entry] of entries) {\n if (removedCount >= targetRemoveCount) break\n\n if (entry.objectUrl) {\n const refCount = this.urlRefCount.get(entry.objectUrl) || 0\n if (refCount === 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n this.cache.delete(key)\n removedCount++\n }\n } else {\n this.cache.delete(key)\n removedCount++\n }\n }\n }\n }\n\n async getCachedMedia(src: string): Promise<CachedMedia> {\n let entry = this.cache.get(src)\n\n if (entry) {\n // Update last accessed time\n entry.lastAccessed = Date.now()\n return entry\n }\n\n // Create new entry\n entry = {\n src,\n isLoading: true,\n lastAccessed: Date.now()\n }\n\n // Update cache with loading entry\n this.cache.set(src, entry)\n\n try {\n // Fetch the media\n const response = await fetch(src, { cache: 'force-cache' })\n if (!response.ok) {\n throw new Error(`Failed to fetch: ${response.status}`)\n }\n\n const blob = await response.blob()\n const objectUrl = URL.createObjectURL(blob)\n\n // Update entry with successful result\n const updatedEntry: CachedMedia = {\n src,\n blob,\n objectUrl,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, updatedEntry)\n return updatedEntry\n } catch (error) {\n console.warn('Failed to cache media:', src, error)\n\n // Update entry with error\n const errorEntry: CachedMedia = {\n src,\n error: true,\n isLoading: false,\n lastAccessed: Date.now()\n }\n\n this.cache.set(src, errorEntry)\n return errorEntry\n }\n }\n\n acquireUrl(src: string): string | undefined {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const currentCount = this.urlRefCount.get(entry.objectUrl) || 0\n this.urlRefCount.set(entry.objectUrl, currentCount + 1)\n return entry.objectUrl\n }\n return undefined\n }\n\n releaseUrl(src: string): void {\n const entry = this.cache.get(src)\n if (entry?.objectUrl) {\n const count = (this.urlRefCount.get(entry.objectUrl) || 1) - 1\n if (count <= 0) {\n URL.revokeObjectURL(entry.objectUrl)\n this.urlRefCount.delete(entry.objectUrl)\n // Remove from cache as well\n this.cache.delete(src)\n } else {\n this.urlRefCount.set(entry.objectUrl, count)\n }\n }\n }\n\n clearCache() {\n // Revoke all object URLs\n for (const entry of Array.from(this.cache.values())) {\n if (entry.objectUrl) {\n URL.revokeObjectURL(entry.objectUrl)\n }\n }\n this.cache.clear()\n this.urlRefCount.clear()\n }\n\n destroy() {\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval)\n this.cleanupInterval = null\n }\n this.clearCache()\n }\n}\n\n// Global instance\nlet mediaCacheInstance: MediaCacheService | null = null\n\nexport function useMediaCache(options?: MediaCacheOptions) {\n if (!mediaCacheInstance) {\n mediaCacheInstance = new MediaCacheService(options)\n }\n\n const getCachedMedia = (src: string) =>\n mediaCacheInstance!.getCachedMedia(src)\n const clearCache = () => mediaCacheInstance!.clearCache()\n const acquireUrl = (src: string) => mediaCacheInstance!.acquireUrl(src)\n const releaseUrl = (src: string) => mediaCacheInstance!.releaseUrl(src)\n\n return {\n getCachedMedia,\n clearCache,\n acquireUrl,\n releaseUrl,\n cache: mediaCacheInstance.cache\n }\n}\n\n// Cleanup on page unload\nif (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', () => {\n if (mediaCacheInstance) {\n mediaCacheInstance.destroy()\n }\n })\n}\n","<template>\n <div\n ref=\"containerRef\"\n class=\"relative flex h-full w-full items-center justify-center overflow-hidden\"\n :class=\"containerClass\"\n >\n <Skeleton\n v-if=\"!isImageLoaded\"\n width=\"100%\"\n height=\"100%\"\n class=\"absolute inset-0\"\n />\n <img\n v-if=\"cachedSrc\"\n :src=\"cachedSrc\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n @load=\"onImageLoad\"\n @error=\"onImageError\"\n />\n <div\n v-if=\"hasError\"\n class=\"absolute inset-0 flex items-center justify-center\"\n >\n <img\n src=\"/assets/images/default-template.png\"\n :alt=\"alt\"\n draggable=\"false\"\n :class=\"imageClass\"\n :style=\"imageStyle\"\n />\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Skeleton from 'primevue/skeleton'\nimport { computed, onUnmounted, ref, watch } from 'vue'\n\nimport { useIntersectionObserver } from '@/composables/useIntersectionObserver'\nimport { useMediaCache } from '@/services/mediaCacheService'\nimport type { ClassValue } from '@/utils/tailwindUtil'\n\nconst {\n src,\n alt = '',\n containerClass = '',\n imageClass = '',\n imageStyle,\n rootMargin = '300px'\n} = defineProps<{\n src: string\n alt?: string\n containerClass?: ClassValue\n imageClass?: ClassValue\n imageStyle?: Record<string, any>\n rootMargin?: string\n}>()\n\nconst containerRef = ref<HTMLElement | null>(null)\nconst isIntersecting = ref(false)\nconst isImageLoaded = ref(false)\nconst hasError = ref(false)\nconst cachedSrc = ref<string | undefined>(undefined)\n\nconst { getCachedMedia, acquireUrl, releaseUrl } = useMediaCache()\n\n// Use intersection observer to detect when the image container comes into view\nuseIntersectionObserver(\n containerRef,\n (entries) => {\n const entry = entries[0]\n isIntersecting.value = entry?.isIntersecting ?? false\n },\n {\n rootMargin,\n threshold: 0.1\n }\n)\n\n// Only start loading the image when it's in view\nconst shouldLoad = computed(() => isIntersecting.value)\n\nwatch(\n shouldLoad,\n async (shouldLoadVal) => {\n if (shouldLoadVal && src && !cachedSrc.value && !hasError.value) {\n try {\n const cachedMedia = await getCachedMedia(src)\n if (cachedMedia.error) {\n hasError.value = true\n } else if (cachedMedia.objectUrl) {\n const acquiredUrl = acquireUrl(src)\n cachedSrc.value = acquiredUrl || cachedMedia.objectUrl\n } else {\n cachedSrc.value = src\n }\n } catch (error) {\n console.warn('Failed to load cached media:', error)\n cachedSrc.value = src\n }\n } else if (!shouldLoadVal) {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n // Hide image when out of view\n isImageLoaded.value = false\n cachedSrc.value = undefined\n hasError.value = false\n }\n },\n { immediate: true }\n)\n\nconst onImageLoad = () => {\n isImageLoaded.value = true\n hasError.value = false\n}\n\nconst onImageError = () => {\n hasError.value = true\n isImageLoaded.value = false\n}\n\nonUnmounted(() => {\n if (cachedSrc.value?.startsWith('blob:')) {\n releaseUrl(src)\n }\n})\n</script>\n"],"names":["useFeatureFlags","flags","reactive","api","remoteConfig","featureFlag","__name","featurePath","defaultValue","computed","readonly","selectedItem","_useModel","__props","t","useI18n","getLabel","val","found","o","optionStyle","styles","formatDisplayName","folderName","specialCases","word","DISALLOWED_MODEL_TYPES","useModelTypes","createSharedComposable","modelTypes","isLoading","error","fetchModelTypes","useAsyncState","folder","a","b","err","modelValue","isVisible","handleEscapeKey","event","watch","visible","stop","useEventListener","onWatcherCleanup","showCivitaiHelp","ref","showHuggingFaceHelp","emit","__emit","civitaiIcon","civitaiUrl","huggingFaceIcon","huggingFaceUrl","props","url","value","civitaiImportSource","huggingfaceImportSource","validateSourceUrl","source","hostname","h","useUploadModelWizard","assetsStore","useAssetsStore","modelToNodeStore","useModelToNodeStore","currentStep","isFetchingMetadata","isUploading","uploadStatus","uploadError","wizardData","selectedModelType","importSources","detectedSource","canFetchMetadata","canUploadModel","fetchMetadata","cleanedUrl","supportedSources","s","metadata","assetService","typeTag","tag","type","st","uploadModel","tags","filename","previewId","baseFilename","extension","mimeMatch","providers","provider","goToPreviousStep","dialogStore","useDialogStore","handleFetchMetadata","handleUploadModel","handleClose","onMounted","_imports_0$1","titleKey","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_ctx","showSubscriptionDialog","useSubscription","handleSubscribe","useModelUpload","execute","isUploadButtonEnabled","showUploadDialog","UploadModelDialogHeader","UploadModelDialog","UploadModelUpgradeModalHeader","UploadModelUpgradeModal","_imports_0","useIntersectionObserver","target","callback","options","immediate","observerOptions","isSupported","isIntersecting","observer","cleanup","observe","entries","entry","unobserve","onBeforeUnmount","MediaCacheService","now","keysToDelete","key","removedCount","targetRemoveCount","src","response","blob","objectUrl","updatedEntry","errorEntry","currentCount","count","mediaCacheInstance","useMediaCache","containerRef","isImageLoaded","hasError","cachedSrc","getCachedMedia","acquireUrl","releaseUrl","shouldLoad","shouldLoadVal","cachedMedia","acquiredUrl","onImageLoad","onImageError","onUnmounted"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var G=Object.defineProperty;var r=(v,_)=>G(v,"name",{value:_,configurable:!0});import{g as J,s as K,ad as F,d as Q,ae as P,q as j,a as q,G as W}from"./vendor-primevue-rR0TB_Js.js";import{a2 as z,bh as X,bi as M,_ as k,m as Y,$ as Z,o as ee,a1 as te,z as se,l as ae,dm as oe,dn as ie}from"./index-FoJ8Eu4a.js";import{r as b,w as O,ci as V,bq as H,E as I,h as ne,c as $,d as f,j as U,z as c,br as o,k as d,A as w,u as n,e as l,q as B,l as re,s as le}from"./vendor-other-BxP-0xn6.js";import{u as ce}from"./vendor-vue-BEnTqVKr.js";import"./vendor-xterm-CWYFmgbN.js";import"./vendor-three-BsnVSA6y.js";import"./vendor-tiptap-4vvRsqpM.js";var R=(v=>(v.CREDIT_ADDED="credit_added",v.ACCOUNT_CREATED="account_created",v.API_USAGE_STARTED="api_usage_started",v.API_USAGE_COMPLETED="api_usage_completed",v))(R||{});const N=V.create({baseURL:M(),headers:{"Content-Type":"application/json"}}),de=r(()=>{const v=b(!1),_=b(null),{d:x}=ce();O(()=>M(),e=>{N.defaults.baseURL=e});const A=r((e,s,y)=>{if(X(e))return;let p;if(!V.isAxiosError(e))p=`${s} failed: ${e instanceof Error?e.message:String(e)}`;else{const a=e,i=a.response?.status;i&&y?.[i]?p=y[i]:p=a.response?.data?.message??`${s} failed with status ${i}`}_.value=p},"handleRequestError"),g=r(async(e,s)=>{const{errorContext:y,routeSpecificErrors:p}=s;v.value=!0,_.value=null;try{return(await e()).data}catch(a){return A(a,y,p),null}finally{v.value=!1}},"executeRequest");function m(e){switch(e){case"credit_added":return"Credits Added";case"account_created":return"Account Created";case"api_usage_completed":return"API Usage";default:return e}}r(m,"formatEventType");function u(e){const s=new Date(e);return x(s,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}r(u,"formatDate");function S(e){return e.split("_").map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join(" ")}r(S,"formatJsonKey");function C(e){return typeof e=="number"?e.toLocaleString():typeof e=="string"&&e.match(/^\d{4}-\d{2}-\d{2}/)?new Date(e).toLocaleString():e}r(C,"formatJsonValue");function D(e){switch(e){case"credit_added":return"success";case"account_created":return"info";case"api_usage_completed":return"warning";default:return"info"}}r(D,"getEventSeverity");function E(e){const{amount:s,api_name:y,model:p,...a}=e.params||{};return Object.keys(a).length>0}r(E,"hasAdditionalInfo");function L(e){const{...s}=e.params||{};return Object.entries(s).map(([y,p])=>{const a=S(y),i=C(p);return`<strong>${a}:</strong> ${i}`}).join("<br>")}r(L,"getTooltipContent");function t(e){return e?(e/100).toFixed(2):"0.00"}r(t,"formatAmount");async function h({page:e=1,limit:s=10}={}){const y="Fetching customer events",p={400:"Invalid input, object invalid",404:"Not found"},a=await z().getAuthHeader();return a?await g(()=>N.get("/customers/events",{params:{page:e,limit:s},headers:a}),{errorContext:y,routeSpecificErrors:p}):(_.value="Authentication header is missing",null)}return r(h,"getMyEvents"),{isLoading:v,error:_,getMyEvents:h,formatEventType:m,getEventSeverity:D,formatAmount:t,hasAdditionalInfo:E,formatDate:u,formatJsonKey:S,formatJsonValue:C,getTooltipContent:L}},"useCustomerEventsService"),ue={key:0,class:"flex items-center justify-center p-8"},me={key:1,class:"p-4"},pe={class:"event-details"},fe={key:0,class:"font-semibold text-green-500"},ve={key:1},ge={key:2,class:"flex flex-col gap-1"},he={class:"font-semibold"},_e={class:"text-sm text-smoke-400"},ye=H({__name:"UsageLogsTable",setup(v,{expose:_}){const x=b([]),A=b(!0),g=b(null),m=de(),u=b({page:1,limit:7,total:0,totalPages:0}),S=I(()=>(u.value.page-1)*u.value.limit),C=I(()=>{const t=new Map;return x.value.forEach(h=>{m.hasAdditionalInfo(h)&&h.event_id&&t.set(h.event_id,m.getTooltipContent(h))}),t}),D=r(async()=>{A.value=!0,g.value=null;try{const t=await m.getMyEvents({page:u.value.page,limit:u.value.limit});t?(t.events&&(x.value=t.events),t.page&&(u.value.page=t.page),t.limit&&(u.value.limit=t.limit),t.total&&(u.value.total=t.total),t.totalPages&&(u.value.totalPages=t.totalPages),Y()?.checkForCompletedTopup(t.events)):g.value=m.error.value||"Failed to load events"}catch(t){g.value=t instanceof Error?t.message:"Unknown error",console.error("Error loading events:",t)}finally{A.value=!1}},"loadEvents"),E=r(t=>{u.value.page=t.page+1,D().catch(h=>{console.error("Error loading events:",h)})},"onPageChange");return _({refresh:r(async()=>{u.value.page=1,await D()},"refresh")}),(t,h)=>{const e=ne("tooltip");return f(),$("div",null,[A.value?(f(),$("div",ue,[c(o(J))])):g.value?(f(),$("div",me,[c(o(K),{severity:"error",closable:!1},{default:d(()=>[w(n(g.value),1)]),_:1})])):(f(),U(o(F),{key:2,value:x.value,paginator:!0,rows:u.value.limit,"total-records":u.value.total,first:S.value,lazy:!0,class:"p-datatable-sm custom-datatable",onPage:E},{default:d(()=>[c(o(P),{field:"event_type",header:t.$t("credits.eventType")},{body:d(({data:s})=>[c(o(Q),{value:o(m).formatEventType(s.event_type),severity:o(m).getEventSeverity(s.event_type)},null,8,["value","severity"])]),_:1},8,["header"]),c(o(P),{field:"details",header:t.$t("credits.details")},{body:d(({data:s})=>[l("div",pe,[s.event_type===o(R).CREDIT_ADDED?(f(),$("div",fe,n(t.$t("credits.added"))+" $"+n(o(m).formatAmount(s.params?.amount)),1)):s.event_type===o(R).ACCOUNT_CREATED?(f(),$("div",ve,n(t.$t("credits.accountInitialized")),1)):s.event_type===o(R).API_USAGE_COMPLETED?(f(),$("div",ge,[l("div",he,n(s.params?.api_name||"API"),1),l("div",_e,n(t.$t("credits.model"))+": "+n(s.params?.model||"-"),1)])):B("",!0)])]),_:1},8,["header"]),c(o(P),{field:"createdAt",header:t.$t("credits.time")},{body:d(({data:s})=>[w(n(o(m).formatDate(s.createdAt)),1)]),_:1},8,["header"]),c(o(P),{field:"params",header:t.$t("credits.additionalInfo")},{body:d(({data:s})=>[o(m).hasAdditionalInfo(s)?re((f(),U(k,{key:0,variant:"textonly",size:"icon-sm","aria-label":t.$t("credits.additionalInfo")},{default:d(()=>h[0]||(h[0]=[l("i",{class:"pi pi-info-circle"},null,-1)])),_:2},1032,["aria-label"])),[[e,{escape:!1,value:C.value.get(s.event_id)||"",pt:{text:{style:{width:"max-content !important"}}}},void 0,{top:!0}]]):B("",!0)]),_:1},8,["header"])]),_:1},8,["value","rows","total-records","first"]))])}}}),Ce={class:"flex h-full flex-col"},$e={class:"mb-2 text-2xl font-bold"},be={class:"flex flex-col gap-2"},xe={class:"text-sm font-medium text-muted"},Ae={class:"flex items-center justify-between"},Ee={class:"flex flex-row items-center"},ke={key:1,class:"text-xs text-muted"},we={class:"flex items-center justify-between"},Se={key:0,class:"grow"},De={class:"text-sm font-medium"},Te={class:"text-xs text-muted"},Pe={class:"flex flex-row gap-2"},Fe=H({__name:"LegacyCreditsPanel",setup(v){const{buildDocsUrl:_,docsPaths:x}=Z(),A=ee(),g=z(),m=te(),u=se(),{isActiveSubscription:S}=ae(),C=I(()=>g.loading),D=I(()=>g.isFetchingBalance),E=b(null),L=I(()=>g.lastBalanceUpdateTime?g.lastBalanceUpdateTime.toLocaleString():"");O(()=>g.lastBalanceUpdateTime,(a,i)=>{a&&a!==i&&E.value&&E.value.refresh()});const t=r(()=>{A.showTopUpCreditsDialog()},"handlePurchaseCreditsClick"),h=r(async()=>{await m.accessBillingPortal()},"handleCreditsHistoryClick"),e=r(async()=>{await u.execute("Comfy.ContactSupport")},"handleMessageSupport"),s=r(()=>{window.open(_("/tutorials/api-nodes/faq",{includeLocale:!0}),"_blank")},"handleFaqClick"),y=r(()=>{window.open(_(x.partnerNodesPricing,{includeLocale:!0}),"_blank")},"handleOpenPartnerNodesInfo"),p=b([]);return(a,i)=>(f(),U(o(W),{value:"Credits",class:"credits-container h-full"},{default:d(()=>[l("div",Ce,[l("h2",$e,n(a.$t("credits.credits")),1),c(o(j)),l("div",be,[l("h3",xe,n(a.$t("credits.yourCreditBalance")),1),l("div",Ae,[c(oe,{"text-class":"text-3xl font-bold"}),C.value?(f(),U(o(q),{key:0,width:"2rem",height:"2rem"})):o(S)?(f(),U(k,{key:1,loading:C.value,onClick:t},{default:d(()=>[w(n(a.$t("credits.purchaseCredits")),1)]),_:1},8,["loading"])):B("",!0)]),l("div",Ee,[D.value?(f(),U(o(q),{key:0,width:"12rem",height:"1rem",class:"text-xs"})):L.value?(f(),$("div",ke,n(a.$t("credits.lastUpdated"))+": "+n(L.value),1)):B("",!0),c(k,{variant:"muted-textonly",size:"icon-sm","aria-label":a.$t("g.refresh"),onClick:i[0]||(i[0]=()=>o(m).fetchBalance())},{default:d(()=>i[1]||(i[1]=[l("i",{class:"pi pi-refresh"},null,-1)])),_:1},8,["aria-label"])])]),l("div",we,[l("h3",null,n(a.$t("credits.activity")),1),c(k,{variant:"muted-textonly",loading:C.value,onClick:h},{default:d(()=>[i[2]||(i[2]=l("i",{class:"pi pi-arrow-up-right"},null,-1)),w(" "+n(a.$t("credits.invoiceHistory")),1)]),_:1},8,["loading"])]),p.value.length>0?(f(),$("div",Se,[c(o(F),{value:p.value,"show-headers":!1},{default:d(()=>[c(o(P),{field:"title",header:a.$t("g.name")},{body:d(({data:T})=>[l("div",De,n(T.title),1),l("div",Te,n(T.timestamp),1)]),_:1},8,["header"]),c(o(P),{field:"amount",header:a.$t("g.amount")},{body:d(({data:T})=>[l("div",{class:le(["text-center text-base font-medium",T.isPositive?"text-sky-500":"text-red-400"])},n(T.isPositive?"+":"-")+"$"+n(o(ie)(T.amount,"usd")),3)]),_:1},8,["header"])]),_:1},8,["value"])])):B("",!0),c(o(j)),c(ye,{ref_key:"usageLogsTableRef",ref:E},null,512),l("div",Pe,[c(k,{variant:"muted-textonly",onClick:s},{default:d(()=>[i[3]||(i[3]=l("i",{class:"pi pi-question-circle"},null,-1)),w(" "+n(a.$t("credits.faqs")),1)]),_:1}),c(k,{variant:"muted-textonly",onClick:y},{default:d(()=>[i[4]||(i[4]=l("i",{class:"pi pi-question-circle"},null,-1)),w(" "+n(a.$t("subscription.partnerNodesCredits")),1)]),_:1}),c(k,{variant:"muted-textonly",onClick:e},{default:d(()=>[i[5]||(i[5]=l("i",{class:"pi pi-comments"},null,-1)),w(" "+n(a.$t("credits.messageSupport")),1)]),_:1})])])]),_:1}))}});export{Fe as default};
2
- //# sourceMappingURL=LegacyCreditsPanel-DSos04Q3.js.map
1
+ var G=Object.defineProperty;var r=(v,_)=>G(v,"name",{value:_,configurable:!0});import{g as J,s as K,ad as F,d as Q,ae as P,q as j,a as q,G as W}from"./vendor-primevue-rR0TB_Js.js";import{a2 as z,bh as X,bi as M,_ as k,m as Y,$ as Z,o as ee,a1 as te,z as se,l as ae,dm as oe,dn as ie}from"./index-BODInyrK.js";import{r as b,w as O,ci as V,bq as H,E as I,h as ne,c as $,d as f,j as U,z as c,br as o,k as d,A as w,u as n,e as l,q as B,l as re,s as le}from"./vendor-other-BxP-0xn6.js";import{u as ce}from"./vendor-vue-BEnTqVKr.js";import"./vendor-xterm-CWYFmgbN.js";import"./vendor-three-BsnVSA6y.js";import"./vendor-tiptap-4vvRsqpM.js";var R=(v=>(v.CREDIT_ADDED="credit_added",v.ACCOUNT_CREATED="account_created",v.API_USAGE_STARTED="api_usage_started",v.API_USAGE_COMPLETED="api_usage_completed",v))(R||{});const N=V.create({baseURL:M(),headers:{"Content-Type":"application/json"}}),de=r(()=>{const v=b(!1),_=b(null),{d:x}=ce();O(()=>M(),e=>{N.defaults.baseURL=e});const A=r((e,s,y)=>{if(X(e))return;let p;if(!V.isAxiosError(e))p=`${s} failed: ${e instanceof Error?e.message:String(e)}`;else{const a=e,i=a.response?.status;i&&y?.[i]?p=y[i]:p=a.response?.data?.message??`${s} failed with status ${i}`}_.value=p},"handleRequestError"),g=r(async(e,s)=>{const{errorContext:y,routeSpecificErrors:p}=s;v.value=!0,_.value=null;try{return(await e()).data}catch(a){return A(a,y,p),null}finally{v.value=!1}},"executeRequest");function m(e){switch(e){case"credit_added":return"Credits Added";case"account_created":return"Account Created";case"api_usage_completed":return"API Usage";default:return e}}r(m,"formatEventType");function u(e){const s=new Date(e);return x(s,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}r(u,"formatDate");function S(e){return e.split("_").map(s=>s.charAt(0).toUpperCase()+s.slice(1)).join(" ")}r(S,"formatJsonKey");function C(e){return typeof e=="number"?e.toLocaleString():typeof e=="string"&&e.match(/^\d{4}-\d{2}-\d{2}/)?new Date(e).toLocaleString():e}r(C,"formatJsonValue");function D(e){switch(e){case"credit_added":return"success";case"account_created":return"info";case"api_usage_completed":return"warning";default:return"info"}}r(D,"getEventSeverity");function E(e){const{amount:s,api_name:y,model:p,...a}=e.params||{};return Object.keys(a).length>0}r(E,"hasAdditionalInfo");function L(e){const{...s}=e.params||{};return Object.entries(s).map(([y,p])=>{const a=S(y),i=C(p);return`<strong>${a}:</strong> ${i}`}).join("<br>")}r(L,"getTooltipContent");function t(e){return e?(e/100).toFixed(2):"0.00"}r(t,"formatAmount");async function h({page:e=1,limit:s=10}={}){const y="Fetching customer events",p={400:"Invalid input, object invalid",404:"Not found"},a=await z().getAuthHeader();return a?await g(()=>N.get("/customers/events",{params:{page:e,limit:s},headers:a}),{errorContext:y,routeSpecificErrors:p}):(_.value="Authentication header is missing",null)}return r(h,"getMyEvents"),{isLoading:v,error:_,getMyEvents:h,formatEventType:m,getEventSeverity:D,formatAmount:t,hasAdditionalInfo:E,formatDate:u,formatJsonKey:S,formatJsonValue:C,getTooltipContent:L}},"useCustomerEventsService"),ue={key:0,class:"flex items-center justify-center p-8"},me={key:1,class:"p-4"},pe={class:"event-details"},fe={key:0,class:"font-semibold text-green-500"},ve={key:1},ge={key:2,class:"flex flex-col gap-1"},he={class:"font-semibold"},_e={class:"text-sm text-smoke-400"},ye=H({__name:"UsageLogsTable",setup(v,{expose:_}){const x=b([]),A=b(!0),g=b(null),m=de(),u=b({page:1,limit:7,total:0,totalPages:0}),S=I(()=>(u.value.page-1)*u.value.limit),C=I(()=>{const t=new Map;return x.value.forEach(h=>{m.hasAdditionalInfo(h)&&h.event_id&&t.set(h.event_id,m.getTooltipContent(h))}),t}),D=r(async()=>{A.value=!0,g.value=null;try{const t=await m.getMyEvents({page:u.value.page,limit:u.value.limit});t?(t.events&&(x.value=t.events),t.page&&(u.value.page=t.page),t.limit&&(u.value.limit=t.limit),t.total&&(u.value.total=t.total),t.totalPages&&(u.value.totalPages=t.totalPages),Y()?.checkForCompletedTopup(t.events)):g.value=m.error.value||"Failed to load events"}catch(t){g.value=t instanceof Error?t.message:"Unknown error",console.error("Error loading events:",t)}finally{A.value=!1}},"loadEvents"),E=r(t=>{u.value.page=t.page+1,D().catch(h=>{console.error("Error loading events:",h)})},"onPageChange");return _({refresh:r(async()=>{u.value.page=1,await D()},"refresh")}),(t,h)=>{const e=ne("tooltip");return f(),$("div",null,[A.value?(f(),$("div",ue,[c(o(J))])):g.value?(f(),$("div",me,[c(o(K),{severity:"error",closable:!1},{default:d(()=>[w(n(g.value),1)]),_:1})])):(f(),U(o(F),{key:2,value:x.value,paginator:!0,rows:u.value.limit,"total-records":u.value.total,first:S.value,lazy:!0,class:"p-datatable-sm custom-datatable",onPage:E},{default:d(()=>[c(o(P),{field:"event_type",header:t.$t("credits.eventType")},{body:d(({data:s})=>[c(o(Q),{value:o(m).formatEventType(s.event_type),severity:o(m).getEventSeverity(s.event_type)},null,8,["value","severity"])]),_:1},8,["header"]),c(o(P),{field:"details",header:t.$t("credits.details")},{body:d(({data:s})=>[l("div",pe,[s.event_type===o(R).CREDIT_ADDED?(f(),$("div",fe,n(t.$t("credits.added"))+" $"+n(o(m).formatAmount(s.params?.amount)),1)):s.event_type===o(R).ACCOUNT_CREATED?(f(),$("div",ve,n(t.$t("credits.accountInitialized")),1)):s.event_type===o(R).API_USAGE_COMPLETED?(f(),$("div",ge,[l("div",he,n(s.params?.api_name||"API"),1),l("div",_e,n(t.$t("credits.model"))+": "+n(s.params?.model||"-"),1)])):B("",!0)])]),_:1},8,["header"]),c(o(P),{field:"createdAt",header:t.$t("credits.time")},{body:d(({data:s})=>[w(n(o(m).formatDate(s.createdAt)),1)]),_:1},8,["header"]),c(o(P),{field:"params",header:t.$t("credits.additionalInfo")},{body:d(({data:s})=>[o(m).hasAdditionalInfo(s)?re((f(),U(k,{key:0,variant:"textonly",size:"icon-sm","aria-label":t.$t("credits.additionalInfo")},{default:d(()=>h[0]||(h[0]=[l("i",{class:"pi pi-info-circle"},null,-1)])),_:2},1032,["aria-label"])),[[e,{escape:!1,value:C.value.get(s.event_id)||"",pt:{text:{style:{width:"max-content !important"}}}},void 0,{top:!0}]]):B("",!0)]),_:1},8,["header"])]),_:1},8,["value","rows","total-records","first"]))])}}}),Ce={class:"flex h-full flex-col"},$e={class:"mb-2 text-2xl font-bold"},be={class:"flex flex-col gap-2"},xe={class:"text-sm font-medium text-muted"},Ae={class:"flex items-center justify-between"},Ee={class:"flex flex-row items-center"},ke={key:1,class:"text-xs text-muted"},we={class:"flex items-center justify-between"},Se={key:0,class:"grow"},De={class:"text-sm font-medium"},Te={class:"text-xs text-muted"},Pe={class:"flex flex-row gap-2"},Fe=H({__name:"LegacyCreditsPanel",setup(v){const{buildDocsUrl:_,docsPaths:x}=Z(),A=ee(),g=z(),m=te(),u=se(),{isActiveSubscription:S}=ae(),C=I(()=>g.loading),D=I(()=>g.isFetchingBalance),E=b(null),L=I(()=>g.lastBalanceUpdateTime?g.lastBalanceUpdateTime.toLocaleString():"");O(()=>g.lastBalanceUpdateTime,(a,i)=>{a&&a!==i&&E.value&&E.value.refresh()});const t=r(()=>{A.showTopUpCreditsDialog()},"handlePurchaseCreditsClick"),h=r(async()=>{await m.accessBillingPortal()},"handleCreditsHistoryClick"),e=r(async()=>{await u.execute("Comfy.ContactSupport")},"handleMessageSupport"),s=r(()=>{window.open(_("/tutorials/api-nodes/faq",{includeLocale:!0}),"_blank")},"handleFaqClick"),y=r(()=>{window.open(_(x.partnerNodesPricing,{includeLocale:!0}),"_blank")},"handleOpenPartnerNodesInfo"),p=b([]);return(a,i)=>(f(),U(o(W),{value:"Credits",class:"credits-container h-full"},{default:d(()=>[l("div",Ce,[l("h2",$e,n(a.$t("credits.credits")),1),c(o(j)),l("div",be,[l("h3",xe,n(a.$t("credits.yourCreditBalance")),1),l("div",Ae,[c(oe,{"text-class":"text-3xl font-bold"}),C.value?(f(),U(o(q),{key:0,width:"2rem",height:"2rem"})):o(S)?(f(),U(k,{key:1,loading:C.value,onClick:t},{default:d(()=>[w(n(a.$t("credits.purchaseCredits")),1)]),_:1},8,["loading"])):B("",!0)]),l("div",Ee,[D.value?(f(),U(o(q),{key:0,width:"12rem",height:"1rem",class:"text-xs"})):L.value?(f(),$("div",ke,n(a.$t("credits.lastUpdated"))+": "+n(L.value),1)):B("",!0),c(k,{variant:"muted-textonly",size:"icon-sm","aria-label":a.$t("g.refresh"),onClick:i[0]||(i[0]=()=>o(m).fetchBalance())},{default:d(()=>i[1]||(i[1]=[l("i",{class:"pi pi-refresh"},null,-1)])),_:1},8,["aria-label"])])]),l("div",we,[l("h3",null,n(a.$t("credits.activity")),1),c(k,{variant:"muted-textonly",loading:C.value,onClick:h},{default:d(()=>[i[2]||(i[2]=l("i",{class:"pi pi-arrow-up-right"},null,-1)),w(" "+n(a.$t("credits.invoiceHistory")),1)]),_:1},8,["loading"])]),p.value.length>0?(f(),$("div",Se,[c(o(F),{value:p.value,"show-headers":!1},{default:d(()=>[c(o(P),{field:"title",header:a.$t("g.name")},{body:d(({data:T})=>[l("div",De,n(T.title),1),l("div",Te,n(T.timestamp),1)]),_:1},8,["header"]),c(o(P),{field:"amount",header:a.$t("g.amount")},{body:d(({data:T})=>[l("div",{class:le(["text-center text-base font-medium",T.isPositive?"text-sky-500":"text-red-400"])},n(T.isPositive?"+":"-")+"$"+n(o(ie)(T.amount,"usd")),3)]),_:1},8,["header"])]),_:1},8,["value"])])):B("",!0),c(o(j)),c(ye,{ref_key:"usageLogsTableRef",ref:E},null,512),l("div",Pe,[c(k,{variant:"muted-textonly",onClick:s},{default:d(()=>[i[3]||(i[3]=l("i",{class:"pi pi-question-circle"},null,-1)),w(" "+n(a.$t("credits.faqs")),1)]),_:1}),c(k,{variant:"muted-textonly",onClick:y},{default:d(()=>[i[4]||(i[4]=l("i",{class:"pi pi-question-circle"},null,-1)),w(" "+n(a.$t("subscription.partnerNodesCredits")),1)]),_:1}),c(k,{variant:"muted-textonly",onClick:e},{default:d(()=>[i[5]||(i[5]=l("i",{class:"pi pi-comments"},null,-1)),w(" "+n(a.$t("credits.messageSupport")),1)]),_:1})])])]),_:1}))}});export{Fe as default};
2
+ //# sourceMappingURL=LegacyCreditsPanel-BujZmWgC.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LegacyCreditsPanel-DSos04Q3.js","sources":["../../src/services/customerEventsService.ts","../../src/components/dialog/content/setting/UsageLogsTable.vue","../../src/components/dialog/content/setting/LegacyCreditsPanel.vue"],"sourcesContent":["import type { AxiosError, AxiosResponse } from 'axios'\nimport axios from 'axios'\nimport { ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { getComfyApiBaseUrl } from '@/config/comfyApi'\nimport { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'\nimport type { components, operations } from '@/types/comfyRegistryTypes'\nimport { isAbortError } from '@/utils/typeGuardUtil'\n\nexport enum EventType {\n CREDIT_ADDED = 'credit_added',\n ACCOUNT_CREATED = 'account_created',\n API_USAGE_STARTED = 'api_usage_started',\n API_USAGE_COMPLETED = 'api_usage_completed'\n}\n\ntype CustomerEventsResponse =\n operations['GetCustomerEvents']['responses']['200']['content']['application/json']\n\ntype CustomerEventsResponseQuery =\n operations['GetCustomerEvents']['parameters']['query']\n\nexport type AuditLog = components['schemas']['AuditLog']\n\nconst customerApiClient = axios.create({\n baseURL: getComfyApiBaseUrl(),\n headers: {\n 'Content-Type': 'application/json'\n }\n})\n\nexport const useCustomerEventsService = () => {\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n const { d } = useI18n()\n\n watch(\n () => getComfyApiBaseUrl(),\n (url) => {\n customerApiClient.defaults.baseURL = url\n }\n )\n\n const handleRequestError = (\n err: unknown,\n context: string,\n routeSpecificErrors?: Record<number, string>\n ) => {\n // Don't treat cancellation as an error\n if (isAbortError(err)) return\n\n let message: string\n if (!axios.isAxiosError(err)) {\n message = `${context} failed: ${err instanceof Error ? err.message : String(err)}`\n } else {\n const axiosError = err as AxiosError<{ message: string }>\n const status = axiosError.response?.status\n if (status && routeSpecificErrors?.[status]) {\n message = routeSpecificErrors[status]\n } else {\n message =\n axiosError.response?.data?.message ??\n `${context} failed with status ${status}`\n }\n }\n\n error.value = message\n }\n\n const executeRequest = async <T>(\n requestCall: () => Promise<AxiosResponse<T>>,\n options: {\n errorContext: string\n routeSpecificErrors?: Record<number, string>\n }\n ): Promise<T | null> => {\n const { errorContext, routeSpecificErrors } = options\n\n isLoading.value = true\n error.value = null\n\n try {\n const response = await requestCall()\n return response.data\n } catch (err) {\n handleRequestError(err, errorContext, routeSpecificErrors)\n return null\n } finally {\n isLoading.value = false\n }\n }\n\n function formatEventType(eventType: string) {\n switch (eventType) {\n case 'credit_added':\n return 'Credits Added'\n case 'account_created':\n return 'Account Created'\n case 'api_usage_completed':\n return 'API Usage'\n default:\n return eventType\n }\n }\n\n function formatDate(dateString: string): string {\n const date = new Date(dateString)\n\n return d(date, {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n })\n }\n\n function formatJsonKey(key: string) {\n return key\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n }\n\n function formatJsonValue(value: any) {\n if (typeof value === 'number') {\n // Format numbers with commas and decimals if needed\n return value.toLocaleString()\n }\n if (typeof value === 'string' && value.match(/^\\d{4}-\\d{2}-\\d{2}/)) {\n // Format dates nicely\n return new Date(value).toLocaleString()\n }\n return value\n }\n\n function getEventSeverity(eventType: string) {\n switch (eventType) {\n case 'credit_added':\n return 'success'\n case 'account_created':\n return 'info'\n case 'api_usage_completed':\n return 'warning'\n default:\n return 'info'\n }\n }\n\n function hasAdditionalInfo(event: AuditLog) {\n const { amount, api_name, model, ...otherParams } = event.params || {}\n return Object.keys(otherParams).length > 0\n }\n\n function getTooltipContent(event: AuditLog) {\n const { ...params } = event.params || {}\n\n return Object.entries(params)\n .map(([key, value]) => {\n const formattedKey = formatJsonKey(key)\n const formattedValue = formatJsonValue(value)\n return `<strong>${formattedKey}:</strong> ${formattedValue}`\n })\n .join('<br>')\n }\n\n function formatAmount(amountMicros?: number) {\n if (!amountMicros) return '0.00'\n return (amountMicros / 100).toFixed(2)\n }\n\n async function getMyEvents({\n page = 1,\n limit = 10\n }: CustomerEventsResponseQuery = {}): Promise<CustomerEventsResponse | null> {\n const errorContext = 'Fetching customer events'\n const routeSpecificErrors = {\n 400: 'Invalid input, object invalid',\n 404: 'Not found'\n }\n\n // Get auth headers\n const authHeaders = await useFirebaseAuthStore().getAuthHeader()\n if (!authHeaders) {\n error.value = 'Authentication header is missing'\n return null\n }\n\n const result = await executeRequest<CustomerEventsResponse>(\n () =>\n customerApiClient.get('/customers/events', {\n params: { page, limit },\n headers: authHeaders\n }),\n { errorContext, routeSpecificErrors }\n )\n\n return result\n }\n\n return {\n // State\n isLoading,\n error,\n\n // Methods\n getMyEvents,\n formatEventType,\n getEventSeverity,\n formatAmount,\n hasAdditionalInfo,\n formatDate,\n formatJsonKey,\n formatJsonValue,\n getTooltipContent\n }\n}\n","<template>\n <div>\n <div v-if=\"loading\" class=\"flex items-center justify-center p-8\">\n <ProgressSpinner />\n </div>\n <div v-else-if=\"error\" class=\"p-4\">\n <Message severity=\"error\" :closable=\"false\">{{ error }}</Message>\n </div>\n <DataTable\n v-else\n :value=\"events\"\n :paginator=\"true\"\n :rows=\"pagination.limit\"\n :total-records=\"pagination.total\"\n :first=\"dataTableFirst\"\n :lazy=\"true\"\n class=\"p-datatable-sm custom-datatable\"\n @page=\"onPageChange\"\n >\n <Column field=\"event_type\" :header=\"$t('credits.eventType')\">\n <template #body=\"{ data }\">\n <Badge\n :value=\"customerEventService.formatEventType(data.event_type)\"\n :severity=\"customerEventService.getEventSeverity(data.event_type)\"\n />\n </template>\n </Column>\n <Column field=\"details\" :header=\"$t('credits.details')\">\n <template #body=\"{ data }\">\n <div class=\"event-details\">\n <!-- Credits Added -->\n <template v-if=\"data.event_type === EventType.CREDIT_ADDED\">\n <div class=\"font-semibold text-green-500\">\n {{ $t('credits.added') }} ${{\n customerEventService.formatAmount(data.params?.amount)\n }}\n </div>\n </template>\n\n <!-- Account Created -->\n <template v-else-if=\"data.event_type === EventType.ACCOUNT_CREATED\">\n <div>{{ $t('credits.accountInitialized') }}</div>\n </template>\n\n <!-- API Usage -->\n <template\n v-else-if=\"data.event_type === EventType.API_USAGE_COMPLETED\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"font-semibold\">\n {{ data.params?.api_name || 'API' }}\n </div>\n <div class=\"text-sm text-smoke-400\">\n {{ $t('credits.model') }}: {{ data.params?.model || '-' }}\n </div>\n </div>\n </template>\n </div>\n </template>\n </Column>\n <Column field=\"createdAt\" :header=\"$t('credits.time')\">\n <template #body=\"{ data }\">\n {{ customerEventService.formatDate(data.createdAt) }}\n </template>\n </Column>\n <Column field=\"params\" :header=\"$t('credits.additionalInfo')\">\n <template #body=\"{ data }\">\n <Button\n v-if=\"customerEventService.hasAdditionalInfo(data)\"\n v-tooltip.top=\"{\n escape: false,\n value: tooltipContentMap.get(data.event_id) || '',\n pt: {\n text: {\n style: {\n width: 'max-content !important'\n }\n }\n }\n }\"\n variant=\"textonly\"\n size=\"icon-sm\"\n :aria-label=\"$t('credits.additionalInfo')\"\n >\n <i class=\"pi pi-info-circle\" />\n </Button>\n </template>\n </Column>\n </DataTable>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Badge from 'primevue/badge'\nimport Column from 'primevue/column'\nimport DataTable from 'primevue/datatable'\nimport Message from 'primevue/message'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport { computed, ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useTelemetry } from '@/platform/telemetry'\nimport type { AuditLog } from '@/services/customerEventsService'\nimport {\n EventType,\n useCustomerEventsService\n} from '@/services/customerEventsService'\n\nconst events = ref<AuditLog[]>([])\nconst loading = ref(true)\nconst error = ref<string | null>(null)\n\nconst customerEventService = useCustomerEventsService()\n\nconst pagination = ref({\n page: 1,\n limit: 7,\n total: 0,\n totalPages: 0\n})\n\nconst dataTableFirst = computed(\n () => (pagination.value.page - 1) * pagination.value.limit\n)\n\nconst tooltipContentMap = computed(() => {\n const map = new Map<string, string>()\n events.value.forEach((event) => {\n if (customerEventService.hasAdditionalInfo(event) && event.event_id) {\n map.set(event.event_id, customerEventService.getTooltipContent(event))\n }\n })\n return map\n})\n\nconst loadEvents = async () => {\n loading.value = true\n error.value = null\n\n try {\n const response = await customerEventService.getMyEvents({\n page: pagination.value.page,\n limit: pagination.value.limit\n })\n\n if (response) {\n if (response.events) {\n events.value = response.events\n }\n\n if (response.page) {\n pagination.value.page = response.page\n }\n\n if (response.limit) {\n pagination.value.limit = response.limit\n }\n\n if (response.total) {\n pagination.value.total = response.total\n }\n\n if (response.totalPages) {\n pagination.value.totalPages = response.totalPages\n }\n\n // Check if a pending top-up has completed\n useTelemetry()?.checkForCompletedTopup(response.events)\n } else {\n error.value = customerEventService.error.value || 'Failed to load events'\n }\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'Unknown error'\n console.error('Error loading events:', err)\n } finally {\n loading.value = false\n }\n}\n\nconst onPageChange = (event: { page: number }) => {\n pagination.value.page = event.page + 1\n loadEvents().catch((error) => {\n console.error('Error loading events:', error)\n })\n}\n\nconst refresh = async () => {\n pagination.value.page = 1\n await loadEvents()\n}\n\ndefineExpose({\n refresh\n})\n</script>\n","<template>\n <TabPanel value=\"Credits\" class=\"credits-container h-full\">\n <!-- Legacy Design -->\n <div class=\"flex h-full flex-col\">\n <h2 class=\"mb-2 text-2xl font-bold\">\n {{ $t('credits.credits') }}\n </h2>\n\n <Divider />\n\n <div class=\"flex flex-col gap-2\">\n <h3 class=\"text-sm font-medium text-muted\">\n {{ $t('credits.yourCreditBalance') }}\n </h3>\n <div class=\"flex items-center justify-between\">\n <UserCredit text-class=\"text-3xl font-bold\" />\n <Skeleton v-if=\"loading\" width=\"2rem\" height=\"2rem\" />\n <Button\n v-else-if=\"isActiveSubscription\"\n :loading=\"loading\"\n @click=\"handlePurchaseCreditsClick\"\n >\n {{ $t('credits.purchaseCredits') }}\n </Button>\n </div>\n <div class=\"flex flex-row items-center\">\n <Skeleton\n v-if=\"balanceLoading\"\n width=\"12rem\"\n height=\"1rem\"\n class=\"text-xs\"\n />\n <div v-else-if=\"formattedLastUpdateTime\" class=\"text-xs text-muted\">\n {{ $t('credits.lastUpdated') }}: {{ formattedLastUpdateTime }}\n </div>\n <Button\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n :aria-label=\"$t('g.refresh')\"\n @click=\"() => authActions.fetchBalance()\"\n >\n <i class=\"pi pi-refresh\" />\n </Button>\n </div>\n </div>\n\n <div class=\"flex items-center justify-between\">\n <h3>{{ $t('credits.activity') }}</h3>\n <Button\n variant=\"muted-textonly\"\n :loading=\"loading\"\n @click=\"handleCreditsHistoryClick\"\n >\n <i class=\"pi pi-arrow-up-right\" />\n {{ $t('credits.invoiceHistory') }}\n </Button>\n </div>\n\n <template v-if=\"creditHistory.length > 0\">\n <div class=\"grow\">\n <DataTable :value=\"creditHistory\" :show-headers=\"false\">\n <Column field=\"title\" :header=\"$t('g.name')\">\n <template #body=\"{ data }\">\n <div class=\"text-sm font-medium\">{{ data.title }}</div>\n <div class=\"text-xs text-muted\">{{ data.timestamp }}</div>\n </template>\n </Column>\n <Column field=\"amount\" :header=\"$t('g.amount')\">\n <template #body=\"{ data }\">\n <div\n :class=\"[\n 'text-center text-base font-medium',\n data.isPositive ? 'text-sky-500' : 'text-red-400'\n ]\"\n >\n {{ data.isPositive ? '+' : '-' }}${{\n formatMetronomeCurrency(data.amount, 'usd')\n }}\n </div>\n </template>\n </Column>\n </DataTable>\n </div>\n </template>\n\n <Divider />\n\n <UsageLogsTable ref=\"usageLogsTableRef\" />\n\n <div class=\"flex flex-row gap-2\">\n <Button variant=\"muted-textonly\" @click=\"handleFaqClick\">\n <i class=\"pi pi-question-circle\" />\n {{ $t('credits.faqs') }}\n </Button>\n <Button variant=\"muted-textonly\" @click=\"handleOpenPartnerNodesInfo\">\n <i class=\"pi pi-question-circle\" />\n {{ $t('subscription.partnerNodesCredits') }}\n </Button>\n <Button variant=\"muted-textonly\" @click=\"handleMessageSupport\">\n <i class=\"pi pi-comments\" />\n {{ $t('credits.messageSupport') }}\n </Button>\n </div>\n </div>\n </TabPanel>\n</template>\n\n<script setup lang=\"ts\">\nimport Column from 'primevue/column'\nimport DataTable from 'primevue/datatable'\nimport Divider from 'primevue/divider'\nimport Skeleton from 'primevue/skeleton'\nimport TabPanel from 'primevue/tabpanel'\nimport { computed, ref, watch } from 'vue'\n\nimport UserCredit from '@/components/common/UserCredit.vue'\nimport UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useDialogService } from '@/services/dialogService'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'\nimport { formatMetronomeCurrency } from '@/utils/formatUtil'\n\ninterface CreditHistoryItemData {\n title: string\n timestamp: string\n amount: number\n isPositive: boolean\n}\n\nconst { buildDocsUrl, docsPaths } = useExternalLink()\nconst dialogService = useDialogService()\nconst authStore = useFirebaseAuthStore()\nconst authActions = useFirebaseAuthActions()\nconst commandStore = useCommandStore()\nconst telemetry = useTelemetry()\nconst { isActiveSubscription } = useSubscription()\nconst loading = computed(() => authStore.loading)\nconst balanceLoading = computed(() => authStore.isFetchingBalance)\n\nconst usageLogsTableRef = ref<InstanceType<typeof UsageLogsTable> | null>(null)\n\nconst formattedLastUpdateTime = computed(() =>\n authStore.lastBalanceUpdateTime\n ? authStore.lastBalanceUpdateTime.toLocaleString()\n : ''\n)\n\nwatch(\n () => authStore.lastBalanceUpdateTime,\n (newTime, oldTime) => {\n if (newTime && newTime !== oldTime && usageLogsTableRef.value) {\n usageLogsTableRef.value.refresh()\n }\n }\n)\n\nconst handlePurchaseCreditsClick = () => {\n // Track purchase credits entry from Settings > Credits panel\n useTelemetry()?.trackAddApiCreditButtonClicked()\n dialogService.showTopUpCreditsDialog()\n}\n\nconst handleCreditsHistoryClick = async () => {\n await authActions.accessBillingPortal()\n}\n\nconst handleMessageSupport = async () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'help_feedback',\n is_external: true,\n source: 'credits_panel'\n })\n await commandStore.execute('Comfy.ContactSupport')\n}\n\nconst handleFaqClick = () => {\n window.open(\n buildDocsUrl('/tutorials/api-nodes/faq', { includeLocale: true }),\n '_blank'\n )\n}\n\nconst handleOpenPartnerNodesInfo = () => {\n window.open(\n buildDocsUrl(docsPaths.partnerNodesPricing, { includeLocale: true }),\n '_blank'\n )\n}\n\nconst creditHistory = ref<CreditHistoryItemData[]>([])\n</script>\n"],"names":["EventType","customerApiClient","axios","getComfyApiBaseUrl","useCustomerEventsService","__name","isLoading","ref","error","d","useI18n","watch","url","handleRequestError","err","context","routeSpecificErrors","isAbortError","message","axiosError","status","executeRequest","requestCall","options","errorContext","formatEventType","eventType","formatDate","dateString","date","formatJsonKey","key","word","formatJsonValue","value","getEventSeverity","hasAdditionalInfo","event","amount","api_name","model","otherParams","getTooltipContent","params","formattedKey","formattedValue","formatAmount","amountMicros","getMyEvents","page","limit","authHeaders","useFirebaseAuthStore","events","loading","customerEventService","pagination","dataTableFirst","computed","tooltipContentMap","map","loadEvents","response","useTelemetry","onPageChange","__expose","buildDocsUrl","docsPaths","useExternalLink","dialogService","useDialogService","authStore","authActions","useFirebaseAuthActions","commandStore","useCommandStore","isActiveSubscription","useSubscription","balanceLoading","usageLogsTableRef","formattedLastUpdateTime","newTime","oldTime","handlePurchaseCreditsClick","handleCreditsHistoryClick","handleMessageSupport","handleFaqClick","handleOpenPartnerNodesInfo","creditHistory"],"mappings":"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"}
1
+ {"version":3,"file":"LegacyCreditsPanel-BujZmWgC.js","sources":["../../src/services/customerEventsService.ts","../../src/components/dialog/content/setting/UsageLogsTable.vue","../../src/components/dialog/content/setting/LegacyCreditsPanel.vue"],"sourcesContent":["import type { AxiosError, AxiosResponse } from 'axios'\nimport axios from 'axios'\nimport { ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { getComfyApiBaseUrl } from '@/config/comfyApi'\nimport { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'\nimport type { components, operations } from '@/types/comfyRegistryTypes'\nimport { isAbortError } from '@/utils/typeGuardUtil'\n\nexport enum EventType {\n CREDIT_ADDED = 'credit_added',\n ACCOUNT_CREATED = 'account_created',\n API_USAGE_STARTED = 'api_usage_started',\n API_USAGE_COMPLETED = 'api_usage_completed'\n}\n\ntype CustomerEventsResponse =\n operations['GetCustomerEvents']['responses']['200']['content']['application/json']\n\ntype CustomerEventsResponseQuery =\n operations['GetCustomerEvents']['parameters']['query']\n\nexport type AuditLog = components['schemas']['AuditLog']\n\nconst customerApiClient = axios.create({\n baseURL: getComfyApiBaseUrl(),\n headers: {\n 'Content-Type': 'application/json'\n }\n})\n\nexport const useCustomerEventsService = () => {\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n const { d } = useI18n()\n\n watch(\n () => getComfyApiBaseUrl(),\n (url) => {\n customerApiClient.defaults.baseURL = url\n }\n )\n\n const handleRequestError = (\n err: unknown,\n context: string,\n routeSpecificErrors?: Record<number, string>\n ) => {\n // Don't treat cancellation as an error\n if (isAbortError(err)) return\n\n let message: string\n if (!axios.isAxiosError(err)) {\n message = `${context} failed: ${err instanceof Error ? err.message : String(err)}`\n } else {\n const axiosError = err as AxiosError<{ message: string }>\n const status = axiosError.response?.status\n if (status && routeSpecificErrors?.[status]) {\n message = routeSpecificErrors[status]\n } else {\n message =\n axiosError.response?.data?.message ??\n `${context} failed with status ${status}`\n }\n }\n\n error.value = message\n }\n\n const executeRequest = async <T>(\n requestCall: () => Promise<AxiosResponse<T>>,\n options: {\n errorContext: string\n routeSpecificErrors?: Record<number, string>\n }\n ): Promise<T | null> => {\n const { errorContext, routeSpecificErrors } = options\n\n isLoading.value = true\n error.value = null\n\n try {\n const response = await requestCall()\n return response.data\n } catch (err) {\n handleRequestError(err, errorContext, routeSpecificErrors)\n return null\n } finally {\n isLoading.value = false\n }\n }\n\n function formatEventType(eventType: string) {\n switch (eventType) {\n case 'credit_added':\n return 'Credits Added'\n case 'account_created':\n return 'Account Created'\n case 'api_usage_completed':\n return 'API Usage'\n default:\n return eventType\n }\n }\n\n function formatDate(dateString: string): string {\n const date = new Date(dateString)\n\n return d(date, {\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n })\n }\n\n function formatJsonKey(key: string) {\n return key\n .split('_')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n }\n\n function formatJsonValue(value: any) {\n if (typeof value === 'number') {\n // Format numbers with commas and decimals if needed\n return value.toLocaleString()\n }\n if (typeof value === 'string' && value.match(/^\\d{4}-\\d{2}-\\d{2}/)) {\n // Format dates nicely\n return new Date(value).toLocaleString()\n }\n return value\n }\n\n function getEventSeverity(eventType: string) {\n switch (eventType) {\n case 'credit_added':\n return 'success'\n case 'account_created':\n return 'info'\n case 'api_usage_completed':\n return 'warning'\n default:\n return 'info'\n }\n }\n\n function hasAdditionalInfo(event: AuditLog) {\n const { amount, api_name, model, ...otherParams } = event.params || {}\n return Object.keys(otherParams).length > 0\n }\n\n function getTooltipContent(event: AuditLog) {\n const { ...params } = event.params || {}\n\n return Object.entries(params)\n .map(([key, value]) => {\n const formattedKey = formatJsonKey(key)\n const formattedValue = formatJsonValue(value)\n return `<strong>${formattedKey}:</strong> ${formattedValue}`\n })\n .join('<br>')\n }\n\n function formatAmount(amountMicros?: number) {\n if (!amountMicros) return '0.00'\n return (amountMicros / 100).toFixed(2)\n }\n\n async function getMyEvents({\n page = 1,\n limit = 10\n }: CustomerEventsResponseQuery = {}): Promise<CustomerEventsResponse | null> {\n const errorContext = 'Fetching customer events'\n const routeSpecificErrors = {\n 400: 'Invalid input, object invalid',\n 404: 'Not found'\n }\n\n // Get auth headers\n const authHeaders = await useFirebaseAuthStore().getAuthHeader()\n if (!authHeaders) {\n error.value = 'Authentication header is missing'\n return null\n }\n\n const result = await executeRequest<CustomerEventsResponse>(\n () =>\n customerApiClient.get('/customers/events', {\n params: { page, limit },\n headers: authHeaders\n }),\n { errorContext, routeSpecificErrors }\n )\n\n return result\n }\n\n return {\n // State\n isLoading,\n error,\n\n // Methods\n getMyEvents,\n formatEventType,\n getEventSeverity,\n formatAmount,\n hasAdditionalInfo,\n formatDate,\n formatJsonKey,\n formatJsonValue,\n getTooltipContent\n }\n}\n","<template>\n <div>\n <div v-if=\"loading\" class=\"flex items-center justify-center p-8\">\n <ProgressSpinner />\n </div>\n <div v-else-if=\"error\" class=\"p-4\">\n <Message severity=\"error\" :closable=\"false\">{{ error }}</Message>\n </div>\n <DataTable\n v-else\n :value=\"events\"\n :paginator=\"true\"\n :rows=\"pagination.limit\"\n :total-records=\"pagination.total\"\n :first=\"dataTableFirst\"\n :lazy=\"true\"\n class=\"p-datatable-sm custom-datatable\"\n @page=\"onPageChange\"\n >\n <Column field=\"event_type\" :header=\"$t('credits.eventType')\">\n <template #body=\"{ data }\">\n <Badge\n :value=\"customerEventService.formatEventType(data.event_type)\"\n :severity=\"customerEventService.getEventSeverity(data.event_type)\"\n />\n </template>\n </Column>\n <Column field=\"details\" :header=\"$t('credits.details')\">\n <template #body=\"{ data }\">\n <div class=\"event-details\">\n <!-- Credits Added -->\n <template v-if=\"data.event_type === EventType.CREDIT_ADDED\">\n <div class=\"font-semibold text-green-500\">\n {{ $t('credits.added') }} ${{\n customerEventService.formatAmount(data.params?.amount)\n }}\n </div>\n </template>\n\n <!-- Account Created -->\n <template v-else-if=\"data.event_type === EventType.ACCOUNT_CREATED\">\n <div>{{ $t('credits.accountInitialized') }}</div>\n </template>\n\n <!-- API Usage -->\n <template\n v-else-if=\"data.event_type === EventType.API_USAGE_COMPLETED\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"font-semibold\">\n {{ data.params?.api_name || 'API' }}\n </div>\n <div class=\"text-sm text-smoke-400\">\n {{ $t('credits.model') }}: {{ data.params?.model || '-' }}\n </div>\n </div>\n </template>\n </div>\n </template>\n </Column>\n <Column field=\"createdAt\" :header=\"$t('credits.time')\">\n <template #body=\"{ data }\">\n {{ customerEventService.formatDate(data.createdAt) }}\n </template>\n </Column>\n <Column field=\"params\" :header=\"$t('credits.additionalInfo')\">\n <template #body=\"{ data }\">\n <Button\n v-if=\"customerEventService.hasAdditionalInfo(data)\"\n v-tooltip.top=\"{\n escape: false,\n value: tooltipContentMap.get(data.event_id) || '',\n pt: {\n text: {\n style: {\n width: 'max-content !important'\n }\n }\n }\n }\"\n variant=\"textonly\"\n size=\"icon-sm\"\n :aria-label=\"$t('credits.additionalInfo')\"\n >\n <i class=\"pi pi-info-circle\" />\n </Button>\n </template>\n </Column>\n </DataTable>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport Badge from 'primevue/badge'\nimport Column from 'primevue/column'\nimport DataTable from 'primevue/datatable'\nimport Message from 'primevue/message'\nimport ProgressSpinner from 'primevue/progressspinner'\nimport { computed, ref } from 'vue'\n\nimport Button from '@/components/ui/button/Button.vue'\nimport { useTelemetry } from '@/platform/telemetry'\nimport type { AuditLog } from '@/services/customerEventsService'\nimport {\n EventType,\n useCustomerEventsService\n} from '@/services/customerEventsService'\n\nconst events = ref<AuditLog[]>([])\nconst loading = ref(true)\nconst error = ref<string | null>(null)\n\nconst customerEventService = useCustomerEventsService()\n\nconst pagination = ref({\n page: 1,\n limit: 7,\n total: 0,\n totalPages: 0\n})\n\nconst dataTableFirst = computed(\n () => (pagination.value.page - 1) * pagination.value.limit\n)\n\nconst tooltipContentMap = computed(() => {\n const map = new Map<string, string>()\n events.value.forEach((event) => {\n if (customerEventService.hasAdditionalInfo(event) && event.event_id) {\n map.set(event.event_id, customerEventService.getTooltipContent(event))\n }\n })\n return map\n})\n\nconst loadEvents = async () => {\n loading.value = true\n error.value = null\n\n try {\n const response = await customerEventService.getMyEvents({\n page: pagination.value.page,\n limit: pagination.value.limit\n })\n\n if (response) {\n if (response.events) {\n events.value = response.events\n }\n\n if (response.page) {\n pagination.value.page = response.page\n }\n\n if (response.limit) {\n pagination.value.limit = response.limit\n }\n\n if (response.total) {\n pagination.value.total = response.total\n }\n\n if (response.totalPages) {\n pagination.value.totalPages = response.totalPages\n }\n\n // Check if a pending top-up has completed\n useTelemetry()?.checkForCompletedTopup(response.events)\n } else {\n error.value = customerEventService.error.value || 'Failed to load events'\n }\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'Unknown error'\n console.error('Error loading events:', err)\n } finally {\n loading.value = false\n }\n}\n\nconst onPageChange = (event: { page: number }) => {\n pagination.value.page = event.page + 1\n loadEvents().catch((error) => {\n console.error('Error loading events:', error)\n })\n}\n\nconst refresh = async () => {\n pagination.value.page = 1\n await loadEvents()\n}\n\ndefineExpose({\n refresh\n})\n</script>\n","<template>\n <TabPanel value=\"Credits\" class=\"credits-container h-full\">\n <!-- Legacy Design -->\n <div class=\"flex h-full flex-col\">\n <h2 class=\"mb-2 text-2xl font-bold\">\n {{ $t('credits.credits') }}\n </h2>\n\n <Divider />\n\n <div class=\"flex flex-col gap-2\">\n <h3 class=\"text-sm font-medium text-muted\">\n {{ $t('credits.yourCreditBalance') }}\n </h3>\n <div class=\"flex items-center justify-between\">\n <UserCredit text-class=\"text-3xl font-bold\" />\n <Skeleton v-if=\"loading\" width=\"2rem\" height=\"2rem\" />\n <Button\n v-else-if=\"isActiveSubscription\"\n :loading=\"loading\"\n @click=\"handlePurchaseCreditsClick\"\n >\n {{ $t('credits.purchaseCredits') }}\n </Button>\n </div>\n <div class=\"flex flex-row items-center\">\n <Skeleton\n v-if=\"balanceLoading\"\n width=\"12rem\"\n height=\"1rem\"\n class=\"text-xs\"\n />\n <div v-else-if=\"formattedLastUpdateTime\" class=\"text-xs text-muted\">\n {{ $t('credits.lastUpdated') }}: {{ formattedLastUpdateTime }}\n </div>\n <Button\n variant=\"muted-textonly\"\n size=\"icon-sm\"\n :aria-label=\"$t('g.refresh')\"\n @click=\"() => authActions.fetchBalance()\"\n >\n <i class=\"pi pi-refresh\" />\n </Button>\n </div>\n </div>\n\n <div class=\"flex items-center justify-between\">\n <h3>{{ $t('credits.activity') }}</h3>\n <Button\n variant=\"muted-textonly\"\n :loading=\"loading\"\n @click=\"handleCreditsHistoryClick\"\n >\n <i class=\"pi pi-arrow-up-right\" />\n {{ $t('credits.invoiceHistory') }}\n </Button>\n </div>\n\n <template v-if=\"creditHistory.length > 0\">\n <div class=\"grow\">\n <DataTable :value=\"creditHistory\" :show-headers=\"false\">\n <Column field=\"title\" :header=\"$t('g.name')\">\n <template #body=\"{ data }\">\n <div class=\"text-sm font-medium\">{{ data.title }}</div>\n <div class=\"text-xs text-muted\">{{ data.timestamp }}</div>\n </template>\n </Column>\n <Column field=\"amount\" :header=\"$t('g.amount')\">\n <template #body=\"{ data }\">\n <div\n :class=\"[\n 'text-center text-base font-medium',\n data.isPositive ? 'text-sky-500' : 'text-red-400'\n ]\"\n >\n {{ data.isPositive ? '+' : '-' }}${{\n formatMetronomeCurrency(data.amount, 'usd')\n }}\n </div>\n </template>\n </Column>\n </DataTable>\n </div>\n </template>\n\n <Divider />\n\n <UsageLogsTable ref=\"usageLogsTableRef\" />\n\n <div class=\"flex flex-row gap-2\">\n <Button variant=\"muted-textonly\" @click=\"handleFaqClick\">\n <i class=\"pi pi-question-circle\" />\n {{ $t('credits.faqs') }}\n </Button>\n <Button variant=\"muted-textonly\" @click=\"handleOpenPartnerNodesInfo\">\n <i class=\"pi pi-question-circle\" />\n {{ $t('subscription.partnerNodesCredits') }}\n </Button>\n <Button variant=\"muted-textonly\" @click=\"handleMessageSupport\">\n <i class=\"pi pi-comments\" />\n {{ $t('credits.messageSupport') }}\n </Button>\n </div>\n </div>\n </TabPanel>\n</template>\n\n<script setup lang=\"ts\">\nimport Column from 'primevue/column'\nimport DataTable from 'primevue/datatable'\nimport Divider from 'primevue/divider'\nimport Skeleton from 'primevue/skeleton'\nimport TabPanel from 'primevue/tabpanel'\nimport { computed, ref, watch } from 'vue'\n\nimport UserCredit from '@/components/common/UserCredit.vue'\nimport UsageLogsTable from '@/components/dialog/content/setting/UsageLogsTable.vue'\nimport Button from '@/components/ui/button/Button.vue'\nimport { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'\nimport { useExternalLink } from '@/composables/useExternalLink'\nimport { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'\nimport { useTelemetry } from '@/platform/telemetry'\nimport { useDialogService } from '@/services/dialogService'\nimport { useCommandStore } from '@/stores/commandStore'\nimport { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'\nimport { formatMetronomeCurrency } from '@/utils/formatUtil'\n\ninterface CreditHistoryItemData {\n title: string\n timestamp: string\n amount: number\n isPositive: boolean\n}\n\nconst { buildDocsUrl, docsPaths } = useExternalLink()\nconst dialogService = useDialogService()\nconst authStore = useFirebaseAuthStore()\nconst authActions = useFirebaseAuthActions()\nconst commandStore = useCommandStore()\nconst telemetry = useTelemetry()\nconst { isActiveSubscription } = useSubscription()\nconst loading = computed(() => authStore.loading)\nconst balanceLoading = computed(() => authStore.isFetchingBalance)\n\nconst usageLogsTableRef = ref<InstanceType<typeof UsageLogsTable> | null>(null)\n\nconst formattedLastUpdateTime = computed(() =>\n authStore.lastBalanceUpdateTime\n ? authStore.lastBalanceUpdateTime.toLocaleString()\n : ''\n)\n\nwatch(\n () => authStore.lastBalanceUpdateTime,\n (newTime, oldTime) => {\n if (newTime && newTime !== oldTime && usageLogsTableRef.value) {\n usageLogsTableRef.value.refresh()\n }\n }\n)\n\nconst handlePurchaseCreditsClick = () => {\n // Track purchase credits entry from Settings > Credits panel\n useTelemetry()?.trackAddApiCreditButtonClicked()\n dialogService.showTopUpCreditsDialog()\n}\n\nconst handleCreditsHistoryClick = async () => {\n await authActions.accessBillingPortal()\n}\n\nconst handleMessageSupport = async () => {\n telemetry?.trackHelpResourceClicked({\n resource_type: 'help_feedback',\n is_external: true,\n source: 'credits_panel'\n })\n await commandStore.execute('Comfy.ContactSupport')\n}\n\nconst handleFaqClick = () => {\n window.open(\n buildDocsUrl('/tutorials/api-nodes/faq', { includeLocale: true }),\n '_blank'\n )\n}\n\nconst handleOpenPartnerNodesInfo = () => {\n window.open(\n buildDocsUrl(docsPaths.partnerNodesPricing, { includeLocale: true }),\n '_blank'\n )\n}\n\nconst creditHistory = ref<CreditHistoryItemData[]>([])\n</script>\n"],"names":["EventType","customerApiClient","axios","getComfyApiBaseUrl","useCustomerEventsService","__name","isLoading","ref","error","d","useI18n","watch","url","handleRequestError","err","context","routeSpecificErrors","isAbortError","message","axiosError","status","executeRequest","requestCall","options","errorContext","formatEventType","eventType","formatDate","dateString","date","formatJsonKey","key","word","formatJsonValue","value","getEventSeverity","hasAdditionalInfo","event","amount","api_name","model","otherParams","getTooltipContent","params","formattedKey","formattedValue","formatAmount","amountMicros","getMyEvents","page","limit","authHeaders","useFirebaseAuthStore","events","loading","customerEventService","pagination","dataTableFirst","computed","tooltipContentMap","map","loadEvents","response","useTelemetry","onPageChange","__expose","buildDocsUrl","docsPaths","useExternalLink","dialogService","useDialogService","authStore","authActions","useFirebaseAuthActions","commandStore","useCommandStore","isActiveSubscription","useSubscription","balanceLoading","usageLogsTableRef","formattedLastUpdateTime","newTime","oldTime","handlePurchaseCreditsClick","handleCreditsHistoryClick","handleMessageSupport","handleFaqClick","handleOpenPartnerNodesInfo","creditHistory"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{_ as o}from"./Load3D.vue_vue_type_script_setup_true_lang-BJ0IqFdp.js";import"./vendor-other-BxP-0xn6.js";import"./vendor-primevue-rR0TB_Js.js";import"./index-FoJ8Eu4a.js";import"./vendor-vue-BEnTqVKr.js";import"./vendor-xterm-CWYFmgbN.js";import"./vendor-three-BsnVSA6y.js";import"./vendor-tiptap-4vvRsqpM.js";export{o as default};
2
- //# sourceMappingURL=Load3D-Cu5zG2Cw.js.map
1
+ import{_ as o}from"./Load3D.vue_vue_type_script_setup_true_lang-_XswHXHt.js";import"./vendor-other-BxP-0xn6.js";import"./vendor-primevue-rR0TB_Js.js";import"./index-BODInyrK.js";import"./vendor-vue-BEnTqVKr.js";import"./vendor-xterm-CWYFmgbN.js";import"./vendor-three-BsnVSA6y.js";import"./vendor-tiptap-4vvRsqpM.js";export{o as default};
2
+ //# sourceMappingURL=Load3D-DWafZUud.js.map