mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
manager: design improved related to infopanel
This commit is contained in:
@@ -283,6 +283,9 @@
|
||||
},
|
||||
"manager": {
|
||||
"title": "Nodes Manager",
|
||||
"basicInfo": "Basic Info",
|
||||
"actions": "Actions",
|
||||
"selected": "Selected",
|
||||
"legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.",
|
||||
"legacyManagerUI": "Use Legacy UI",
|
||||
"legacyManagerUIDescription": "To use the legacy Manager UI, start ComfyUI with --enable-manager-legacy-ui",
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
}
|
||||
},
|
||||
overlay: {
|
||||
class: 'bg-comfy-input rounded-lg mt-1 shadow-lg border border-border-default'
|
||||
class:
|
||||
'bg-comfy-input rounded-lg mt-1 shadow-lg border border-border-default'
|
||||
},
|
||||
list: { class: 'p-1' },
|
||||
option: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Button
|
||||
v-tooltip.top="$t('manager.tryUpdateTooltip')"
|
||||
variant="inverted"
|
||||
variant="primary"
|
||||
:size
|
||||
:disabled="isUpdating"
|
||||
@click="tryUpdate"
|
||||
@@ -11,6 +11,7 @@
|
||||
duration="1s"
|
||||
:size="size === 'sm' ? 12 : 16"
|
||||
/>
|
||||
<i v-else class="icon-[lucide--refresh-cw]" />
|
||||
<span>{{ isUpdating ? t('g.updating') : t('manager.tryUpdate') }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Button
|
||||
variant="destructive"
|
||||
:size
|
||||
@click="uninstallItems"
|
||||
>
|
||||
<Button variant="destructive" :size @click="uninstallItems">
|
||||
<i class="icon-[lucide--trash-2]" />
|
||||
{{
|
||||
nodePacks.length > 1
|
||||
? t('manager.uninstallSelected')
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
>
|
||||
<DotSpinner v-if="isUpdating" duration="1s" :size="12" />
|
||||
<i v-else class="icon-[lucide--refresh-cw]" />
|
||||
<span>{{ $t('manager.updateAll') }}</span>
|
||||
<span>{{
|
||||
nodePacks.length > 1 ? $t('manager.updateAll') : $t('manager.update')
|
||||
}}</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,55 +1,110 @@
|
||||
<template>
|
||||
<template v-if="nodePack">
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<div class="w-full px-6 pt-6">
|
||||
<InfoPanelHeader
|
||||
:node-packs="[nodePack]"
|
||||
:has-conflict="hasCompatibilityIssues"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
ref="scrollContainer"
|
||||
class="scrollbar-hide flex-1 flex flex-col p-6 pt-2 text-sm"
|
||||
>
|
||||
<div class="mb-6">
|
||||
<MetadataRow
|
||||
v-if="!importFailed && isPackInstalled(nodePack.id)"
|
||||
:label="t('manager.filter.enabled')"
|
||||
class="flex"
|
||||
style="align-items: center"
|
||||
>
|
||||
<PackEnableToggle
|
||||
:node-pack="nodePack"
|
||||
:has-conflict="hasCompatibilityIssues"
|
||||
<div
|
||||
ref="scrollContainer"
|
||||
class="flex h-full flex-col overflow-y-auto scrollbar-custom"
|
||||
>
|
||||
<PropertiesAccordionItem v-if="!importFailed" :class="accordionClass">
|
||||
<template #label>
|
||||
<span class="text-xs uppercase font-inter">
|
||||
{{ t('manager.actions') }}
|
||||
</span>
|
||||
</template>
|
||||
<div class="flex flex-col gap-1 px-4">
|
||||
<template v-if="canTryNightlyUpdate">
|
||||
<PackTryUpdateButton :node-pack="nodePack" size="md" />
|
||||
<PackUninstallButton :node-packs="[nodePack]" size="md" />
|
||||
</template>
|
||||
<template v-else-if="isUpdateAvailable">
|
||||
<PackUpdateButton :node-packs="[nodePack]" size="md" />
|
||||
<PackUninstallButton :node-packs="[nodePack]" size="md" />
|
||||
</template>
|
||||
<template v-else-if="isAllInstalled">
|
||||
<PackUninstallButton :node-packs="[nodePack]" size="md" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<PackInstallButton
|
||||
:node-packs="[nodePack]"
|
||||
size="md"
|
||||
:has-conflict="hasCompatibilityIssues || hasConflictInfo"
|
||||
:conflict-info="conflictInfo"
|
||||
/>
|
||||
</MetadataRow>
|
||||
<MetadataRow
|
||||
v-for="item in infoItems"
|
||||
v-show="item.value !== undefined && item.value !== null"
|
||||
:key="item.key"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
<MetadataRow :label="t('g.status')">
|
||||
<PackStatusMessage
|
||||
:status-type="
|
||||
nodePack.status as components['schemas']['NodeVersionStatus']
|
||||
"
|
||||
:has-compatibility-issues="hasCompatibilityIssues"
|
||||
/>
|
||||
</MetadataRow>
|
||||
<MetadataRow :label="t('manager.version')">
|
||||
<PackVersionBadge :node-pack="nodePack" :is-selected="true" />
|
||||
</MetadataRow>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mb-6 flex-1 overflow-hidden">
|
||||
<InfoTabs
|
||||
</PropertiesAccordionItem>
|
||||
|
||||
<PropertiesAccordionItem :class="accordionClass">
|
||||
<template #label>
|
||||
<span class="text-xs uppercase font-inter">
|
||||
{{ t('manager.basicInfo') }}
|
||||
</span>
|
||||
</template>
|
||||
<ModelInfoField :label="t('g.name')">
|
||||
<span class="text-muted-foreground">{{ nodePack.name }}</span>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField
|
||||
v-if="!importFailed && isPackInstalled(nodePack.id)"
|
||||
:label="t('manager.filter.enabled')"
|
||||
>
|
||||
<PackEnableToggle
|
||||
:node-pack="nodePack"
|
||||
:has-compatibility-issues="hasCompatibilityIssues"
|
||||
:conflict-result="conflictResult"
|
||||
:has-conflict="hasCompatibilityIssues"
|
||||
/>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField
|
||||
v-for="item in infoItems"
|
||||
v-show="item.value !== undefined && item.value !== null"
|
||||
:key="item.key"
|
||||
:label="item.label"
|
||||
>
|
||||
<span class="text-muted-foreground">{{ item.value }}</span>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField :label="t('g.status')">
|
||||
<PackStatusMessage
|
||||
:status-type="
|
||||
nodePack.status as components['schemas']['NodeVersionStatus']
|
||||
"
|
||||
:has-compatibility-issues="hasCompatibilityIssues"
|
||||
/>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField :label="t('manager.version')">
|
||||
<PackVersionBadge :node-pack="nodePack" :is-selected="true" />
|
||||
</ModelInfoField>
|
||||
</PropertiesAccordionItem>
|
||||
|
||||
<PropertiesAccordionItem :class="accordionClass">
|
||||
<template #label>
|
||||
<span class="text-xs uppercase font-inter">
|
||||
{{ t('g.description') }}
|
||||
</span>
|
||||
</template>
|
||||
<DescriptionTabPanel :node-pack="nodePack" />
|
||||
</PropertiesAccordionItem>
|
||||
|
||||
<PropertiesAccordionItem
|
||||
v-if="hasCompatibilityIssues"
|
||||
:class="accordionClass"
|
||||
>
|
||||
<template #label>
|
||||
<span class="text-xs uppercase font-inter">
|
||||
⚠️ {{ importFailed ? t('g.error') : t('g.warning') }}
|
||||
</span>
|
||||
</template>
|
||||
<div class="px-4 py-2">
|
||||
<WarningTabPanel :conflict-result="conflictResult" />
|
||||
</div>
|
||||
</div>
|
||||
</PropertiesAccordionItem>
|
||||
|
||||
<PropertiesAccordionItem :class="accordionClass">
|
||||
<template #label>
|
||||
<span class="text-xs uppercase font-inter">
|
||||
{{ t('g.nodes') }}
|
||||
</span>
|
||||
</template>
|
||||
<div class="px-4 py-2">
|
||||
<NodesTabPanel :node-pack="nodePack" :node-names="nodeNames" />
|
||||
</div>
|
||||
</PropertiesAccordionItem>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -60,23 +115,34 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useScroll, whenever } from '@vueuse/core'
|
||||
import { computed, provide, ref } from 'vue'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { computed, provide, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import PropertiesAccordionItem from '@/components/rightSidePanel/layout/PropertiesAccordionItem.vue'
|
||||
import ModelInfoField from '@/platform/assets/components/modelInfo/ModelInfoField.vue'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
||||
import PackVersionBadge from '@/workbench/extensions/manager/components/manager/PackVersionBadge.vue'
|
||||
import PackEnableToggle from '@/workbench/extensions/manager/components/manager/button/PackEnableToggle.vue'
|
||||
import InfoPanelHeader from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelHeader.vue'
|
||||
import InfoTabs from '@/workbench/extensions/manager/components/manager/infoPanel/InfoTabs.vue'
|
||||
import MetadataRow from '@/workbench/extensions/manager/components/manager/infoPanel/MetadataRow.vue'
|
||||
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
|
||||
import PackTryUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue'
|
||||
import PackUninstallButton from '@/workbench/extensions/manager/components/manager/button/PackUninstallButton.vue'
|
||||
import PackUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackUpdateButton.vue'
|
||||
import DescriptionTabPanel from '@/workbench/extensions/manager/components/manager/infoPanel/tabs/DescriptionTabPanel.vue'
|
||||
import NodesTabPanel from '@/workbench/extensions/manager/components/manager/infoPanel/tabs/NodesTabPanel.vue'
|
||||
import WarningTabPanel from '@/workbench/extensions/manager/components/manager/infoPanel/tabs/WarningTabPanel.vue'
|
||||
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
|
||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||
import { useImportFailedDetection } from '@/workbench/extensions/manager/composables/useImportFailedDetection'
|
||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||
import { useConflictDetectionStore } from '@/workbench/extensions/manager/stores/conflictDetectionStore'
|
||||
import { IsInstallingKey } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||
import type {
|
||||
ConflictDetail,
|
||||
ConflictDetectionResult
|
||||
} from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||
import { ImportFailedKey } from '@/workbench/extensions/manager/types/importFailedTypes'
|
||||
|
||||
interface InfoItem {
|
||||
@@ -91,7 +157,12 @@ const { nodePack } = defineProps<{
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const { isPackInstalled } = useComfyManagerStore()
|
||||
const accordionClass = cn(
|
||||
'bg-modal-panel-background border-t border-border-default'
|
||||
)
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
const { isPackInstalled } = managerStore
|
||||
const isInstalled = computed(() => isPackInstalled(nodePack.id))
|
||||
const isInstalling = ref(false)
|
||||
provide(IsInstallingKey, isInstalling)
|
||||
@@ -99,7 +170,27 @@ whenever(isInstalled, () => {
|
||||
isInstalling.value = false
|
||||
})
|
||||
|
||||
const { canTryNightlyUpdate, isUpdateAvailable } = usePackUpdateStatus(
|
||||
() => nodePack
|
||||
)
|
||||
|
||||
const isAllInstalled = ref(false)
|
||||
watch(
|
||||
() => managerStore.installedPacks,
|
||||
() => {
|
||||
isAllInstalled.value = isPackInstalled(nodePack.id)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const { checkNodeCompatibility } = useConflictDetection()
|
||||
|
||||
const conflictInfo = computed<ConflictDetail[]>(() => {
|
||||
const compatibility = checkNodeCompatibility(nodePack)
|
||||
return compatibility.conflicts ?? []
|
||||
})
|
||||
|
||||
const hasConflictInfo = computed(() => conflictInfo.value.length > 0)
|
||||
const { getConflictsForPackageByID } = useConflictDetectionStore()
|
||||
|
||||
const { t, d, n } = useI18n()
|
||||
@@ -140,6 +231,12 @@ provide(ImportFailedKey, {
|
||||
showImportFailedDialog
|
||||
})
|
||||
|
||||
const nodeNames = computed(() => {
|
||||
// @ts-expect-error comfy_nodes is an Algolia-specific field
|
||||
const { comfy_nodes } = nodePack
|
||||
return comfy_nodes ?? []
|
||||
})
|
||||
|
||||
const infoItems = computed<InfoItem[]>(() => [
|
||||
{
|
||||
key: 'publisher',
|
||||
@@ -162,20 +259,11 @@ const infoItems = computed<InfoItem[]>(() => [
|
||||
}
|
||||
])
|
||||
|
||||
const { y } = useScroll(scrollContainer, {
|
||||
eventListenerOptions: {
|
||||
passive: true
|
||||
}
|
||||
})
|
||||
const onNodePackChange = () => {
|
||||
y.value = 0
|
||||
}
|
||||
|
||||
whenever(
|
||||
() => nodePack.id,
|
||||
(nodePackId, oldNodePackId) => {
|
||||
if (nodePackId !== oldNodePackId) {
|
||||
onNodePackChange()
|
||||
if (nodePackId !== oldNodePackId && scrollContainer.value) {
|
||||
scrollContainer.value.scrollTop = 0
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div v-if="nodePacks?.length" class="flex flex-col items-center">
|
||||
<PackIcon :node-pack="nodePacks[0]" width="204" height="106" />
|
||||
<p class="text-center text-base font-bold">{{ nodePacks[0].name }}</p>
|
||||
<div v-if="!importFailed" class="flex justify-center gap-2">
|
||||
<template v-if="canTryNightlyUpdate">
|
||||
@@ -8,7 +7,11 @@
|
||||
<PackUninstallButton :node-packs="nodePacks" size="md" />
|
||||
</template>
|
||||
<template v-else-if="isAllInstalled">
|
||||
<PackUninstallButton v-bind="$attrs" size="md" :node-packs="nodePacks" />
|
||||
<PackUninstallButton
|
||||
v-bind="$attrs"
|
||||
size="md"
|
||||
:node-packs="nodePacks"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<PackInstallButton
|
||||
@@ -37,7 +40,6 @@ import type { components } from '@/types/comfyRegistryTypes'
|
||||
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
|
||||
import PackTryUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue'
|
||||
import PackUninstallButton from '@/workbench/extensions/manager/components/manager/button/PackUninstallButton.vue'
|
||||
import PackIcon from '@/workbench/extensions/manager/components/manager/packIcon/PackIcon.vue'
|
||||
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
|
||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||
|
||||
@@ -1,64 +1,67 @@
|
||||
<template>
|
||||
<div v-if="nodePacks?.length" class="flex h-full flex-col">
|
||||
<div class="flex-1 overflow-auto p-6">
|
||||
<InfoPanelHeader :node-packs>
|
||||
<template #thumbnail>
|
||||
<PackIconStacked :node-packs="nodePacks" />
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="mt-5">
|
||||
<span class="mr-2 inline-block text-base text-blue-500">{{
|
||||
nodePacks.length
|
||||
}}</span>
|
||||
<span class="text-base">{{ $t('manager.packsSelected') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #install-button>
|
||||
<!-- Mixed: Don't show any button -->
|
||||
<div v-if="isMixed" class="text-sm text-neutral-500">
|
||||
{{ $t('manager.mixedSelectionMessage') }}
|
||||
</div>
|
||||
<!-- All installed: Show update (if nightly) and uninstall buttons -->
|
||||
<div
|
||||
v-else-if="isAllInstalled"
|
||||
class="flex w-full justify-center gap-2"
|
||||
>
|
||||
<Button
|
||||
v-if="hasNightlyPacks"
|
||||
v-tooltip.top="$t('manager.tryUpdateTooltip')"
|
||||
variant="textonly"
|
||||
size="md"
|
||||
:disabled="isUpdatingSelected"
|
||||
@click="updateSelectedNightlyPacks"
|
||||
>
|
||||
<DotSpinner v-if="isUpdatingSelected" duration="1s" :size="16" />
|
||||
<span>{{ updateSelectedLabel }}</span>
|
||||
</Button>
|
||||
<PackUninstallButton size="md" :node-packs="installedPacks" />
|
||||
</div>
|
||||
<!-- None installed: Show install button -->
|
||||
<PackInstallButton
|
||||
v-else-if="isNoneInstalled"
|
||||
<div
|
||||
v-if="nodePacks?.length"
|
||||
class="flex h-full flex-col overflow-y-auto scrollbar-custom"
|
||||
>
|
||||
<PropertiesAccordionItem :class="accordionClass">
|
||||
<template #label>
|
||||
<span class="text-xs uppercase font-inter">
|
||||
{{ t('manager.actions') }}
|
||||
</span>
|
||||
</template>
|
||||
<div class="flex justify-center gap-2 px-4 py-2">
|
||||
<!-- Mixed: Don't show any button -->
|
||||
<div v-if="isMixed" class="text-sm text-neutral-500">
|
||||
{{ $t('manager.mixedSelectionMessage') }}
|
||||
</div>
|
||||
<!-- All installed: Show update (if nightly) and uninstall buttons -->
|
||||
<template v-else-if="isAllInstalled">
|
||||
<Button
|
||||
v-if="hasNightlyPacks"
|
||||
v-tooltip.top="$t('manager.tryUpdateTooltip')"
|
||||
variant="textonly"
|
||||
size="md"
|
||||
:node-packs="notInstalledPacks"
|
||||
:has-conflict="hasConflicts"
|
||||
:conflict-info="conflictInfo"
|
||||
/>
|
||||
:disabled="isUpdatingSelected"
|
||||
@click="updateSelectedNightlyPacks"
|
||||
>
|
||||
<DotSpinner v-if="isUpdatingSelected" duration="1s" :size="16" />
|
||||
<span>{{ updateSelectedLabel }}</span>
|
||||
</Button>
|
||||
<PackUninstallButton size="md" :node-packs="installedPacks" />
|
||||
</template>
|
||||
</InfoPanelHeader>
|
||||
<div class="mb-6">
|
||||
<MetadataRow :label="$t('g.status')">
|
||||
<PackStatusMessage
|
||||
:status-type="overallStatus"
|
||||
:has-compatibility-issues="hasConflicts"
|
||||
/>
|
||||
</MetadataRow>
|
||||
<MetadataRow
|
||||
:label="$t('manager.totalNodes')"
|
||||
:value="totalNodesCount"
|
||||
<!-- None installed: Show install button -->
|
||||
<PackInstallButton
|
||||
v-else-if="isNoneInstalled"
|
||||
size="md"
|
||||
:node-packs="notInstalledPacks"
|
||||
:has-conflict="hasConflicts"
|
||||
:conflict-info="conflictInfo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PropertiesAccordionItem>
|
||||
|
||||
<PropertiesAccordionItem :class="accordionClass">
|
||||
<template #label>
|
||||
<span class="text-xs uppercase font-inter">
|
||||
{{ t('manager.basicInfo') }}
|
||||
</span>
|
||||
</template>
|
||||
<ModelInfoField :label="t('manager.selected')">
|
||||
<span>
|
||||
<span class="font-bold text-blue-500">{{ nodePacks.length }}</span>
|
||||
{{ t('manager.packsSelected') }}
|
||||
</span>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField :label="t('g.status')">
|
||||
<PackStatusMessage
|
||||
:status-type="overallStatus"
|
||||
:has-compatibility-issues="hasConflicts"
|
||||
/>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField :label="t('manager.totalNodes')">
|
||||
<span class="text-muted-foreground">{{ totalNodesCount }}</span>
|
||||
</ModelInfoField>
|
||||
</PropertiesAccordionItem>
|
||||
</div>
|
||||
<div v-else class="mx-8 mt-4 flex-1 overflow-hidden text-sm">
|
||||
{{ $t('manager.infoPanelEmpty') }}
|
||||
@@ -71,15 +74,15 @@ import { computed, onUnmounted, provide, ref, toRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DotSpinner from '@/components/common/DotSpinner.vue'
|
||||
import PropertiesAccordionItem from '@/components/rightSidePanel/layout/PropertiesAccordionItem.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import ModelInfoField from '@/platform/assets/components/modelInfo/ModelInfoField.vue'
|
||||
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
||||
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
|
||||
import PackUninstallButton from '@/workbench/extensions/manager/components/manager/button/PackUninstallButton.vue'
|
||||
import InfoPanelHeader from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelHeader.vue'
|
||||
import MetadataRow from '@/workbench/extensions/manager/components/manager/infoPanel/MetadataRow.vue'
|
||||
import PackIconStacked from '@/workbench/extensions/manager/components/manager/packIcon/PackIconStacked.vue'
|
||||
import { usePacksSelection } from '@/workbench/extensions/manager/composables/nodePack/usePacksSelection'
|
||||
import { usePacksStatus } from '@/workbench/extensions/manager/composables/nodePack/usePacksStatus'
|
||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||
@@ -92,6 +95,11 @@ const { nodePacks } = defineProps<{
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const accordionClass = cn(
|
||||
'bg-modal-panel-background border-t border-border-default'
|
||||
)
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
const nodePacksRef = toRef(() => nodePacks)
|
||||
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
:href="section.text"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<i v-if="isGitHubLink(section.text)" class="pi pi-github text-base" />
|
||||
<i v-if="isGitHubLink(section.text)" class="pi pi-github text-base pr-1 pb-1" />
|
||||
<span class="break-all">{{ section.text }}</span>
|
||||
</a>
|
||||
<MarkdownText v-else :text="section.text" class="text-muted" />
|
||||
|
||||
@@ -1,24 +1,57 @@
|
||||
<template>
|
||||
<div class="overflow-hidden">
|
||||
<InfoTextSection
|
||||
v-if="nodePack?.description"
|
||||
:sections="descriptionSections"
|
||||
/>
|
||||
<p v-else class="text-sm text-muted italic">
|
||||
{{ $t('manager.noDescription') }}
|
||||
</p>
|
||||
<div v-if="nodePack?.latest_version?.dependencies?.length">
|
||||
<p class="mb-1">
|
||||
{{ $t('manager.dependencies') }}
|
||||
</p>
|
||||
<div>
|
||||
<ModelInfoField :label="t('g.description')">
|
||||
<MarkdownText
|
||||
v-if="nodePack.description"
|
||||
:text="nodePack.description"
|
||||
class="text-muted-foreground"
|
||||
/>
|
||||
<span v-else class="text-muted-foreground italic">
|
||||
{{ t('manager.noDescription') }}
|
||||
</span>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField v-if="nodePack.repository" :label="t('manager.repository')">
|
||||
<a
|
||||
:href="nodePack.repository"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1.5 text-muted-foreground no-underline transition-colors hover:text-foreground"
|
||||
>
|
||||
<i
|
||||
v-if="isGitHubLink(nodePack.repository)"
|
||||
class="pi pi-github text-base"
|
||||
/>
|
||||
<span class="break-all">{{ nodePack.repository }}</span>
|
||||
<i class="icon-[lucide--external-link] size-4 shrink-0" />
|
||||
</a>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField v-if="licenseInfo" :label="t('manager.license')">
|
||||
<a
|
||||
v-if="licenseInfo.isUrl"
|
||||
:href="licenseInfo.text"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center gap-1.5 text-muted-foreground no-underline transition-colors hover:text-foreground"
|
||||
>
|
||||
<span class="break-all">{{ licenseInfo.text }}</span>
|
||||
<i class="icon-[lucide--external-link] size-4 shrink-0" />
|
||||
</a>
|
||||
<span v-else class="text-muted-foreground break-all">
|
||||
{{ licenseInfo.text }}
|
||||
</span>
|
||||
</ModelInfoField>
|
||||
<ModelInfoField
|
||||
v-if="nodePack.latest_version?.dependencies?.length"
|
||||
:label="t('manager.dependencies')"
|
||||
>
|
||||
<div
|
||||
v-for="(dep, index) in nodePack.latest_version.dependencies"
|
||||
:key="index"
|
||||
class="break-words text-muted"
|
||||
class="break-words text-muted-foreground"
|
||||
>
|
||||
{{ dep }}
|
||||
</div>
|
||||
</div>
|
||||
</ModelInfoField>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -26,10 +59,10 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ModelInfoField from '@/platform/assets/components/modelInfo/ModelInfoField.vue'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { isValidUrl } from '@/utils/formatUtil'
|
||||
import InfoTextSection from '@/workbench/extensions/manager/components/manager/infoPanel/InfoTextSection.vue'
|
||||
import type { TextSection } from '@/workbench/extensions/manager/components/manager/infoPanel/InfoTextSection.vue'
|
||||
import MarkdownText from '@/workbench/extensions/manager/components/manager/infoPanel/MarkdownText.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -37,6 +70,8 @@ const { nodePack } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
}>()
|
||||
|
||||
const isGitHubLink = (url: string): boolean => url.includes('github.com')
|
||||
|
||||
const isLicenseFile = (filename: string): boolean => {
|
||||
// Match LICENSE, LICENSE.md, LICENSE.txt (case insensitive)
|
||||
const licensePattern = /^license(\.md|\.txt)?$/i
|
||||
@@ -118,33 +153,8 @@ const formatLicense = (
|
||||
}
|
||||
}
|
||||
|
||||
const descriptionSections = computed<TextSection[]>(() => {
|
||||
const sections: TextSection[] = [
|
||||
{
|
||||
title: t('g.description'),
|
||||
text: nodePack.description || t('manager.noDescription')
|
||||
}
|
||||
]
|
||||
|
||||
if (nodePack.repository) {
|
||||
sections.push({
|
||||
title: t('manager.repository'),
|
||||
text: nodePack.repository,
|
||||
isUrl: isValidUrl(nodePack.repository)
|
||||
})
|
||||
}
|
||||
|
||||
if (nodePack.license) {
|
||||
const licenseInfo = formatLicense(nodePack.license)
|
||||
if (licenseInfo && licenseInfo.text) {
|
||||
sections.push({
|
||||
title: t('manager.license'),
|
||||
text: licenseInfo.text,
|
||||
isUrl: licenseInfo.isUrl
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return sections
|
||||
const licenseInfo = computed(() => {
|
||||
if (!nodePack.license) return null
|
||||
return formatLicense(nodePack.license)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -3,15 +3,12 @@
|
||||
<div
|
||||
v-for="(conflict, index) in conflictResult?.conflicts || []"
|
||||
:key="index"
|
||||
class="rounded-md bg-secondary-background/60 px-2 py-1"
|
||||
class="rounded-md bg-secondary-background/60"
|
||||
>
|
||||
<!-- Import failed conflicts show detailed error message -->
|
||||
<template v-if="conflict.type === 'import_failed'">
|
||||
<div
|
||||
v-if="conflict.required_value"
|
||||
class="overflow-x-hidden rounded px-2"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground break-all font-mono">
|
||||
<div v-if="conflict.required_value" class="overflow-x-hidden rounded">
|
||||
<p class="m-0 text-xs text-muted-foreground break-all font-mono">
|
||||
{{ conflict.required_value }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
<div class="flex flex-1 flex-col rounded-lg min-h-0">
|
||||
<div class="h-full w-full py-2 px-3">
|
||||
<div class="flex h-full w-full flex-col gap-y-1">
|
||||
<span class="truncate overflow-hidden text-xs font-bold text-ellipsis">
|
||||
<span
|
||||
class="truncate overflow-hidden text-xs font-bold text-ellipsis"
|
||||
>
|
||||
{{ nodePack.name }}
|
||||
</span>
|
||||
<p
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<i class="pi pi-download text-muted"></i>
|
||||
<span>{{ formattedDownloads }}</span>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
<PackInstallButton
|
||||
v-if="!isInstalled"
|
||||
:node-packs="[nodePack]"
|
||||
|
||||
Reference in New Issue
Block a user