Compare commits

...

5 Commits

Author SHA1 Message Date
github-actions
da7df96a27 Update locales [skip ci] 2025-05-24 13:15:23 +00:00
Terry Jia
2591d39da3 add fetch node 2025-05-24 09:03:10 -04:00
Terry Jia
674d04c9cf Export vue new (#3966)
Co-authored-by: hayden <48267247+hayden-fr@users.noreply.github.com>
2025-05-23 18:24:33 -07:00
Terry Jia
8209765eec [3d] improve mtl support logic (#3965) 2025-05-23 18:22:13 -07:00
Terry Jia
9d48638464 [3d] fix wrong generated language translation for 3d node output (#3967)
Co-authored-by: github-actions <github-actions@github.com>
2025-05-22 16:49:48 -07:00
22 changed files with 496 additions and 250 deletions

View File

@@ -1,59 +0,0 @@
import { Plugin } from 'vite'
/**
* Vite plugin that adds an alias export for Vue's createBaseVNode as createElementVNode.
*
* This plugin addresses compatibility issues where some components or libraries
* might be using the older createElementVNode function name instead of createBaseVNode.
* It modifies the Vue vendor chunk during build to add the alias export.
*
* @returns {Plugin} A Vite plugin that modifies the Vue vendor chunk exports
*/
export function addElementVnodeExportPlugin(): Plugin {
return {
name: 'add-element-vnode-export-plugin',
renderChunk(code, chunk, _options) {
if (chunk.name.startsWith('vendor-vue')) {
const exportRegex = /(export\s*\{)([^}]*)(\}\s*;?\s*)$/
const match = code.match(exportRegex)
if (match) {
const existingExports = match[2].trim()
const exportsArray = existingExports
.split(',')
.map((e) => e.trim())
.filter(Boolean)
const hasCreateBaseVNode = exportsArray.some((e) =>
e.startsWith('createBaseVNode')
)
const hasCreateElementVNode = exportsArray.some((e) =>
e.includes('createElementVNode')
)
if (hasCreateBaseVNode && !hasCreateElementVNode) {
const newExportStatement = `${match[1]} ${existingExports ? existingExports + ',' : ''} createBaseVNode as createElementVNode ${match[3]}`
const newCode = code.replace(exportRegex, newExportStatement)
console.log(
`[add-element-vnode-export-plugin] Added 'createBaseVNode as createElementVNode' export to vendor-vue chunk.`
)
return { code: newCode, map: null }
} else if (!hasCreateBaseVNode) {
console.warn(
`[add-element-vnode-export-plugin] Warning: 'createBaseVNode' not found in exports of vendor-vue chunk. Cannot add alias.`
)
}
} else {
console.warn(
`[add-element-vnode-export-plugin] Warning: Could not find expected export block format in vendor-vue chunk.`
)
}
}
return null
}
}
}

View File

@@ -1,9 +1,24 @@
import type { OutputOptions } from 'rollup'
import { HtmlTagDescriptor, Plugin } from 'vite'
import glob from 'fast-glob'
import fs from 'fs-extra'
import { dirname, join } from 'node:path'
import { HtmlTagDescriptor, Plugin, normalizePath } from 'vite'
interface VendorLibrary {
interface ImportMapSource {
name: string
pattern: RegExp
pattern: string | RegExp
entry: string
recursiveDependence?: boolean
override?: Record<string, Partial<ImportMapSource>>
}
const parseDeps = (root: string, pkg: string) => {
const pkgPath = join(root, 'node_modules', pkg, 'package.json')
if (fs.existsSync(pkgPath)) {
const content = fs.readFileSync(pkgPath, 'utf-8')
const pkg = JSON.parse(content)
return Object.keys(pkg.dependencies || {})
}
return []
}
/**
@@ -23,53 +38,89 @@ interface VendorLibrary {
* @returns {Plugin} A Vite plugin that generates and injects an import map
*/
export function generateImportMapPlugin(
vendorLibraries: VendorLibrary[]
importMapSources: ImportMapSource[]
): Plugin {
const importMapEntries: Record<string, string> = {}
const resolvedImportMapSources: Map<string, ImportMapSource> = new Map()
const assetDir = 'assets/lib'
let root: string
return {
name: 'generate-import-map-plugin',
// Configure manual chunks during the build process
configResolved(config) {
root = config.root
if (config.build) {
// Ensure rollupOptions exists
if (!config.build.rollupOptions) {
config.build.rollupOptions = {}
}
const outputOptions: OutputOptions = {
manualChunks: (id: string) => {
for (const lib of vendorLibraries) {
if (lib.pattern.test(id)) {
return `vendor-${lib.name}`
}
for (const source of importMapSources) {
resolvedImportMapSources.set(source.name, source)
if (source.recursiveDependence) {
const deps = parseDeps(root, source.name)
while (deps.length) {
const dep = deps.shift()!
const depSource = Object.assign({}, source, {
name: dep,
pattern: dep,
...source.override?.[dep]
})
resolvedImportMapSources.set(depSource.name, depSource)
const _deps = parseDeps(root, depSource.name)
deps.unshift(..._deps)
}
return null
},
// Disable minification of internal exports to preserve function names
minifyInternalExports: false
}
}
config.build.rollupOptions.output = outputOptions
const external: (string | RegExp)[] = []
for (const [, source] of resolvedImportMapSources) {
external.push(source.pattern)
}
config.build.rollupOptions.external = external
}
},
generateBundle(_options, bundle) {
for (const fileName in bundle) {
const chunk = bundle[fileName]
if (chunk.type === 'chunk' && !chunk.isEntry) {
// Find matching vendor library by chunk name
const vendorLib = vendorLibraries.find(
(lib) => chunk.name === `vendor-${lib.name}`
)
generateBundle(_options) {
for (const [, source] of resolvedImportMapSources) {
if (source.entry) {
const moduleFile = join(source.name, source.entry)
const sourceFile = join(root, 'node_modules', moduleFile)
const targetFile = join(root, 'dist', assetDir, moduleFile)
if (vendorLib) {
const relativePath = `./${chunk.fileName.replace(/\\/g, '/')}`
importMapEntries[vendorLib.name] = relativePath
importMapEntries[source.name] =
'./' + normalizePath(join(assetDir, moduleFile))
console.log(
`[ImportMap Plugin] Found chunk: ${chunk.name} -> Mapped '${vendorLib.name}' to '${relativePath}'`
)
const targetDir = dirname(targetFile)
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
fs.copyFileSync(sourceFile, targetFile)
}
if (source.recursiveDependence) {
const files = glob.sync(['**/*.{js,mjs}'], {
cwd: join(root, 'node_modules', source.name)
})
for (const file of files) {
const moduleFile = join(source.name, file)
const sourceFile = join(root, 'node_modules', moduleFile)
const targetFile = join(root, 'dist', assetDir, moduleFile)
importMapEntries[normalizePath(join(source.name, dirname(file)))] =
'./' + normalizePath(join(assetDir, moduleFile))
const targetDir = dirname(targetFile)
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
fs.copyFileSync(sourceFile, targetFile)
}
}
}

View File

@@ -1,3 +1,2 @@
export { addElementVnodeExportPlugin } from './addElementVnodeExportPlugin'
export { comfyAPIPlugin } from './comfyAPIPlugin'
export { generateImportMapPlugin } from './generateImportMapPlugin'

View File

@@ -0,0 +1,97 @@
import { t } from '@/i18n'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { useExtensionService } from '@/services/extensionService'
import { useToastStore } from '@/stores/toastStore'
useExtensionService().registerExtension({
name: 'Comfy.FetchApi',
async nodeCreated(node) {
if (node.constructor.comfyClass !== 'FetchApi') return
const onExecuted = node.onExecuted
const msg = t('toastMessages.unableToFetchFile')
const downloadFile = async (
typeValue: string,
subfolderValue: string,
filenameValue: string
) => {
try {
const params = [
'filename=' + encodeURIComponent(filenameValue),
'type=' + encodeURIComponent(typeValue),
'subfolder=' + encodeURIComponent(subfolderValue),
app.getRandParam().substring(1)
].join('&')
const fetchURL = `/view?${params}`
const response = await api.fetchApi(fetchURL)
if (!response.ok) {
console.error(response)
useToastStore().addAlert(msg)
return false
}
const blob = await response.blob()
const downloadFilename = filenameValue
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.style.display = 'none'
a.href = url
a.download = downloadFilename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
return true
} catch (error) {
console.error(error)
useToastStore().addAlert(msg)
return false
}
}
const type = node.widgets?.find((w) => w.name === 'type')
const subfolder = node.widgets?.find((w) => w.name === 'subfolder')
const filename = node.widgets?.find((w) => w.name === 'filename')
node.onExecuted = function (message: any) {
onExecuted?.apply(this, arguments as any)
const typeInput = message.result[0]
const subfolderInput = message.result[1]
const filenameInput = message.result[2]
const autoDownload = node.widgets?.find((w) => w.name === 'auto_download')
if (type && subfolder && filename) {
type.value = typeInput
subfolder.value = subfolderInput
filename.value = filenameInput
if (autoDownload && autoDownload.value) {
downloadFile(typeInput, subfolderInput, filenameInput)
}
}
}
node.addWidget('button', 'download', 'download', async () => {
if (type && subfolder && filename) {
await downloadFile(
type.value as string,
subfolder.value as string,
filename.value as string
)
} else {
console.error(msg)
useToastStore().addAlert(msg)
}
})
}
})

View File

@@ -1,3 +1,4 @@
import './apiNode'
import './clipspace'
import './contextMenuFilter'
import './dynamicPrompts'

View File

@@ -266,7 +266,7 @@ useExtensionService().registerExtension({
LOAD_3D_ANIMATION(node) {
const fileInput = document.createElement('input')
fileInput.type = 'file'
fileInput.accept = '.fbx,glb,gltf'
fileInput.accept = '.gltf,.glb,.fbx'
fileInput.style.display = 'none'
fileInput.onchange = async () => {
if (fileInput.files?.length) {
@@ -452,31 +452,43 @@ useExtensionService().registerExtension({
const onExecuted = node.onExecuted
node.onExecuted = function (message: any) {
onExecuted?.apply(this, arguments as any)
useLoad3dService().waitForLoad3d(node, (load3d) => {
const config = new Load3DConfiguration(load3d)
let filePath = message.result[0]
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
if (!filePath) {
const msg = t('toastMessages.unableToGetModelFilePath')
console.error(msg)
useToastStore().addAlert(msg)
}
if (modelWidget) {
const lastTimeModelFile = node.properties['Last Time Model File']
let cameraState = message.result[1]
if (lastTimeModelFile) {
modelWidget.value = lastTimeModelFile
useLoad3dService().waitForLoad3d(node, (load3d) => {
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
if (modelWidget) {
modelWidget.value = filePath.replaceAll('\\', '/')
const config = new Load3DConfiguration(load3d)
const cameraState = node.properties['Camera Info']
config.configure('output', modelWidget, cameraState)
}
})
}
node.onExecuted = function (message: any) {
onExecuted?.apply(this, arguments as any)
let filePath = message.result[0]
if (!filePath) {
const msg = t('toastMessages.unableToGetModelFilePath')
console.error(msg)
useToastStore().addAlert(msg)
}
let cameraState = message.result[1]
modelWidget.value = filePath.replaceAll('\\', '/')
node.properties['Last Time Model File'] = modelWidget.value
config.configure('output', modelWidget, cameraState)
}
}
})
}
})
@@ -526,29 +538,42 @@ useExtensionService().registerExtension({
const onExecuted = node.onExecuted
node.onExecuted = function (message: any) {
onExecuted?.apply(this, arguments as any)
useLoad3dService().waitForLoad3d(node, (load3d) => {
const config = new Load3DConfiguration(load3d)
let filePath = message.result[0]
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
if (!filePath) {
const msg = t('toastMessages.unableToGetModelFilePath')
console.error(msg)
useToastStore().addAlert(msg)
}
if (modelWidget) {
const lastTimeModelFile = node.properties['Last Time Model File']
let cameraState = message.result[1]
if (lastTimeModelFile) {
modelWidget.value = lastTimeModelFile
useLoad3dService().waitForLoad3d(node, (load3d) => {
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
if (modelWidget) {
modelWidget.value = filePath.replaceAll('\\', '/')
const config = new Load3DConfiguration(load3d)
const cameraState = node.properties['Camera Info']
config.configure('output', modelWidget, cameraState)
}
})
}
node.onExecuted = function (message: any) {
onExecuted?.apply(this, arguments as any)
let filePath = message.result[0]
if (!filePath) {
const msg = t('toastMessages.unableToGetModelFilePath')
console.error(msg)
useToastStore().addAlert(msg)
}
let cameraState = message.result[1]
modelWidget.value = filePath.replaceAll('\\', '/')
node.properties['Last Time Model File'] = modelWidget.value
config.configure('output', modelWidget, cameraState)
}
}
})
}
})

View File

@@ -132,6 +132,14 @@ export class LoaderManager implements LoaderManagerInterface {
if (this.modelManager.materialMode === 'original') {
const mtlUrl = url.replace(/(filename=.*?)\.obj/, '$1.mtl')
const subfolderMatch = url.match(/[?&]subfolder=([^&]*)/)
const subfolder = subfolderMatch
? decodeURIComponent(subfolderMatch[1])
: '3d'
this.mtlLoader.setSubfolder(subfolder)
try {
const materials = await this.mtlLoader.loadAsync(mtlUrl)
materials.preload()

View File

@@ -38,6 +38,10 @@ class OverrideMTLLoader extends Loader {
this.loadRootFolder = loadRootFolder
}
setSubfolder(subfolder) {
this.subfolder = subfolder
}
/**
* Starts loading from the given URL and passes the loaded MTL asset
* to the `onLoad()` callback.
@@ -135,7 +139,8 @@ class OverrideMTLLoader extends Loader {
const materialCreator = new OverrideMaterialCreator(
this.resourcePath || path,
this.materialOptions,
this.loadRootFolder
this.loadRootFolder,
this.subfolder
)
materialCreator.setCrossOrigin(this.crossOrigin)
materialCreator.setManager(this.manager)
@@ -155,7 +160,7 @@ class OverrideMTLLoader extends Loader {
*/
class OverrideMaterialCreator {
constructor(baseUrl = '', options = {}, loadRootFolder) {
constructor(baseUrl = '', options = {}, loadRootFolder, subfolder) {
this.baseUrl = baseUrl
this.options = options
this.materialsInfo = {}
@@ -164,6 +169,7 @@ class OverrideMaterialCreator {
this.nameLookup = {}
this.loadRootFolder = loadRootFolder
this.subfolder = subfolder
this.crossOrigin = 'anonymous'
@@ -283,16 +289,25 @@ class OverrideMaterialCreator {
/**
* Override for ComfyUI api url
*/
function resolveURL(baseUrl, url, loadRootFolder) {
function resolveURL(baseUrl, url, loadRootFolder, subfolder) {
if (typeof url !== 'string' || url === '') return ''
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, -1)
}
if (!baseUrl.endsWith('api')) {
baseUrl = '/api'
}
baseUrl =
baseUrl +
'/view?filename=' +
url +
'&type=' +
loadRootFolder +
'&subfolder=3d'
'&subfolder=' +
subfolder
return baseUrl
}
@@ -302,7 +317,12 @@ class OverrideMaterialCreator {
const texParams = scope.getTextureParams(value, params)
const map = scope.loadTexture(
resolveURL(scope.baseUrl, texParams.url, scope.loadRootFolder)
resolveURL(
scope.baseUrl,
texParams.url,
scope.loadRootFolder,
scope.subfolder
)
)
map.repeat.copy(texParams.scale)

View File

@@ -1273,7 +1273,8 @@
"failedToPurchaseCredits": "Failed to purchase credits: {error}",
"unauthorizedDomain": "Your domain {domain} is not authorized to use this service. Please contact {email} to add your domain to the whitelist.",
"useApiKeyTip": "Tip: Can't access normal login? Use the Comfy API Key option.",
"nothingSelected": "Nothing selected"
"nothingSelected": "Nothing selected",
"unableToFetchFile": "Unable to fetch file"
},
"auth": {
"apiKey": {

View File

@@ -1359,6 +1359,7 @@
"pendingTasksDeleted": "Tareas pendientes eliminadas",
"pleaseSelectNodesToGroup": "Por favor, seleccione los nodos (u otros grupos) para crear un grupo para",
"pleaseSelectOutputNodes": "Por favor, selecciona los nodos de salida",
"unableToFetchFile": "No se pudo obtener el archivo",
"unableToGetModelFilePath": "No se puede obtener la ruta del archivo del modelo",
"unauthorizedDomain": "Tu dominio {domain} no está autorizado para usar este servicio. Por favor, contacta a {email} para agregar tu dominio a la lista blanca.",
"updateRequested": "Actualización solicitada",

View File

@@ -3403,7 +3403,7 @@
"clear": {
},
"height": {
"name": "altura"
"name": "alto"
},
"image": {
"name": "imagen"
@@ -3417,20 +3417,26 @@
"name": "ancho"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "imagen"
},
"1": {
"name": "mask"
},
"2": {
"name": "ruta_malla"
},
"3": {
"name": "normal"
},
{
"4": {
"name": "lineart"
},
{
"name": "camera_info"
"5": {
"name": "info_cámara"
}
]
}
},
"Load3DAnimation": {
"display_name": "Cargar 3D - Animación",
@@ -3438,7 +3444,7 @@
"clear": {
},
"height": {
"name": "altura"
"name": "alto"
},
"image": {
"name": "imagen"
@@ -3452,17 +3458,23 @@
"name": "ancho"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "imagen"
},
"1": {
"name": "mask"
},
"2": {
"name": "ruta_malla"
},
"3": {
"name": "normal"
},
{
"name": "camera_info"
"4": {
"name": "info_cámara"
}
]
}
},
"LoadAudio": {
"display_name": "CargarAudio",

View File

@@ -1359,6 +1359,7 @@
"pendingTasksDeleted": "Tâches en attente supprimées",
"pleaseSelectNodesToGroup": "Veuillez sélectionner les nœuds (ou autres groupes) pour créer un groupe pour",
"pleaseSelectOutputNodes": "Veuillez sélectionner les nœuds de sortie",
"unableToFetchFile": "Impossible de récupérer le fichier",
"unableToGetModelFilePath": "Impossible d'obtenir le chemin du fichier modèle",
"unauthorizedDomain": "Votre domaine {domain} n'est pas autorisé à utiliser ce service. Veuillez contacter {email} pour ajouter votre domaine à la liste blanche.",
"updateRequested": "Mise à jour demandée",

View File

@@ -3417,20 +3417,26 @@
"name": "largeur"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "image"
},
"1": {
"name": "masque"
},
"2": {
"name": "chemin_maillage"
},
"3": {
"name": "normale"
},
{
"name": "ligne artistique"
"4": {
"name": "lineart"
},
{
"name": "informations caméra"
"5": {
"name": "info_caméra"
}
]
}
},
"Load3DAnimation": {
"display_name": "Charger 3D - Animation",
@@ -3452,17 +3458,23 @@
"name": "largeur"
}
},
"outputs": [
null,
null,
null,
{
"name": "normal"
"outputs": {
"0": {
"name": "image"
},
{
"name": "camera_info"
"1": {
"name": "masque"
},
"2": {
"name": "chemin_maillage"
},
"3": {
"name": "normale"
},
"4": {
"name": "info_caméra"
}
]
}
},
"LoadAudio": {
"display_name": "ChargerAudio",

View File

@@ -1359,6 +1359,7 @@
"pendingTasksDeleted": "保留中のタスクが削除されました",
"pleaseSelectNodesToGroup": "グループを作成するためのノード(または他のグループ)を選択してください",
"pleaseSelectOutputNodes": "出力ノードを選択してください",
"unableToFetchFile": "ファイルを取得できません",
"unableToGetModelFilePath": "モデルファイルのパスを取得できません",
"unauthorizedDomain": "あなたのドメイン {domain} はこのサービスを利用する権限がありません。ご利用のドメインをホワイトリストに追加するには、{email} までご連絡ください。",
"updateRequested": "更新が要求されました",

View File

@@ -3417,23 +3417,29 @@
"name": "幅"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "画像"
},
"1": {
"name": "マスク"
},
"2": {
"name": "メッシュパス"
},
"3": {
"name": "法線"
},
{
"4": {
"name": "線画"
},
{
"5": {
"name": "カメラ情報"
}
]
}
},
"Load3DAnimation": {
"display_name": "3D読み込 - アニメーション",
"display_name": "3D読み込 - アニメーション",
"inputs": {
"clear": {
},
@@ -3452,17 +3458,23 @@
"name": "幅"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "画像"
},
"1": {
"name": "マスク"
},
"2": {
"name": "メッシュパス"
},
"3": {
"name": "法線"
},
{
"4": {
"name": "カメラ情報"
}
]
}
},
"LoadAudio": {
"display_name": "音声を読み込む",

View File

@@ -1359,6 +1359,7 @@
"pendingTasksDeleted": "보류 중인 작업이 삭제되었습니다",
"pleaseSelectNodesToGroup": "그룹을 만들기 위해 노드(또는 다른 그룹)를 선택해 주세요",
"pleaseSelectOutputNodes": "출력 노드를 선택해 주세요",
"unableToFetchFile": "파일을 가져올 수 없습니다",
"unableToGetModelFilePath": "모델 파일 경로를 가져올 수 없습니다",
"unauthorizedDomain": "귀하의 도메인 {domain}은(는) 이 서비스를 사용할 수 있는 권한이 없습니다. 도메인을 허용 목록에 추가하려면 {email}로 문의해 주세요.",
"updateRequested": "업데이트 요청됨",

View File

@@ -3398,7 +3398,7 @@
}
},
"Load3D": {
"display_name": "3D 로드",
"display_name": "3D 불러오기",
"inputs": {
"clear": {
},
@@ -3417,23 +3417,29 @@
"name": "너비"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "이미지"
},
"1": {
"name": "마스크"
},
"2": {
"name": "메시 경로"
},
"3": {
"name": "노멀"
},
{
"4": {
"name": "라인아트"
},
{
"5": {
"name": "카메라 정보"
}
]
}
},
"Load3DAnimation": {
"display_name": "3D 로드 - 애니메이션",
"display_name": "3D 불러오기 - 애니메이션",
"inputs": {
"clear": {
},
@@ -3452,17 +3458,23 @@
"name": "너비"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "이미지"
},
"1": {
"name": "마스크"
},
"2": {
"name": "메시 경로"
},
"3": {
"name": "노멀"
},
{
"4": {
"name": "카메라 정보"
}
]
}
},
"LoadAudio": {
"display_name": "오디오 로드",

View File

@@ -1359,6 +1359,7 @@
"pendingTasksDeleted": "Ожидающие задачи удалены",
"pleaseSelectNodesToGroup": "Пожалуйста, выберите узлы (или другие группы) для создания группы",
"pleaseSelectOutputNodes": "Пожалуйста, выберите выходные узлы",
"unableToFetchFile": "Не удалось получить файл",
"unableToGetModelFilePath": "Не удалось получить путь к файлу модели",
"unauthorizedDomain": "Ваш домен {domain} не авторизован для использования этого сервиса. Пожалуйста, свяжитесь с {email}, чтобы добавить ваш домен в белый список.",
"updateRequested": "Запрошено обновление",

View File

@@ -3409,7 +3409,7 @@
"name": "изображение"
},
"model_file": {
"name": "файл_модели"
"name": "файл модели"
},
"upload 3d model": {
},
@@ -3417,23 +3417,29 @@
"name": "ширина"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "изображение"
},
"1": {
"name": "mask"
},
"2": {
"name": "путь к mesh"
},
"3": {
"name": "нормаль"
},
{
"name": "линеарт"
"4": {
"name": "линейный рисунок"
},
{
"name": "информация_камеры"
"5": {
"name": "информация о камере"
}
]
}
},
"Load3DAnimation": {
"display_name": "Загрузить 3D Анимация",
"display_name": "Загрузить 3D - Анимация",
"inputs": {
"clear": {
},
@@ -3452,17 +3458,23 @@
"name": "ширина"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "изображение"
},
"1": {
"name": "mask"
},
"2": {
"name": "путь_к_модели"
},
"3": {
"name": "нормаль"
},
{
"name": "информация_камеры"
"4": {
"name": "информация_о_камере"
}
]
}
},
"LoadAudio": {
"display_name": "Загрузить аудио",

View File

@@ -1359,6 +1359,7 @@
"pendingTasksDeleted": "待处理任务已删除",
"pleaseSelectNodesToGroup": "请选取节点(或其他组)以创建分组",
"pleaseSelectOutputNodes": "请选择输出节点",
"unableToFetchFile": "无法获取文件",
"unableToGetModelFilePath": "无法获取模型文件路径",
"unauthorizedDomain": "您的域名 {domain} 未被授权使用此服务。请联系 {email} 将您的域名添加到白名单。",
"updateRequested": "已请求更新",

View File

@@ -3417,20 +3417,26 @@
"name": "宽度"
}
},
"outputs": [
null,
null,
null,
{
"name": "法线"
"outputs": {
"0": {
"name": "image"
},
{
"name": "线稿"
"1": {
"name": "mask"
},
{
"name": "相机信息"
"2": {
"name": "mesh_path"
},
"3": {
"name": "normal"
},
"4": {
"name": "lineart"
},
"5": {
"name": "camera_info"
}
]
}
},
"Load3DAnimation": {
"display_name": "加载3D动画",
@@ -3452,17 +3458,23 @@
"name": "宽度"
}
},
"outputs": [
null,
null,
null,
{
"outputs": {
"0": {
"name": "图像"
},
"1": {
"name": "遮罩"
},
"2": {
"name": "mesh_path"
},
"3": {
"name": "法线"
},
{
"4": {
"name": "相机信息"
}
]
}
},
"LoadAudio": {
"display_name": "加载音频",

View File

@@ -8,11 +8,7 @@ import { createHtmlPlugin } from 'vite-plugin-html'
import vueDevTools from 'vite-plugin-vue-devtools'
import type { UserConfigExport } from 'vitest/config'
import {
addElementVnodeExportPlugin,
comfyAPIPlugin,
generateImportMapPlugin
} from './build/plugins'
import { comfyAPIPlugin, generateImportMapPlugin } from './build/plugins'
dotenv.config()
@@ -77,11 +73,40 @@ export default defineConfig({
: [vue()]),
comfyAPIPlugin(IS_DEV),
generateImportMapPlugin([
{ name: 'vue', pattern: /[\\/]node_modules[\\/]vue[\\/]/ },
{ name: 'primevue', pattern: /[\\/]node_modules[\\/]primevue[\\/]/ },
{ name: 'vue-i18n', pattern: /[\\/]node_modules[\\/]vue-i18n[\\/]/ }
{
name: 'vue',
pattern: 'vue',
entry: './dist/vue.esm-browser.prod.js'
},
{
name: 'vue-i18n',
pattern: 'vue-i18n',
entry: './dist/vue-i18n.esm-browser.prod.js'
},
{
name: 'primevue',
pattern: /^primevue\/?.*/,
entry: './index.mjs',
recursiveDependence: true
},
{
name: '@primevue/themes',
pattern: /^@primevue\/themes\/?.*/,
entry: './index.mjs',
recursiveDependence: true
},
{
name: '@primevue/forms',
pattern: /^@primevue\/forms\/?.*/,
entry: './index.mjs',
recursiveDependence: true,
override: {
'@primeuix/forms': {
entry: ''
}
}
}
]),
addElementVnodeExportPlugin(),
Icons({
compiler: 'vue3'