Compare commits
2 Commits
v2.7.0
...
use-comfy-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d44c20469e | ||
|
|
6ef473211f |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -199,4 +199,6 @@ web/
|
|||||||
config/
|
config/
|
||||||
|
|
||||||
# private info
|
# private info
|
||||||
private.key
|
private.key
|
||||||
|
|
||||||
|
.idea/
|
||||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode"
|
||||||
"lokalise.i18n-ally"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -43,10 +43,5 @@
|
|||||||
"editor.quickSuggestions": {
|
"editor.quickSuggestions": {
|
||||||
"strings": "on"
|
"strings": "on"
|
||||||
},
|
},
|
||||||
"css.lint.unknownAtRules": "ignore",
|
"css.lint.unknownAtRules": "ignore"
|
||||||
"i18n-ally.localesPaths": [
|
|
||||||
"src/locales"
|
|
||||||
],
|
|
||||||
"i18n-ally.sourceLanguage": "en",
|
|
||||||
"i18n-ally.keystyle": "nested"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,12 @@ utils.download_web_distribution(version)
|
|||||||
from .py import manager
|
from .py import manager
|
||||||
from .py import download
|
from .py import download
|
||||||
from .py import information
|
from .py import information
|
||||||
from .py import upload
|
|
||||||
|
|
||||||
routes = config.routes
|
routes = config.routes
|
||||||
|
|
||||||
manager.ModelManager().add_routes(routes)
|
manager.ModelManager().add_routes(routes)
|
||||||
download.ModelDownload().add_routes(routes)
|
download.ModelDownload().add_routes(routes)
|
||||||
information.Information().add_routes(routes)
|
information.Information().add_routes(routes)
|
||||||
upload.ModelUploader().add_routes(routes)
|
|
||||||
|
|
||||||
|
|
||||||
WEB_DIRECTORY = "web"
|
WEB_DIRECTORY = "web"
|
||||||
|
|||||||
6401
package-lock.json
generated
Normal file
6401
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,11 @@
|
|||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"primevue": "^4.2.5",
|
||||||
|
"vue": "^3.5.13",
|
||||||
|
"vue-i18n": "^9.14.3"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.17.9",
|
"@types/lodash": "^4.17.9",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
@@ -38,9 +43,6 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"markdown-it-metadata-block": "^1.0.6",
|
"markdown-it-metadata-block": "^1.0.6",
|
||||||
"primevue": "^4.0.7",
|
|
||||||
"vue": "^3.5.6",
|
|
||||||
"vue-i18n": "^9.14.0",
|
|
||||||
"yaml": "^2.6.0"
|
"yaml": "^2.6.0"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|||||||
@@ -69,12 +69,8 @@ class CivitaiModelSearcher(ModelSearcher):
|
|||||||
models: list[dict] = []
|
models: list[dict] = []
|
||||||
|
|
||||||
for version in model_versions:
|
for version in model_versions:
|
||||||
version_files: list[dict] = version.get("files", [])
|
model_files: list[dict] = version.get("files", [])
|
||||||
model_files = utils.filter_with(version_files, {"type": "Model"})
|
model_files = utils.filter_with(model_files, {"type": "Model"})
|
||||||
# issue: https://github.com/hayden-fr/ComfyUI-Model-Manager/issues/188
|
|
||||||
# Some Embeddings do not have Model file, but Negative
|
|
||||||
# Make sure there are at least downloadable files
|
|
||||||
model_files = version_files if len(model_files) == 0 else model_files
|
|
||||||
|
|
||||||
shortname = version.get("name", None) if len(model_files) > 0 else None
|
shortname = version.get("name", None) if len(model_files) > 0 else None
|
||||||
|
|
||||||
@@ -112,7 +108,7 @@ class CivitaiModelSearcher(ModelSearcher):
|
|||||||
description_parts.append("")
|
description_parts.append("")
|
||||||
|
|
||||||
model = {
|
model = {
|
||||||
"id": version.get("id"),
|
"id": file.get("id"),
|
||||||
"shortname": shortname or basename,
|
"shortname": shortname or basename,
|
||||||
"basename": basename,
|
"basename": basename,
|
||||||
"extension": extension,
|
"extension": extension,
|
||||||
@@ -126,7 +122,6 @@ class CivitaiModelSearcher(ModelSearcher):
|
|||||||
"downloadPlatform": "civitai",
|
"downloadPlatform": "civitai",
|
||||||
"downloadUrl": file.get("downloadUrl"),
|
"downloadUrl": file.get("downloadUrl"),
|
||||||
"hashes": file.get("hashes"),
|
"hashes": file.get("hashes"),
|
||||||
"files": version_files if len(version_files) > 1 else None,
|
|
||||||
}
|
}
|
||||||
models.append(model)
|
models.append(model)
|
||||||
|
|
||||||
|
|||||||
79
py/upload.py
79
py/upload.py
@@ -1,79 +0,0 @@
|
|||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
import folder_paths
|
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
|
|
||||||
from . import utils
|
|
||||||
|
|
||||||
|
|
||||||
class ModelUploader:
|
|
||||||
def add_routes(self, routes):
|
|
||||||
|
|
||||||
@routes.get("/model-manager/supported-extensions")
|
|
||||||
async def fetch_model_exts(request):
|
|
||||||
"""
|
|
||||||
Get model exts
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
supported_extensions = list(folder_paths.supported_pt_extensions)
|
|
||||||
return web.json_response({"success": True, "data": supported_extensions})
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"Get model supported extension failed: {str(e)}"
|
|
||||||
utils.print_error(error_msg)
|
|
||||||
return web.json_response({"success": False, "error": error_msg})
|
|
||||||
|
|
||||||
@routes.post("/model-manager/upload")
|
|
||||||
async def upload_model(request):
|
|
||||||
"""
|
|
||||||
Upload model
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
reader = await request.multipart()
|
|
||||||
await self.upload_model(reader)
|
|
||||||
utils.print_info(f"Upload model success")
|
|
||||||
return web.json_response({"success": True, "data": None})
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"Upload model failed: {str(e)}"
|
|
||||||
utils.print_error(error_msg)
|
|
||||||
return web.json_response({"success": False, "error": error_msg})
|
|
||||||
|
|
||||||
async def upload_model(self, reader):
|
|
||||||
uploaded_size = 0
|
|
||||||
last_update_time = time.time()
|
|
||||||
interval = 1.0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
part = await reader.next()
|
|
||||||
if part is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
name = part.name
|
|
||||||
if name == "folder":
|
|
||||||
file_folder = await part.text()
|
|
||||||
|
|
||||||
if name == "file":
|
|
||||||
filename = part.filename
|
|
||||||
filepath = f"{file_folder}/{filename}"
|
|
||||||
tmp_filepath = f"{file_folder}/{filename}.tmp"
|
|
||||||
|
|
||||||
with open(tmp_filepath, "wb") as f:
|
|
||||||
while True:
|
|
||||||
chunk = await part.read_chunk()
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
f.write(chunk)
|
|
||||||
uploaded_size += len(chunk)
|
|
||||||
|
|
||||||
if time.time() - last_update_time >= interval:
|
|
||||||
update_upload_progress = {
|
|
||||||
"uploaded_size": uploaded_size,
|
|
||||||
}
|
|
||||||
await utils.send_json("update_upload_progress", update_upload_progress)
|
|
||||||
|
|
||||||
update_upload_progress = {
|
|
||||||
"uploaded_size": uploaded_size,
|
|
||||||
}
|
|
||||||
await utils.send_json("update_upload_progress", update_upload_progress)
|
|
||||||
os.rename(tmp_filepath, filepath)
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-model-manager"
|
name = "comfyui-model-manager"
|
||||||
description = "Manage models: browsing, download and delete."
|
description = "Manage models: browsing, download and delete."
|
||||||
version = "2.7.0"
|
version = "2.6.3"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
dependencies = ["markdownify"]
|
dependencies = ["markdownify"]
|
||||||
|
|
||||||
|
|||||||
21
src/App.vue
21
src/App.vue
@@ -10,7 +10,6 @@ import DialogDownload from 'components/DialogDownload.vue'
|
|||||||
import DialogExplorer from 'components/DialogExplorer.vue'
|
import DialogExplorer from 'components/DialogExplorer.vue'
|
||||||
import DialogManager from 'components/DialogManager.vue'
|
import DialogManager from 'components/DialogManager.vue'
|
||||||
import DialogScanning from 'components/DialogScanning.vue'
|
import DialogScanning from 'components/DialogScanning.vue'
|
||||||
import DialogUpload from 'components/DialogUpload.vue'
|
|
||||||
import GlobalDialogStack from 'components/GlobalDialogStack.vue'
|
import GlobalDialogStack from 'components/GlobalDialogStack.vue'
|
||||||
import GlobalLoading from 'components/GlobalLoading.vue'
|
import GlobalLoading from 'components/GlobalLoading.vue'
|
||||||
import GlobalToast from 'components/GlobalToast.vue'
|
import GlobalToast from 'components/GlobalToast.vue'
|
||||||
@@ -65,21 +64,6 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const openUploadDialog = () => {
|
|
||||||
dialog.open({
|
|
||||||
key: 'model-manager-upload',
|
|
||||||
title: t('uploadModel'),
|
|
||||||
content: DialogUpload,
|
|
||||||
headerButtons: [
|
|
||||||
{
|
|
||||||
key: 'refresh',
|
|
||||||
icon: 'pi pi-refresh',
|
|
||||||
command: refreshModelsAndConfig,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const openManagerDialog = () => {
|
const openManagerDialog = () => {
|
||||||
const { cardWidth, gutter, aspect, flat } = config
|
const { cardWidth, gutter, aspect, flat } = config
|
||||||
|
|
||||||
@@ -109,11 +93,6 @@ onMounted(() => {
|
|||||||
icon: 'pi pi-download',
|
icon: 'pi pi-download',
|
||||||
command: openDownloadDialog,
|
command: openDownloadDialog,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'upload',
|
|
||||||
icon: 'pi pi-upload',
|
|
||||||
command: openUploadDialog,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
minWidth: cardWidth * 2 + gutter + 42,
|
minWidth: cardWidth * 2 + gutter + 42,
|
||||||
minHeight: (cardWidth / aspect) * 0.5 + 162,
|
minHeight: (cardWidth / aspect) * 0.5 + 162,
|
||||||
|
|||||||
@@ -31,20 +31,12 @@
|
|||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
<ModelContent
|
<ModelContent
|
||||||
v-if="currentModel"
|
v-if="currentModel"
|
||||||
:key="`${currentModel.id}-${currentModel.currentFileId}`"
|
:key="currentModel.id"
|
||||||
:model="currentModel"
|
:model="currentModel"
|
||||||
:editable="true"
|
:editable="true"
|
||||||
@submit="createDownTask"
|
@submit="createDownTask"
|
||||||
>
|
>
|
||||||
<template #action>
|
<template #action>
|
||||||
<div v-if="currentModel.files" class="flex-1">
|
|
||||||
<ResponseSelect
|
|
||||||
:model-value="currentModel.currentFileId"
|
|
||||||
:items="currentModel.selectionFiles"
|
|
||||||
:type="isMobile ? 'drop' : 'button'"
|
|
||||||
>
|
|
||||||
</ResponseSelect>
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-download"
|
icon="pi pi-download"
|
||||||
:label="$t('download')"
|
:label="$t('download')"
|
||||||
|
|||||||
@@ -1,274 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="h-full px-4">
|
|
||||||
<!-- <div v-show="batchScanningStep === 0" class="h-full">
|
|
||||||
<div class="flex h-full items-center px-8">
|
|
||||||
<div class="h-20 w-full opacity-60">
|
|
||||||
<ProgressBar mode="indeterminate" style="height: 6px"></ProgressBar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<Stepper v-model:value="stepValue" class="flex h-full flex-col" linear>
|
|
||||||
<StepList>
|
|
||||||
<Step :value="1">{{ $t('selectModelType') }}</Step>
|
|
||||||
<Step :value="2">{{ $t('selectSubdirectory') }}</Step>
|
|
||||||
<Step :value="3">{{ $t('chooseFile') }}</Step>
|
|
||||||
</StepList>
|
|
||||||
<StepPanels class="flex-1 overflow-hidden">
|
|
||||||
<StepPanel :value="1" class="h-full">
|
|
||||||
<div class="flex h-full flex-col overflow-hidden">
|
|
||||||
<ResponseScroll>
|
|
||||||
<div class="flex flex-wrap gap-4">
|
|
||||||
<Button
|
|
||||||
v-for="item in typeOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
@click="item.command"
|
|
||||||
></Button>
|
|
||||||
</div>
|
|
||||||
</ResponseScroll>
|
|
||||||
</div>
|
|
||||||
</StepPanel>
|
|
||||||
<StepPanel :value="2" class="h-full">
|
|
||||||
<div class="flex h-full flex-col overflow-hidden">
|
|
||||||
<ResponseScroll class="flex-1">
|
|
||||||
<Tree
|
|
||||||
class="h-full"
|
|
||||||
v-model:selection-keys="selectedKey"
|
|
||||||
:value="pathOptions"
|
|
||||||
selectionMode="single"
|
|
||||||
:pt:nodeLabel:class="'text-ellipsis overflow-hidden'"
|
|
||||||
></Tree>
|
|
||||||
</ResponseScroll>
|
|
||||||
|
|
||||||
<div class="flex justify-between pt-6">
|
|
||||||
<Button
|
|
||||||
:label="$t('back')"
|
|
||||||
severity="secondary"
|
|
||||||
icon="pi pi-arrow-left"
|
|
||||||
@click="handleBackTypeSelect"
|
|
||||||
></Button>
|
|
||||||
<Button
|
|
||||||
:label="$t('next')"
|
|
||||||
icon="pi pi-arrow-right"
|
|
||||||
icon-pos="right"
|
|
||||||
:disabled="!enabledUpload"
|
|
||||||
@click="handleConfirmSubdir"
|
|
||||||
></Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</StepPanel>
|
|
||||||
<StepPanel :value="3" class="h-full">
|
|
||||||
<div class="flex h-full flex-col items-center justify-center">
|
|
||||||
<template v-if="showUploadProgress">
|
|
||||||
<div class="w-4/5">
|
|
||||||
<ProgressBar
|
|
||||||
:value="uploadProgress"
|
|
||||||
:pt:value:style="{ transition: 'width .1s linear' }"
|
|
||||||
></ProgressBar>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<div class="overflow-hidden break-words py-8">
|
|
||||||
<div class="overflow-hidden px-8">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="pb-2">
|
|
||||||
{{ $t('selectedSpecialPath') }}
|
|
||||||
</div>
|
|
||||||
<div class="leading-5 opacity-60">
|
|
||||||
{{ selectedModelFolder }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-center gap-4">
|
|
||||||
<Button
|
|
||||||
v-for="item in uploadActions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:icon="item.icon"
|
|
||||||
@click="item.command.call(item)"
|
|
||||||
></Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="h-1/4"></div>
|
|
||||||
</div>
|
|
||||||
</StepPanel>
|
|
||||||
</StepPanels>
|
|
||||||
</Stepper>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import ResponseScroll from 'components/ResponseScroll.vue'
|
|
||||||
import { configSetting } from 'hooks/config'
|
|
||||||
import { useModelFolder, useModels } from 'hooks/model'
|
|
||||||
import { request } from 'hooks/request'
|
|
||||||
import { useToast } from 'hooks/toast'
|
|
||||||
import Button from 'primevue/button'
|
|
||||||
import ProgressBar from 'primevue/progressbar'
|
|
||||||
import Step from 'primevue/step'
|
|
||||||
import StepList from 'primevue/steplist'
|
|
||||||
import StepPanel from 'primevue/steppanel'
|
|
||||||
import StepPanels from 'primevue/steppanels'
|
|
||||||
import Stepper from 'primevue/stepper'
|
|
||||||
import Tree from 'primevue/tree'
|
|
||||||
import { api, app } from 'scripts/comfyAPI'
|
|
||||||
import { computed, onMounted, onUnmounted, ref, toValue } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
const { toast } = useToast()
|
|
||||||
|
|
||||||
const stepValue = ref(1)
|
|
||||||
|
|
||||||
const { folders } = useModels()
|
|
||||||
|
|
||||||
const currentType = ref<string>()
|
|
||||||
const typeOptions = computed(() => {
|
|
||||||
const excludeScanTypes = app.ui?.settings.getSettingValue<string>(
|
|
||||||
configSetting.excludeScanTypes,
|
|
||||||
)
|
|
||||||
const customBlackList =
|
|
||||||
excludeScanTypes
|
|
||||||
?.split(',')
|
|
||||||
.map((type) => type.trim())
|
|
||||||
.filter(Boolean) ?? []
|
|
||||||
return Object.keys(folders.value)
|
|
||||||
.filter((folder) => !customBlackList.includes(folder))
|
|
||||||
.map((type) => {
|
|
||||||
return {
|
|
||||||
label: type,
|
|
||||||
value: type,
|
|
||||||
command: () => {
|
|
||||||
currentType.value = type
|
|
||||||
stepValue.value++
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const { pathOptions } = useModelFolder({ type: currentType })
|
|
||||||
|
|
||||||
const selectedModelFolder = ref<string>()
|
|
||||||
const selectedKey = computed({
|
|
||||||
get: () => {
|
|
||||||
const key = selectedModelFolder.value
|
|
||||||
return key ? { [key]: true } : {}
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
const key = Object.keys(val)[0]
|
|
||||||
selectedModelFolder.value = key
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const enabledUpload = computed(() => {
|
|
||||||
return !!selectedModelFolder.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleBackTypeSelect = () => {
|
|
||||||
selectedModelFolder.value = undefined
|
|
||||||
currentType.value = undefined
|
|
||||||
stepValue.value--
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleConfirmSubdir = () => {
|
|
||||||
stepValue.value++
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadTotalSize = ref<number>()
|
|
||||||
const uploadSize = ref<number>()
|
|
||||||
const uploadProgress = computed(() => {
|
|
||||||
const total = toValue(uploadTotalSize)
|
|
||||||
const size = toValue(uploadSize)
|
|
||||||
if (typeof total === 'number' && typeof size === 'number') {
|
|
||||||
return Math.floor((size / total) * 100)
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
|
||||||
const showUploadProgress = computed(() => {
|
|
||||||
return typeof uploadProgress.value !== 'undefined'
|
|
||||||
})
|
|
||||||
|
|
||||||
const uploadActions = ref([
|
|
||||||
{
|
|
||||||
value: 'back',
|
|
||||||
label: t('back'),
|
|
||||||
icon: 'pi pi-arrow-left',
|
|
||||||
command: () => {
|
|
||||||
stepValue.value--
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'full',
|
|
||||||
label: t('chooseFile'),
|
|
||||||
command: () => {
|
|
||||||
const input = document.createElement('input')
|
|
||||||
input.type = 'file'
|
|
||||||
input.accept = supportedExtensions.value.join(',')
|
|
||||||
input.onchange = async () => {
|
|
||||||
const files = input.files
|
|
||||||
const file = files?.item(0)
|
|
||||||
if (!file) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
uploadTotalSize.value = file.size
|
|
||||||
uploadSize.value = 0
|
|
||||||
const body = new FormData()
|
|
||||||
body.append('folder', toValue(selectedModelFolder)!)
|
|
||||||
body.append('file', file)
|
|
||||||
|
|
||||||
await request('/upload', {
|
|
||||||
method: 'POST',
|
|
||||||
body: body,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Error',
|
|
||||||
detail: error.message,
|
|
||||||
life: 5000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input.click()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const supportedExtensions = ref([])
|
|
||||||
|
|
||||||
const fetchSupportedExtensions = async () => {
|
|
||||||
try {
|
|
||||||
const result = await request('/supported-extensions')
|
|
||||||
supportedExtensions.value = result ?? []
|
|
||||||
} catch (error) {
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Error',
|
|
||||||
detail: error.message,
|
|
||||||
life: 5000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const update_process = (event: CustomEvent) => {
|
|
||||||
const detail = event.detail
|
|
||||||
uploadSize.value = detail.uploaded_size
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchSupportedExtensions()
|
|
||||||
|
|
||||||
api.addEventListener('update_upload_progress', update_process)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
api.removeEventListener('update_upload_progress', update_process)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
></ModelPreview>
|
></ModelPreview>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 overflow-hidden">
|
<div class="flex flex-col gap-4 overflow-hidden">
|
||||||
<div class="flex h-10 items-center justify-end gap-4">
|
<div class="flex items-center justify-end gap-4">
|
||||||
<slot name="action" :metadata="formInstance.metadata.value"></slot>
|
<slot name="action" :metadata="formInstance.metadata.value"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,17 @@ import { useLoading } from 'hooks/loading'
|
|||||||
import { request } from 'hooks/request'
|
import { request } from 'hooks/request'
|
||||||
import { defineStore } from 'hooks/store'
|
import { defineStore } from 'hooks/store'
|
||||||
import { useToast } from 'hooks/toast'
|
import { useToast } from 'hooks/toast'
|
||||||
import { upperFirst } from 'lodash'
|
|
||||||
import { api } from 'scripts/comfyAPI'
|
import { api } from 'scripts/comfyAPI'
|
||||||
import {
|
import {
|
||||||
|
BaseModel,
|
||||||
DownloadTask,
|
DownloadTask,
|
||||||
DownloadTaskOptions,
|
DownloadTaskOptions,
|
||||||
SelectOptions,
|
SelectOptions,
|
||||||
VersionModel,
|
VersionModel,
|
||||||
VersionModelFile,
|
|
||||||
} from 'types/typings'
|
} from 'types/typings'
|
||||||
import { bytesToSize } from 'utils/common'
|
import { bytesToSize } from 'utils/common'
|
||||||
import { onBeforeMount, onMounted, ref, watch } from 'vue'
|
import { onBeforeMount, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import yaml from 'yaml'
|
|
||||||
|
|
||||||
export const useDownload = defineStore('download', (store) => {
|
export const useDownload = defineStore('download', (store) => {
|
||||||
const { toast, confirm, wrapperToastError } = useToast()
|
const { toast, confirm, wrapperToastError } = useToast()
|
||||||
@@ -164,60 +162,12 @@ declare module 'hooks/store' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type WithSelection<T> = SelectOptions & { item: T }
|
|
||||||
|
|
||||||
type FileSelectionVersionModel = VersionModel & {
|
|
||||||
currentFileId?: number
|
|
||||||
selectionFiles?: WithSelection<VersionModelFile>[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useModelSearch = () => {
|
export const useModelSearch = () => {
|
||||||
const loading = useLoading()
|
const loading = useLoading()
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const data = ref<WithSelection<FileSelectionVersionModel>[]>([])
|
const data = ref<(SelectOptions & { item: VersionModel })[]>([])
|
||||||
const current = ref<string | number>()
|
const current = ref<string | number>()
|
||||||
const currentModel = ref<FileSelectionVersionModel>()
|
const currentModel = ref<BaseModel>()
|
||||||
|
|
||||||
const genFileSelectionItem = (
|
|
||||||
item: VersionModel,
|
|
||||||
): FileSelectionVersionModel => {
|
|
||||||
const fileSelectionItem: FileSelectionVersionModel = { ...item }
|
|
||||||
fileSelectionItem.selectionFiles = fileSelectionItem.files
|
|
||||||
?.sort((file) => (file.type === 'Model' ? -1 : 1))
|
|
||||||
.map((file) => {
|
|
||||||
const parts = file.name.split('.')
|
|
||||||
const extension = `.${parts.pop()}`
|
|
||||||
const basename = parts.join('.')
|
|
||||||
|
|
||||||
const regexp = /---\n([\s\S]*?)\n---/
|
|
||||||
const yamlMetadataMatch = item.description.match(regexp)
|
|
||||||
const yamlMetadata = yaml.parse(yamlMetadataMatch?.[1] || '')
|
|
||||||
yamlMetadata.hashes = file.hashes
|
|
||||||
yamlMetadata.metadata = file.metadata
|
|
||||||
const yamlContent = `---\n${yaml.stringify(yamlMetadata)}---`
|
|
||||||
const description = item.description.replace(regexp, yamlContent)
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: file.type === 'Model' ? upperFirst(item.type) : file.type,
|
|
||||||
value: file.id,
|
|
||||||
item: file,
|
|
||||||
command() {
|
|
||||||
if (currentModel.value) {
|
|
||||||
currentModel.value.basename = basename
|
|
||||||
currentModel.value.extension = extension
|
|
||||||
currentModel.value.sizeBytes = file.sizeKB * 1024
|
|
||||||
currentModel.value.metadata = file.metadata
|
|
||||||
currentModel.value.downloadUrl = file.downloadUrl
|
|
||||||
currentModel.value.hashes = file.hashes
|
|
||||||
currentModel.value.description = description
|
|
||||||
currentModel.value.currentFileId = file.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fileSelectionItem.currentFileId = item.files?.[0]?.id
|
|
||||||
return fileSelectionItem
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSearchByUrl = async (url: string) => {
|
const handleSearchByUrl = async (url: string) => {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
@@ -227,17 +177,14 @@ export const useModelSearch = () => {
|
|||||||
loading.show()
|
loading.show()
|
||||||
return request(`/model-info?model-page=${encodeURIComponent(url)}`, {})
|
return request(`/model-info?model-page=${encodeURIComponent(url)}`, {})
|
||||||
.then((resData: VersionModel[]) => {
|
.then((resData: VersionModel[]) => {
|
||||||
data.value = resData.map((item) => {
|
data.value = resData.map((item) => ({
|
||||||
const resolvedItem = genFileSelectionItem(item)
|
label: item.shortname,
|
||||||
return {
|
value: item.id,
|
||||||
label: item.shortname,
|
item,
|
||||||
value: item.id,
|
command() {
|
||||||
item: resolvedItem,
|
current.value = item.id
|
||||||
command() {
|
},
|
||||||
current.value = item.id
|
}))
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
current.value = data.value[0]?.value
|
current.value = data.value[0]?.value
|
||||||
currentModel.value = data.value[0]?.item
|
currentModel.value = data.value[0]?.item
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { defineStore } from 'hooks/store'
|
|||||||
import { useToast } from 'hooks/toast'
|
import { useToast } from 'hooks/toast'
|
||||||
import { castArray, cloneDeep } from 'lodash'
|
import { castArray, cloneDeep } from 'lodash'
|
||||||
import { TreeNode } from 'primevue/treenode'
|
import { TreeNode } from 'primevue/treenode'
|
||||||
import { api, app } from 'scripts/comfyAPI'
|
import { app } from 'scripts/comfyAPI'
|
||||||
import { BaseModel, Model, SelectEvent, WithResolved } from 'types/typings'
|
import { BaseModel, Model, SelectEvent, WithResolved } from 'types/typings'
|
||||||
import { bytesToSize, formatDate, previewUrlToFile } from 'utils/common'
|
import { bytesToSize, formatDate, previewUrlToFile } from 'utils/common'
|
||||||
import { ModelGrid } from 'utils/legacy'
|
import { ModelGrid } from 'utils/legacy'
|
||||||
@@ -27,18 +27,16 @@ import {
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { configSetting } from './config'
|
import { configSetting } from './config'
|
||||||
|
|
||||||
const systemStat = ref()
|
|
||||||
|
|
||||||
type ModelFolder = Record<string, string[]>
|
type ModelFolder = Record<string, string[]>
|
||||||
|
|
||||||
const modelFolderProvideKey = Symbol('modelFolder') as InjectionKey<
|
const modelFolderProvideKey = Symbol('modelFolder') as InjectionKey<
|
||||||
Ref<ModelFolder>
|
Ref<ModelFolder>
|
||||||
>
|
>
|
||||||
|
|
||||||
export const genModelFullName = (model: BaseModel, splitter = '/') => {
|
export const genModelFullName = (model: BaseModel) => {
|
||||||
return [model.subFolder, `${model.basename}${model.extension}`]
|
return [model.subFolder, `${model.basename}${model.extension}`]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(splitter)
|
.join('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const genModelUrl = (model: BaseModel) => {
|
export const genModelUrl = (model: BaseModel) => {
|
||||||
@@ -236,12 +234,6 @@ export const useModels = defineStore('models', (store) => {
|
|||||||
return [prefixPath, fullname].filter(Boolean).join('/')
|
return [prefixPath, fullname].filter(Boolean).join('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
api.getSystemStats().then((res) => {
|
|
||||||
systemStat.value = res
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initialized: initialized,
|
initialized: initialized,
|
||||||
folders: folders,
|
folders: folders,
|
||||||
@@ -725,12 +717,11 @@ export const useModelNodeAction = () => {
|
|||||||
// Use the legacy method instead
|
// Use the legacy method instead
|
||||||
const removeEmbeddingExtension = true
|
const removeEmbeddingExtension = true
|
||||||
const strictDragToAdd = false
|
const strictDragToAdd = false
|
||||||
const splitter = systemStat.value?.system.os === 'nt' ? '\\' : '/'
|
|
||||||
|
|
||||||
ModelGrid.dragAddModel(
|
ModelGrid.dragAddModel(
|
||||||
event,
|
event,
|
||||||
model.type,
|
model.type,
|
||||||
genModelFullName(model, splitter),
|
genModelFullName(model),
|
||||||
removeEmbeddingExtension,
|
removeEmbeddingExtension,
|
||||||
strictDragToAdd,
|
strictDragToAdd,
|
||||||
)
|
)
|
||||||
|
|||||||
155
src/i18n.ts
155
src/i18n.ts
@@ -1,12 +1,157 @@
|
|||||||
import { app } from 'scripts/comfyAPI'
|
import { app } from 'scripts/comfyAPI'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import en from './locales/en.json'
|
|
||||||
import zh from './locales/zh.json'
|
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
en: en,
|
en: {
|
||||||
zh: zh,
|
model: 'Model',
|
||||||
|
modelManager: 'Model Manager',
|
||||||
|
openModelManager: 'Open Model Manager',
|
||||||
|
searchModels: 'Search models',
|
||||||
|
modelCopied: 'Model Copied',
|
||||||
|
download: 'Download',
|
||||||
|
downloadList: 'Download List',
|
||||||
|
downloadTask: 'Download Task',
|
||||||
|
createDownloadTask: 'Create Download Task',
|
||||||
|
parseModelUrl: 'Parse Model URL',
|
||||||
|
pleaseInputModelUrl: 'Input a URL from civitai.com or huggingface.co',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
save: 'Save',
|
||||||
|
delete: 'Delete',
|
||||||
|
deleteAsk: 'Confirm delete this {0}?',
|
||||||
|
modelType: 'Model Type',
|
||||||
|
default: 'Default',
|
||||||
|
network: 'Network',
|
||||||
|
local: 'Local',
|
||||||
|
none: 'None',
|
||||||
|
uploadFile: 'Upload File',
|
||||||
|
tapToChange: 'Tap description to change content',
|
||||||
|
name: 'Name',
|
||||||
|
width: 'Width',
|
||||||
|
height: 'Height',
|
||||||
|
reset: 'Reset',
|
||||||
|
back: 'Back',
|
||||||
|
next: 'Next',
|
||||||
|
batchScanModelInformation: 'Batch scan model information',
|
||||||
|
modelInformationScanning: 'Scanning model information',
|
||||||
|
selectModelType: 'Select model type',
|
||||||
|
selectSubdirectory: 'Select subdirectory',
|
||||||
|
scanModelInformation: 'Scan model information',
|
||||||
|
selectedAllPaths: 'Selected all model paths',
|
||||||
|
selectedSpecialPath: 'Selected special path',
|
||||||
|
scanMissInformation: 'Download missing information',
|
||||||
|
scanFullInformation: 'Override full information',
|
||||||
|
noModelsInCurrentPath: 'There are no models available in the current path',
|
||||||
|
sort: {
|
||||||
|
name: 'Name',
|
||||||
|
size: 'Largest',
|
||||||
|
created: 'Latest created',
|
||||||
|
modified: 'Latest modified',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
extraLarge: 'Extra Large Icons',
|
||||||
|
large: 'Large Icons',
|
||||||
|
medium: 'Medium Icons',
|
||||||
|
small: 'Small Icons',
|
||||||
|
custom: 'Custom Size',
|
||||||
|
customTip: 'Set in `Settings > Model Manager > UI`',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
type: 'Model Type',
|
||||||
|
pathIndex: 'Directory',
|
||||||
|
basename: 'File Name',
|
||||||
|
sizeBytes: 'File Size',
|
||||||
|
createdAt: 'Created At',
|
||||||
|
updatedAt: 'Updated At',
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
apiKey: 'API Key',
|
||||||
|
cardHeight: 'Card Height',
|
||||||
|
cardWidth: 'Card Width',
|
||||||
|
scan: 'Scan',
|
||||||
|
scanMissing: 'Download missing information or preview',
|
||||||
|
scanAll: "Override all models' information and preview",
|
||||||
|
includeHiddenFiles: 'Include hidden files(start with .)',
|
||||||
|
excludeScanTypes: 'Exclude scan types (separate with commas)',
|
||||||
|
ui: 'UI',
|
||||||
|
cardSize: 'Card Size',
|
||||||
|
useFlatUI: 'Flat Layout',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
model: '模型',
|
||||||
|
modelManager: '模型管理器',
|
||||||
|
openModelManager: '打开模型管理器',
|
||||||
|
searchModels: '搜索模型',
|
||||||
|
modelCopied: '模型节点已拷贝',
|
||||||
|
download: '下载',
|
||||||
|
downloadList: '下载列表',
|
||||||
|
downloadTask: '下载任务',
|
||||||
|
createDownloadTask: '创建下载任务',
|
||||||
|
parseModelUrl: '解析模型URL',
|
||||||
|
pleaseInputModelUrl: '输入 civitai.com 或 huggingface.co 的 URL',
|
||||||
|
cancel: '取消',
|
||||||
|
save: '保存',
|
||||||
|
delete: '删除',
|
||||||
|
deleteAsk: '确定要删除此{0}?',
|
||||||
|
modelType: '模型类型',
|
||||||
|
default: '默认',
|
||||||
|
network: '网络',
|
||||||
|
local: '本地',
|
||||||
|
none: '无',
|
||||||
|
uploadFile: '上传文件',
|
||||||
|
tapToChange: '点击描述可更改内容',
|
||||||
|
name: '名称',
|
||||||
|
width: '宽度',
|
||||||
|
height: '高度',
|
||||||
|
reset: '重置',
|
||||||
|
back: '返回',
|
||||||
|
next: '下一步',
|
||||||
|
batchScanModelInformation: '批量扫描模型信息',
|
||||||
|
modelInformationScanning: '扫描模型信息',
|
||||||
|
selectModelType: '选择模型类型',
|
||||||
|
selectSubdirectory: '选择子目录',
|
||||||
|
scanModelInformation: '扫描模型信息',
|
||||||
|
selectedAllPaths: '已选所有模型路径',
|
||||||
|
selectedSpecialPath: '已选指定路径',
|
||||||
|
scanMissInformation: '下载缺失信息',
|
||||||
|
scanFullInformation: '覆盖所有信息',
|
||||||
|
noModelsInCurrentPath: '当前路径中没有可用的模型',
|
||||||
|
sort: {
|
||||||
|
name: '名称',
|
||||||
|
size: '最大',
|
||||||
|
created: '最新创建',
|
||||||
|
modified: '最新修改',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
extraLarge: '超大图标',
|
||||||
|
large: '大图标',
|
||||||
|
medium: '中等图标',
|
||||||
|
small: '小图标',
|
||||||
|
custom: '自定义尺寸',
|
||||||
|
customTip: '在 `设置 > 模型管理器 > 外观` 中设置',
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
type: '类型',
|
||||||
|
pathIndex: '目录',
|
||||||
|
basename: '文件名',
|
||||||
|
sizeBytes: '文件大小',
|
||||||
|
createdAt: '创建时间',
|
||||||
|
updatedAt: '更新时间',
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
apiKey: '密钥',
|
||||||
|
cardHeight: '卡片高度',
|
||||||
|
cardWidth: '卡片宽度',
|
||||||
|
scan: '扫描',
|
||||||
|
scanMissing: '下载缺失的信息或预览图片',
|
||||||
|
scanAll: '覆盖所有模型信息和预览图片',
|
||||||
|
includeHiddenFiles: '包含隐藏文件(以 . 开头的文件或文件夹)',
|
||||||
|
excludeScanTypes: '排除扫描类型(使用英文逗号隔开)',
|
||||||
|
ui: '外观',
|
||||||
|
cardSize: '卡片尺寸',
|
||||||
|
useFlatUI: '展平布局',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLocalLanguage = () => {
|
const getLocalLanguage = () => {
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
{
|
|
||||||
"model": "Model",
|
|
||||||
"modelManager": "Model Manager",
|
|
||||||
"openModelManager": "Open Model Manager",
|
|
||||||
"searchModels": "Search models",
|
|
||||||
"modelCopied": "Model Copied",
|
|
||||||
"download": "Download",
|
|
||||||
"downloadList": "Download List",
|
|
||||||
"downloadTask": "Download Task",
|
|
||||||
"createDownloadTask": "Create Download Task",
|
|
||||||
"parseModelUrl": "Parse Model URL",
|
|
||||||
"pleaseInputModelUrl": "Input a URL from civitai.com or huggingface.co",
|
|
||||||
"cancel": "Cancel",
|
|
||||||
"save": "Save",
|
|
||||||
"delete": "Delete",
|
|
||||||
"deleteAsk": "Confirm delete this {0}?",
|
|
||||||
"modelType": "Model Type",
|
|
||||||
"default": "Default",
|
|
||||||
"network": "Network",
|
|
||||||
"local": "Local",
|
|
||||||
"none": "None",
|
|
||||||
"uploadFile": "Upload File",
|
|
||||||
"tapToChange": "Tap description to change content",
|
|
||||||
"name": "Name",
|
|
||||||
"width": "Width",
|
|
||||||
"height": "Height",
|
|
||||||
"reset": "Reset",
|
|
||||||
"back": "Back",
|
|
||||||
"next": "Next",
|
|
||||||
"batchScanModelInformation": "Batch scan model information",
|
|
||||||
"modelInformationScanning": "Scanning model information",
|
|
||||||
"selectModelType": "Select model type",
|
|
||||||
"selectSubdirectory": "Select subdirectory",
|
|
||||||
"scanModelInformation": "Scan model information",
|
|
||||||
"selectedAllPaths": "Selected all model paths",
|
|
||||||
"selectedSpecialPath": "Selected special path",
|
|
||||||
"scanMissInformation": "Download missing information",
|
|
||||||
"scanFullInformation": "Override full information",
|
|
||||||
"noModelsInCurrentPath": "There are no models available in the current path",
|
|
||||||
"uploadModel": "Upload Model",
|
|
||||||
"chooseFile": "Choose File",
|
|
||||||
"sort": {
|
|
||||||
"name": "Name",
|
|
||||||
"size": "Largest",
|
|
||||||
"created": "Latest created",
|
|
||||||
"modified": "Latest modified"
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"extraLarge": "Extra Large Icons",
|
|
||||||
"large": "Large Icons",
|
|
||||||
"medium": "Medium Icons",
|
|
||||||
"small": "Small Icons",
|
|
||||||
"custom": "Custom Size",
|
|
||||||
"customTip": "Set in `Settings > Model Manager > UI`"
|
|
||||||
},
|
|
||||||
"info": {
|
|
||||||
"type": "Model Type",
|
|
||||||
"pathIndex": "Directory",
|
|
||||||
"basename": "File Name",
|
|
||||||
"sizeBytes": "File Size",
|
|
||||||
"createdAt": "Created At",
|
|
||||||
"updatedAt": "Updated At"
|
|
||||||
},
|
|
||||||
"setting": {
|
|
||||||
"apiKey": "API Key",
|
|
||||||
"cardHeight": "Card Height",
|
|
||||||
"cardWidth": "Card Width",
|
|
||||||
"scan": "Scan",
|
|
||||||
"scanMissing": "Download missing information or preview",
|
|
||||||
"scanAll": "Override all models' information and preview",
|
|
||||||
"includeHiddenFiles": "Include hidden files(start with .)",
|
|
||||||
"excludeScanTypes": "Exclude scan types (separate with commas)",
|
|
||||||
"ui": "UI",
|
|
||||||
"cardSize": "Card Size",
|
|
||||||
"useFlatUI": "Flat Layout"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
{
|
|
||||||
"model": "模型",
|
|
||||||
"modelManager": "模型管理器",
|
|
||||||
"openModelManager": "打开模型管理器",
|
|
||||||
"searchModels": "搜索模型",
|
|
||||||
"modelCopied": "模型节点已拷贝",
|
|
||||||
"download": "下载",
|
|
||||||
"downloadList": "下载列表",
|
|
||||||
"downloadTask": "下载任务",
|
|
||||||
"createDownloadTask": "创建下载任务",
|
|
||||||
"parseModelUrl": "解析模型URL",
|
|
||||||
"pleaseInputModelUrl": "输入 civitai.com 或 huggingface.co 的 URL",
|
|
||||||
"cancel": "取消",
|
|
||||||
"save": "保存",
|
|
||||||
"delete": "删除",
|
|
||||||
"deleteAsk": "确定要删除此{0}?",
|
|
||||||
"modelType": "模型类型",
|
|
||||||
"default": "默认",
|
|
||||||
"network": "网络",
|
|
||||||
"local": "本地",
|
|
||||||
"none": "无",
|
|
||||||
"uploadFile": "上传文件",
|
|
||||||
"tapToChange": "点击描述可更改内容",
|
|
||||||
"name": "名称",
|
|
||||||
"width": "宽度",
|
|
||||||
"height": "高度",
|
|
||||||
"reset": "重置",
|
|
||||||
"back": "返回",
|
|
||||||
"next": "下一步",
|
|
||||||
"batchScanModelInformation": "批量扫描模型信息",
|
|
||||||
"modelInformationScanning": "扫描模型信息",
|
|
||||||
"selectModelType": "选择模型类型",
|
|
||||||
"selectSubdirectory": "选择子目录",
|
|
||||||
"scanModelInformation": "扫描模型信息",
|
|
||||||
"selectedAllPaths": "已选所有模型路径",
|
|
||||||
"selectedSpecialPath": "已选指定路径",
|
|
||||||
"scanMissInformation": "下载缺失信息",
|
|
||||||
"scanFullInformation": "覆盖所有信息",
|
|
||||||
"noModelsInCurrentPath": "当前路径中没有可用的模型",
|
|
||||||
"uploadModel": "上传模型",
|
|
||||||
"chooseFile": "选择文件",
|
|
||||||
"sort": {
|
|
||||||
"name": "名称",
|
|
||||||
"size": "最大",
|
|
||||||
"created": "最新创建",
|
|
||||||
"modified": "最新修改"
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"extraLarge": "超大图标",
|
|
||||||
"large": "大图标",
|
|
||||||
"medium": "中等图标",
|
|
||||||
"small": "小图标",
|
|
||||||
"custom": "自定义尺寸",
|
|
||||||
"customTip": "在 `设置 > 模型管理器 > 外观` 中设置"
|
|
||||||
},
|
|
||||||
"info": {
|
|
||||||
"type": "类型",
|
|
||||||
"pathIndex": "目录",
|
|
||||||
"basename": "文件名",
|
|
||||||
"sizeBytes": "文件大小",
|
|
||||||
"createdAt": "创建时间",
|
|
||||||
"updatedAt": "更新时间"
|
|
||||||
},
|
|
||||||
"setting": {
|
|
||||||
"apiKey": "密钥",
|
|
||||||
"cardHeight": "卡片高度",
|
|
||||||
"cardWidth": "卡片宽度",
|
|
||||||
"scan": "扫描",
|
|
||||||
"scanMissing": "下载缺失的信息或预览图片",
|
|
||||||
"scanAll": "覆盖所有模型信息和预览图片",
|
|
||||||
"includeHiddenFiles": "包含隐藏文件(以 . 开头的文件或文件夹)",
|
|
||||||
"excludeScanTypes": "排除扫描类型(使用英文逗号隔开)",
|
|
||||||
"ui": "外观",
|
|
||||||
"cardSize": "卡片尺寸",
|
|
||||||
"useFlatUI": "展平布局"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
src/types/global.d.ts
vendored
11
src/types/global.d.ts
vendored
@@ -1,10 +1,6 @@
|
|||||||
declare namespace ComfyAPI {
|
declare namespace ComfyAPI {
|
||||||
namespace api {
|
namespace api {
|
||||||
class ComfyApiEvent {
|
class ComfyApi {
|
||||||
getSystemStats: () => Promise<any>
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComfyApi extends ComfyApiEvent {
|
|
||||||
socket: WebSocket
|
socket: WebSocket
|
||||||
fetchApi: (route: string, options?: RequestInit) => Promise<Response>
|
fetchApi: (route: string, options?: RequestInit) => Promise<Response>
|
||||||
addEventListener: (
|
addEventListener: (
|
||||||
@@ -12,11 +8,6 @@ declare namespace ComfyAPI {
|
|||||||
callback: (event: CustomEvent) => void,
|
callback: (event: CustomEvent) => void,
|
||||||
options?: AddEventListenerOptions,
|
options?: AddEventListenerOptions,
|
||||||
) => void
|
) => void
|
||||||
removeEventListener: (
|
|
||||||
type: string,
|
|
||||||
callback: (event: CustomEvent) => void,
|
|
||||||
options?: AddEventListenerOptions,
|
|
||||||
) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const api: ComfyApi
|
const api: ComfyApi
|
||||||
|
|||||||
11
src/types/typings.d.ts
vendored
11
src/types/typings.d.ts
vendored
@@ -22,22 +22,11 @@ export interface Model extends BaseModel {
|
|||||||
children?: Model[]
|
children?: Model[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VersionModelFile {
|
|
||||||
id: number
|
|
||||||
sizeKB: number
|
|
||||||
name: string
|
|
||||||
type: string
|
|
||||||
metadata: Record<string, string>
|
|
||||||
hashes: Record<string, string>
|
|
||||||
downloadUrl: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VersionModel extends BaseModel {
|
export interface VersionModel extends BaseModel {
|
||||||
shortname: string
|
shortname: string
|
||||||
downloadPlatform: string
|
downloadPlatform: string
|
||||||
downloadUrl: string
|
downloadUrl: string
|
||||||
hashes?: Record<string, string>
|
hashes?: Record<string, string>
|
||||||
files?: VersionModelFile[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WithResolved<T> = Omit<T, 'preview'> & {
|
export type WithResolved<T> = Omit<T, 'preview'> & {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"resolveJsonModule": true,
|
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": false,
|
"strict": false,
|
||||||
|
|||||||
106
vite-plugin-transform-imports.js
Normal file
106
vite-plugin-transform-imports.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
|
||||||
|
const parsePrimeVueMap = () => {
|
||||||
|
const root = process.cwd()
|
||||||
|
const primevueFilePath = resolve(root, 'node_modules/primevue/index.mjs')
|
||||||
|
const primevue = readFileSync(primevueFilePath, 'utf-8')
|
||||||
|
const nameExportRegex =
|
||||||
|
/export\s*{\s*default\s*as\s*(?<name>\w+)\s*}\s*from\s*['"](?<subpackage>primevue\/\S*)['"];?/g
|
||||||
|
const matches = primevue.matchAll(nameExportRegex)
|
||||||
|
const map = {}
|
||||||
|
for (const match of matches) {
|
||||||
|
map[match.groups.subpackage] = match.groups.name
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {import('vite').Plugin}
|
||||||
|
*/
|
||||||
|
export default function customTransformImports() {
|
||||||
|
const externals = [
|
||||||
|
{
|
||||||
|
pattern: 'vue',
|
||||||
|
global: 'Vue',
|
||||||
|
subpackageMap: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /^primevue\/?.*$/,
|
||||||
|
global: 'PrimeVue',
|
||||||
|
subpackageMap: parsePrimeVueMap(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: 'vue-i18n',
|
||||||
|
global: 'VueI18n',
|
||||||
|
subpackageMap: {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'custom-transform-imports',
|
||||||
|
enforce: 'post',
|
||||||
|
config() {
|
||||||
|
return {
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: externals.map((o) => o.pattern),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderChunk(code) {
|
||||||
|
let transformedCode = code
|
||||||
|
|
||||||
|
const toString = (value) => {
|
||||||
|
if (value instanceof RegExp) {
|
||||||
|
return value.source.replace(/^\^|\$$/g, '')
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const external of externals) {
|
||||||
|
const { pattern, global, subpackageMap } = external
|
||||||
|
|
||||||
|
const importRegexp = new RegExp(
|
||||||
|
`import\\s+([^;]*?)\\s+from\\s+["'](${toString(pattern)})["'];?`,
|
||||||
|
'gi',
|
||||||
|
)
|
||||||
|
transformedCode = transformedCode.replace(
|
||||||
|
importRegexp,
|
||||||
|
(_, importedContent, packageName) => {
|
||||||
|
const result = []
|
||||||
|
|
||||||
|
const namedImportRegexp = /,?\s*(?<named>{[^;]*?})/g
|
||||||
|
const namedImports = importedContent.matchAll(namedImportRegexp)
|
||||||
|
for (const m of namedImports) {
|
||||||
|
const named = m.groups.named
|
||||||
|
const aliasNamed = named.replace(/\s+as\s+/g, ': ')
|
||||||
|
result.push(`const ${aliasNamed} = window.${global};`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultImport = importedContent
|
||||||
|
.replace(namedImportRegexp, '')
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
if (defaultImport) {
|
||||||
|
const subpackageName = subpackageMap[packageName]
|
||||||
|
if (subpackageName) {
|
||||||
|
result.push(
|
||||||
|
`const ${defaultImport} = window.${global}.${subpackageName};`,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
result.push(`const ${defaultImport} = window.${global};`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.join('\n')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedCode
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { defineConfig, Plugin } from 'vite'
|
import { defineConfig, Plugin } from 'vite'
|
||||||
|
import transformImports from './vite-plugin-transform-imports'
|
||||||
|
|
||||||
function css(): Plugin {
|
function css(): Plugin {
|
||||||
return {
|
return {
|
||||||
@@ -108,7 +109,14 @@ function createWebVersion(): Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue(), css(), output(), dev(), createWebVersion()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
css(),
|
||||||
|
output(),
|
||||||
|
dev(),
|
||||||
|
createWebVersion(),
|
||||||
|
transformImports(),
|
||||||
|
],
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
outDir: 'web',
|
outDir: 'web',
|
||||||
@@ -119,13 +127,6 @@ export default defineConfig({
|
|||||||
// Disabling tree-shaking
|
// Disabling tree-shaking
|
||||||
// Prevent vite remove unused exports
|
// Prevent vite remove unused exports
|
||||||
treeshake: true,
|
treeshake: true,
|
||||||
output: {
|
|
||||||
manualChunks(id) {
|
|
||||||
if (id.includes('primevue')) {
|
|
||||||
return 'primevue'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
chunkSizeWarningLimit: 1024,
|
chunkSizeWarningLimit: 1024,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user