Compare commits
2 Commits
v1.19.9
...
update-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
702458acbd | ||
|
|
fd01f13fbf |
2
.github/workflows/test-ui.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
repository: 'Comfy-Org/ComfyUI_devtools'
|
||||
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
|
||||
ref: 'd05fd48dd787a4192e16802d4244cfcc0e2f9684'
|
||||
ref: '9d2421fd3208a310e4d0f71fca2ea0c985759c33'
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
@@ -33,11 +33,5 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
||||
@@ -130,11 +130,6 @@
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
||||
@@ -42,12 +42,12 @@
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"scale": 2.1600300525920346,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
63.071794466403446,
|
||||
75.18055335968394
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -499,11 +499,6 @@
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"id": "1a95532f-c8aa-4c9d-a7f6-f928ba2d4862",
|
||||
"revision": 0,
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 3,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "PreviewAny",
|
||||
"pos": [946.2566528320312, 598.4373168945312],
|
||||
"size": [140, 76],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "PreviewAny",
|
||||
"pos": [951.0236206054688, 421.3861083984375],
|
||||
"size": [140, 76],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "source",
|
||||
"type": "*",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewAny"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "PrimitiveString",
|
||||
"pos": [575.1760864257812, 504.5214538574219],
|
||||
"size": [270, 58],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [2, 3]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PrimitiveString"
|
||||
},
|
||||
"widgets_values": ["foo"]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[2, 3, 0, 1, 0, "*"],
|
||||
[3, 3, 0, 4, 0, "*"]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"frontendVersion": "1.19.1",
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -160,4 +160,4 @@
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -43,10 +43,6 @@
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
},
|
||||
"groupNodes": {
|
||||
"group_node": {
|
||||
"nodes": [
|
||||
@@ -405,4 +401,4 @@
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -110,10 +110,6 @@
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
},
|
||||
"groupNodes": {
|
||||
"hello": {
|
||||
"nodes": [
|
||||
@@ -253,4 +249,4 @@
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -44,11 +44,6 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
||||
@@ -61,11 +61,6 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"id": "5635564e-189f-49e4-9b25-6b7634bcd595",
|
||||
"revision": 0,
|
||||
"last_node_id": 78,
|
||||
"last_link_id": 53,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 78,
|
||||
"type": "DevToolsNodeWithV2ComboInput",
|
||||
"pos": [1320, 904],
|
||||
"size": [270.3199157714844, 58],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "COMBO",
|
||||
"type": "COMBO",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DevToolsNodeWithV2ComboInput"
|
||||
},
|
||||
"widgets_values": ["A"]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"frontendVersion": "1.19.7"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -50,11 +50,6 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -38,11 +38,7 @@
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"groupNodes": {},
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
"groupNodes": {}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -54,11 +54,6 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
||||
@@ -92,14 +92,10 @@
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
},
|
||||
"VHS_latentpreview": true,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": false,
|
||||
"VHS_KeepIntermediate": false
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -84,10 +84,6 @@
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
},
|
||||
"reroutes": [
|
||||
{
|
||||
"id": 1,
|
||||
|
||||
@@ -106,7 +106,10 @@
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [0, 0]
|
||||
"offset": {
|
||||
"0": 0,
|
||||
"1": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
|
||||
@@ -368,10 +368,10 @@
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
149.9747408641311,
|
||||
383.8593224280729
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,5 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,4 @@
|
||||
"title": "Load Animated Image"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,6 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
||||
@@ -41,11 +41,6 @@
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
}
|
||||
@@ -54,11 +54,7 @@
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"frontendVersion": "1.17.0",
|
||||
"ds": {
|
||||
"offset": [0, 0],
|
||||
"scale": 1
|
||||
}
|
||||
"frontendVersion": "1.17.0"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
||||
@@ -18,27 +18,3 @@ test.describe('Execution', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Execute to selected output nodes', () => {
|
||||
test('Execute to selected output nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('execution/partial_execution')
|
||||
const input = await comfyPage.getNodeRefById(3)
|
||||
const output1 = await comfyPage.getNodeRefById(1)
|
||||
const output2 = await comfyPage.getNodeRefById(4)
|
||||
expect(await (await input.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('')
|
||||
expect(await (await output2.getWidget(0)).getValue()).toBe('')
|
||||
|
||||
await output1.click('title')
|
||||
|
||||
await comfyPage.executeCommand('Comfy.QueueSelectedOutputNodes')
|
||||
// @note: Wait for the execution to finish. We might want to move to a more
|
||||
// reliable way to wait for the execution to finish. Workflow in this test
|
||||
// is simple enough that this is fine for now.
|
||||
await comfyPage.page.waitForTimeout(200)
|
||||
|
||||
expect(await (await input.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output2.getWidget(0)).getValue()).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -666,12 +666,6 @@ test.describe('Load workflow', () => {
|
||||
expect(activeWorkflowName).toEqual(workflowPathB)
|
||||
})
|
||||
})
|
||||
|
||||
test('Auto fit view after loading workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.EnableWorkflowViewRestore', false)
|
||||
await comfyPage.loadWorkflow('single_ksampler')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler_fit.png')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Load duplicate workflow', () => {
|
||||
|
||||
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 48 KiB |
@@ -53,26 +53,6 @@ test.describe('Combo text widget', () => {
|
||||
const refreshedComboValues = await getComboValues()
|
||||
expect(refreshedComboValues).not.toEqual(initialComboValues)
|
||||
})
|
||||
|
||||
test('Should refresh combo values of nodes with v2 combo input spec', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('node_with_v2_combo_input')
|
||||
// click canvas to focus
|
||||
await comfyPage.page.mouse.click(400, 300)
|
||||
// press R to trigger refresh
|
||||
await comfyPage.page.keyboard.press('r')
|
||||
// wait for nodes' widgets to be updated
|
||||
await comfyPage.page.mouse.click(400, 300)
|
||||
await comfyPage.nextFrame()
|
||||
// get the combo widget's values
|
||||
const comboValues = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].graph.nodes
|
||||
.find((node) => node.title === 'Node With V2 Combo Input')
|
||||
.widgets.find((widget) => widget.name === 'combo_input').options.values
|
||||
})
|
||||
expect(comboValues).toEqual(['A', 'B'])
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Boolean widget', () => {
|
||||
|
||||
12
package-lock.json
generated
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.19.9",
|
||||
"version": "1.19.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.19.9",
|
||||
"version": "1.19.4",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.43",
|
||||
"@comfyorg/litegraph": "^0.15.9",
|
||||
"@comfyorg/litegraph": "^0.15.3",
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
@@ -482,9 +482,9 @@
|
||||
"license": "GPL-3.0-only"
|
||||
},
|
||||
"node_modules/@comfyorg/litegraph": {
|
||||
"version": "0.15.9",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.15.9.tgz",
|
||||
"integrity": "sha512-uMKUZx3VPLnpTOAcdvnppbJPQ/BL7Yt4flU2YeF+835r/59il+iUXCfnObpYmJj5jA4o89atpxByS8Jl1PM9vQ==",
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.15.3.tgz",
|
||||
"integrity": "sha512-YpHhNgV6a1bsi/NCuK/u5m9bgI3HK5qLXCLR2aKUJ4MPVfdILodqcbkKVBOb4li6KKUyg7plQ9GJpml6F2/fYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.19.9",
|
||||
"version": "1.19.4",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -72,7 +72,7 @@
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.43",
|
||||
"@comfyorg/litegraph": "^0.15.9",
|
||||
"@comfyorg/litegraph": "^0.15.3",
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
|
||||
@@ -32,8 +32,6 @@ describe('TreeExplorerTreeNode', () => {
|
||||
handleRename: () => {}
|
||||
} as RenderedTreeExplorerNode
|
||||
|
||||
const mockHandleEditLabel = vi.fn()
|
||||
|
||||
beforeAll(() => {
|
||||
// Create a Vue app instance for PrimeVuePrimeVue
|
||||
const app = createApp({})
|
||||
@@ -50,10 +48,7 @@ describe('TreeExplorerTreeNode', () => {
|
||||
props: { node: mockNode },
|
||||
global: {
|
||||
components: { EditableText, Badge },
|
||||
plugins: [createTestingPinia(), i18n],
|
||||
provide: {
|
||||
[InjectKeyHandleEditLabelFunction]: mockHandleEditLabel
|
||||
}
|
||||
plugins: [createTestingPinia(), i18n]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -77,10 +72,7 @@ describe('TreeExplorerTreeNode', () => {
|
||||
},
|
||||
global: {
|
||||
components: { EditableText, Badge, InputText },
|
||||
plugins: [createTestingPinia(), i18n, PrimeVue],
|
||||
provide: {
|
||||
[InjectKeyHandleEditLabelFunction]: mockHandleEditLabel
|
||||
}
|
||||
plugins: [createTestingPinia(), i18n, PrimeVue]
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,132 +1,95 @@
|
||||
<template>
|
||||
<div class="w-96 p-2 overflow-x-hidden">
|
||||
<ApiKeyForm
|
||||
v-if="showApiKeyForm"
|
||||
@back="showApiKeyForm = false"
|
||||
@success="onSuccess"
|
||||
/>
|
||||
<template v-else>
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 mb-8">
|
||||
<h1 class="text-2xl font-medium leading-normal my-0">
|
||||
{{ isSignIn ? t('auth.login.title') : t('auth.signup.title') }}
|
||||
</h1>
|
||||
<p class="text-base my-0">
|
||||
<span class="text-muted">{{
|
||||
isSignIn
|
||||
? t('auth.login.newUser')
|
||||
: t('auth.signup.alreadyHaveAccount')
|
||||
}}</span>
|
||||
<span
|
||||
class="ml-1 cursor-pointer text-blue-500"
|
||||
@click="toggleState"
|
||||
>{{
|
||||
isSignIn ? t('auth.login.signUp') : t('auth.signup.signIn')
|
||||
}}</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Message v-if="!isSecureContext" severity="warn" class="mb-4">
|
||||
{{ t('auth.login.insecureContextWarning') }}
|
||||
</Message>
|
||||
|
||||
<!-- Form -->
|
||||
<SignInForm v-if="isSignIn" @submit="signInWithEmail" />
|
||||
<template v-else>
|
||||
<Message v-if="userIsInChina" severity="warn" class="mb-4">
|
||||
{{ t('auth.signup.regionRestrictionChina') }}
|
||||
</Message>
|
||||
<SignUpForm v-else @submit="signUpWithEmail" />
|
||||
</template>
|
||||
|
||||
<!-- Divider -->
|
||||
<Divider align="center" layout="horizontal" class="my-8">
|
||||
<span class="text-muted">{{ t('auth.login.orContinueWith') }}</span>
|
||||
</Divider>
|
||||
|
||||
<!-- Social Login Buttons -->
|
||||
<div class="flex flex-col gap-6">
|
||||
<Button
|
||||
type="button"
|
||||
class="h-10"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="signInWithGoogle"
|
||||
>
|
||||
<i class="pi pi-google mr-2"></i>
|
||||
{{
|
||||
isSignIn
|
||||
? t('auth.login.loginWithGoogle')
|
||||
: t('auth.signup.signUpWithGoogle')
|
||||
}}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
class="h-10"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="signInWithGithub"
|
||||
>
|
||||
<i class="pi pi-github mr-2"></i>
|
||||
{{
|
||||
isSignIn
|
||||
? t('auth.login.loginWithGithub')
|
||||
: t('auth.signup.signUpWithGithub')
|
||||
}}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
class="h-10"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="showApiKeyForm = true"
|
||||
>
|
||||
<img
|
||||
src="/assets/images/comfy-logo-mono.svg"
|
||||
class="w-5 h-5 mr-2"
|
||||
alt="Comfy"
|
||||
/>
|
||||
{{ t('auth.login.useApiKey') }}
|
||||
</Button>
|
||||
<small class="text-muted text-center">
|
||||
{{ t('auth.apiKey.helpText') }}
|
||||
<a
|
||||
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('auth.apiKey.generateKey') }}
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Terms & Contact -->
|
||||
<p class="text-xs text-muted mt-8">
|
||||
{{ t('auth.login.termsText') }}
|
||||
<a
|
||||
href="https://www.comfy.org/terms-of-service"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('auth.login.termsLink') }}
|
||||
</a>
|
||||
{{ t('auth.login.andText') }}
|
||||
<a
|
||||
href="https://www.comfy.org/privacy"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('auth.login.privacyLink') }} </a
|
||||
>.
|
||||
{{ t('auth.login.questionsContactPrefix') }}
|
||||
<a href="mailto:hello@comfy.org" class="text-blue-500 cursor-pointer">
|
||||
hello@comfy.org</a
|
||||
>.
|
||||
<div class="w-96 p-2">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col gap-4 mb-8">
|
||||
<h1 class="text-2xl font-medium leading-normal my-0">
|
||||
{{ isSignIn ? t('auth.login.title') : t('auth.signup.title') }}
|
||||
</h1>
|
||||
<p class="text-base my-0">
|
||||
<span class="text-muted">{{
|
||||
isSignIn
|
||||
? t('auth.login.newUser')
|
||||
: t('auth.signup.alreadyHaveAccount')
|
||||
}}</span>
|
||||
<span class="ml-1 cursor-pointer text-blue-500" @click="toggleState">{{
|
||||
isSignIn ? t('auth.login.signUp') : t('auth.signup.signIn')
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Message v-if="!isSecureContext" severity="warn" class="mb-4">
|
||||
{{ t('auth.login.insecureContextWarning') }}
|
||||
</Message>
|
||||
|
||||
<!-- Form -->
|
||||
<SignInForm v-if="isSignIn" @submit="signInWithEmail" />
|
||||
<template v-else>
|
||||
<Message v-if="userIsInChina" severity="warn" class="mb-4">
|
||||
{{ t('auth.signup.regionRestrictionChina') }}
|
||||
</Message>
|
||||
<SignUpForm v-else @submit="signUpWithEmail" />
|
||||
</template>
|
||||
|
||||
<!-- Divider -->
|
||||
<Divider align="center" layout="horizontal" class="my-8">
|
||||
<span class="text-muted">{{ t('auth.login.orContinueWith') }}</span>
|
||||
</Divider>
|
||||
|
||||
<!-- Social Login Buttons -->
|
||||
<div class="flex flex-col gap-6">
|
||||
<Button
|
||||
type="button"
|
||||
class="h-10"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="signInWithGoogle"
|
||||
>
|
||||
<i class="pi pi-google mr-2"></i>
|
||||
{{
|
||||
isSignIn
|
||||
? t('auth.login.loginWithGoogle')
|
||||
: t('auth.signup.signUpWithGoogle')
|
||||
}}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
class="h-10"
|
||||
severity="secondary"
|
||||
outlined
|
||||
@click="signInWithGithub"
|
||||
>
|
||||
<i class="pi pi-github mr-2"></i>
|
||||
{{
|
||||
isSignIn
|
||||
? t('auth.login.loginWithGithub')
|
||||
: t('auth.signup.signUpWithGithub')
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
<!-- Terms & Contact -->
|
||||
<p class="text-xs text-muted mt-8">
|
||||
{{ t('auth.login.termsText') }}
|
||||
<a
|
||||
href="https://www.comfy.org/terms-of-service"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('auth.login.termsLink') }}
|
||||
</a>
|
||||
{{ t('auth.login.andText') }}
|
||||
<a
|
||||
href="https://www.comfy.org/privacy"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('auth.login.privacyLink') }} </a
|
||||
>.
|
||||
{{ t('auth.login.questionsContactPrefix') }}
|
||||
<a href="mailto:hello@comfy.org" class="text-blue-500 cursor-pointer">
|
||||
hello@comfy.org</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -137,12 +100,10 @@ import Message from 'primevue/message'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
|
||||
import { SignInData, SignUpData } from '@/schemas/signInSchema'
|
||||
import { useFirebaseAuthService } from '@/services/firebaseAuthService'
|
||||
import { isInChina } from '@/utils/networkUtil'
|
||||
|
||||
import ApiKeyForm from './signin/ApiKeyForm.vue'
|
||||
import SignInForm from './signin/SignInForm.vue'
|
||||
import SignUpForm from './signin/SignUpForm.vue'
|
||||
|
||||
@@ -154,11 +115,8 @@ const { t } = useI18n()
|
||||
const authService = useFirebaseAuthService()
|
||||
const isSecureContext = window.isSecureContext
|
||||
const isSignIn = ref(true)
|
||||
const showApiKeyForm = ref(false)
|
||||
|
||||
const toggleState = () => {
|
||||
isSignIn.value = !isSignIn.value
|
||||
showApiKeyForm.value = false
|
||||
}
|
||||
|
||||
const signInWithGoogle = async () => {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Tag from 'primevue/tag'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
@@ -21,16 +19,7 @@ describe('SettingItem', () => {
|
||||
const mountComponent = (props: any, options = {}): any => {
|
||||
return mount(SettingItem, {
|
||||
global: {
|
||||
plugins: [PrimeVue, i18n, createPinia()],
|
||||
components: {
|
||||
Tag
|
||||
},
|
||||
directives: {
|
||||
tooltip: Tooltip
|
||||
},
|
||||
stubs: {
|
||||
'i-material-symbols:experiment-outline': true
|
||||
}
|
||||
plugins: [PrimeVue, i18n, createPinia()]
|
||||
},
|
||||
props,
|
||||
...options
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
<h2 class="text-2xl font-bold mb-2">{{ $t('userSettings.title') }}</h2>
|
||||
<Divider class="mb-3" />
|
||||
|
||||
<!-- Normal User Panel -->
|
||||
<div v-if="isLoggedIn" class="flex flex-col gap-2">
|
||||
<div v-if="user" class="flex flex-col gap-2">
|
||||
<UserAvatar
|
||||
v-if="userPhotoUrl"
|
||||
:photo-url="userPhotoUrl"
|
||||
v-if="user.photoURL"
|
||||
:photo-url="user.photoURL"
|
||||
shape="circle"
|
||||
size="large"
|
||||
/>
|
||||
@@ -18,7 +17,7 @@
|
||||
{{ $t('userSettings.name') }}
|
||||
</h3>
|
||||
<div class="text-muted">
|
||||
{{ userDisplayName || $t('userSettings.notSet') }}
|
||||
{{ user.displayName || $t('userSettings.notSet') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,9 +25,9 @@
|
||||
<h3 class="font-medium">
|
||||
{{ $t('userSettings.email') }}
|
||||
</h3>
|
||||
<span class="text-muted">
|
||||
{{ userEmail }}
|
||||
</span>
|
||||
<a :href="'mailto:' + user.email" class="hover:underline">
|
||||
{{ user.email }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-0.5">
|
||||
@@ -91,22 +90,51 @@ import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import TabPanel from 'primevue/tabpanel'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import UserAvatar from '@/components/common/UserAvatar.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
|
||||
const dialogService = useDialogService()
|
||||
const {
|
||||
loading,
|
||||
isLoggedIn,
|
||||
isEmailProvider,
|
||||
userDisplayName,
|
||||
userEmail,
|
||||
userPhotoUrl,
|
||||
providerName,
|
||||
providerIcon,
|
||||
handleSignOut,
|
||||
handleSignIn
|
||||
} = useCurrentUser()
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const commandStore = useCommandStore()
|
||||
const user = computed(() => authStore.currentUser)
|
||||
const loading = computed(() => authStore.loading)
|
||||
|
||||
const providerName = computed(() => {
|
||||
const providerId = user.value?.providerData[0]?.providerId
|
||||
if (providerId?.includes('google')) {
|
||||
return 'Google'
|
||||
}
|
||||
if (providerId?.includes('github')) {
|
||||
return 'GitHub'
|
||||
}
|
||||
return providerId
|
||||
})
|
||||
|
||||
const providerIcon = computed(() => {
|
||||
const providerId = user.value?.providerData[0]?.providerId
|
||||
if (providerId?.includes('google')) {
|
||||
return 'pi pi-google'
|
||||
}
|
||||
if (providerId?.includes('github')) {
|
||||
return 'pi pi-github'
|
||||
}
|
||||
return 'pi pi-user'
|
||||
})
|
||||
|
||||
const isEmailProvider = computed(() => {
|
||||
const providerId = user.value?.providerData[0]?.providerId
|
||||
return providerId === 'password'
|
||||
})
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await commandStore.execute('Comfy.User.SignOut')
|
||||
}
|
||||
|
||||
const handleSignIn = async () => {
|
||||
await commandStore.execute('Comfy.User.OpenSignInDialog')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { Form } from '@primevue/forms'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import Button from 'primevue/button'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
|
||||
|
||||
import ApiKeyForm from './ApiKeyForm.vue'
|
||||
|
||||
const mockStoreApiKey = vi.fn()
|
||||
const mockLoading = vi.fn(() => false)
|
||||
|
||||
vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
useFirebaseAuthStore: vi.fn(() => ({
|
||||
loading: mockLoading()
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/apiKeyAuthStore', () => ({
|
||||
useApiKeyAuthStore: vi.fn(() => ({
|
||||
storeApiKey: mockStoreApiKey
|
||||
}))
|
||||
}))
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
auth: {
|
||||
apiKey: {
|
||||
title: 'API Key',
|
||||
label: 'API Key',
|
||||
placeholder: 'Enter your API Key',
|
||||
error: 'Invalid API Key',
|
||||
helpText: 'Need an API key?',
|
||||
generateKey: 'Get one here',
|
||||
whitelistInfo: 'About non-whitelisted sites',
|
||||
description: 'Use your Comfy API key to enable API Nodes'
|
||||
}
|
||||
},
|
||||
g: {
|
||||
back: 'Back',
|
||||
save: 'Save',
|
||||
learnMore: 'Learn more'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('ApiKeyForm', () => {
|
||||
beforeEach(() => {
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
vi.clearAllMocks()
|
||||
mockStoreApiKey.mockReset()
|
||||
mockLoading.mockReset()
|
||||
})
|
||||
|
||||
const mountComponent = (props: any = {}) => {
|
||||
return mount(ApiKeyForm, {
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
components: { Button, Form, InputText, Message }
|
||||
},
|
||||
props
|
||||
})
|
||||
}
|
||||
|
||||
it('renders correctly with all required elements', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
expect(wrapper.find('h1').text()).toBe('API Key')
|
||||
expect(wrapper.find('label').text()).toBe('API Key')
|
||||
expect(wrapper.findComponent(InputText).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(Button).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('emits back event when back button is clicked', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
await wrapper.findComponent(Button).trigger('click')
|
||||
expect(wrapper.emitted('back')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows loading state when submitting', async () => {
|
||||
mockLoading.mockReturnValue(true)
|
||||
const wrapper = mountComponent()
|
||||
const input = wrapper.findComponent(InputText)
|
||||
|
||||
await input.setValue(
|
||||
'comfyui-123456789012345678901234567890123456789012345678901234567890123456789012'
|
||||
)
|
||||
await wrapper.find('form').trigger('submit')
|
||||
|
||||
const submitButton = wrapper
|
||||
.findAllComponents(Button)
|
||||
.find((btn) => btn.text() === 'Save')
|
||||
expect(submitButton?.props('loading')).toBe(true)
|
||||
})
|
||||
|
||||
it('displays help text and links correctly', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const helpText = wrapper.find('small')
|
||||
expect(helpText.text()).toContain('Need an API key?')
|
||||
expect(helpText.find('a').attributes('href')).toBe(
|
||||
`${COMFY_PLATFORM_BASE_URL}/login`
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-4 mb-8">
|
||||
<h1 class="text-2xl font-medium leading-normal my-0">
|
||||
{{ t('auth.apiKey.title') }}
|
||||
</h1>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-base my-0 text-muted">
|
||||
{{ t('auth.apiKey.description') }}
|
||||
</p>
|
||||
<a
|
||||
href="https://docs.comfy.org/interface/user#logging-in-with-an-api-key"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('g.learnMore') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
v-slot="$form"
|
||||
class="flex flex-col gap-6"
|
||||
:resolver="zodResolver(apiKeySchema)"
|
||||
@submit="onSubmit"
|
||||
>
|
||||
<Message v-if="$form.apiKey?.invalid" severity="error" class="mb-4">
|
||||
{{ $form.apiKey.error.message }}
|
||||
</Message>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label
|
||||
class="opacity-80 text-base font-medium mb-2"
|
||||
for="comfy-org-api-key"
|
||||
>
|
||||
{{ t('auth.apiKey.label') }}
|
||||
</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<InputText
|
||||
pt:root:id="comfy-org-api-key"
|
||||
pt:root:autocomplete="off"
|
||||
class="h-10"
|
||||
name="apiKey"
|
||||
type="password"
|
||||
:placeholder="t('auth.apiKey.placeholder')"
|
||||
:invalid="$form.apiKey?.invalid"
|
||||
/>
|
||||
<small class="text-muted">
|
||||
{{ t('auth.apiKey.helpText') }}
|
||||
<a
|
||||
:href="`${COMFY_PLATFORM_BASE_URL}/login`"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('auth.apiKey.generateKey') }}
|
||||
</a>
|
||||
<span class="mx-1">•</span>
|
||||
<a
|
||||
href="https://docs.comfy.org/tutorials/api-nodes/overview#log-in-with-api-key-on-non-whitelisted-websites"
|
||||
target="_blank"
|
||||
class="text-blue-500 cursor-pointer"
|
||||
>
|
||||
{{ t('auth.apiKey.whitelistInfo') }}
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-4">
|
||||
<Button type="button" link @click="$emit('back')">
|
||||
{{ t('g.back') }}
|
||||
</Button>
|
||||
<Button type="submit" :loading="loading" :disabled="loading">
|
||||
{{ t('g.save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Form, FormSubmitEvent } from '@primevue/forms'
|
||||
import { zodResolver } from '@primevue/forms/resolvers/zod'
|
||||
import Button from 'primevue/button'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { COMFY_PLATFORM_BASE_URL } from '@/config/comfyApi'
|
||||
import { apiKeySchema } from '@/schemas/signInSchema'
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const apiKeyStore = useApiKeyAuthStore()
|
||||
const loading = computed(() => authStore.loading)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'back'): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
const onSubmit = async (event: FormSubmitEvent) => {
|
||||
if (event.valid) {
|
||||
await apiKeyStore.storeApiKey(event.values.apiKey)
|
||||
emit('success')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -16,12 +16,12 @@ import { computed, watch } from 'vue'
|
||||
|
||||
import DomWidget from '@/components/graph/widgets/DomWidget.vue'
|
||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||
import { type DomWidgetState, useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
|
||||
const domWidgetStore = useDomWidgetStore()
|
||||
const widgetStates = computed(() =>
|
||||
Array.from(domWidgetStore.widgetStates.values())
|
||||
const widgetStates = computed(
|
||||
() => Array.from(domWidgetStore.widgetStates.values()) as DomWidgetState[]
|
||||
)
|
||||
|
||||
const updateWidgets = () => {
|
||||
|
||||
@@ -225,6 +225,32 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
// Save the drag & scale info in the serialized workflow if the setting is enabled
|
||||
watch(
|
||||
[
|
||||
() => canvasStore.canvas,
|
||||
() => settingStore.get('Comfy.EnableWorkflowViewRestore')
|
||||
],
|
||||
([canvas, enableWorkflowViewRestore]) => {
|
||||
const extra = canvas?.graph?.extra
|
||||
if (!extra) return
|
||||
|
||||
if (enableWorkflowViewRestore) {
|
||||
extra.ds = {
|
||||
get scale() {
|
||||
return canvas.ds.scale
|
||||
},
|
||||
get offset() {
|
||||
const [x, y] = canvas.ds.offset
|
||||
return [x, y]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delete extra.ds
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
useEventListener(
|
||||
canvasRef,
|
||||
'litegraph:no-items-selected',
|
||||
@@ -267,7 +293,7 @@ onMounted(async () => {
|
||||
workspaceStore.spinner = true
|
||||
// ChangeTracker needs to be initialized before setup, as it will overwrite
|
||||
// some listeners of litegraph canvas.
|
||||
ChangeTracker.init()
|
||||
ChangeTracker.init(comfyApp)
|
||||
await loadCustomNodesI18n()
|
||||
try {
|
||||
await settingStore.loadSettingValues()
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
content: 'p-0 flex flex-row'
|
||||
}"
|
||||
>
|
||||
<ExecuteButton v-show="nodeSelected" />
|
||||
<ColorPickerButton v-show="nodeSelected || groupSelected" />
|
||||
<Button
|
||||
v-show="nodeSelected"
|
||||
@@ -75,7 +74,6 @@ import Panel from 'primevue/panel'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue'
|
||||
import ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue'
|
||||
import { useRefreshableSelection } from '@/composables/useRefreshableSelection'
|
||||
import { st, t } from '@/i18n'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<Button
|
||||
v-tooltip.top="{
|
||||
value: isDisabled
|
||||
? t('selectionToolbox.executeButton.disabledTooltip')
|
||||
: t('selectionToolbox.executeButton.tooltip'),
|
||||
showDelay: 1000
|
||||
}"
|
||||
:severity="isDisabled ? 'secondary' : 'success'"
|
||||
text
|
||||
:disabled="isDisabled"
|
||||
@mouseenter="() => handleMouseEnter()"
|
||||
@mouseleave="() => handleMouseLeave()"
|
||||
@click="handleClick"
|
||||
>
|
||||
<i-lucide:play />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import Button from 'primevue/button'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { isLGraphNode } from '@/utils/litegraphUtil'
|
||||
|
||||
const { t } = useI18n()
|
||||
const canvasStore = useCanvasStore()
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
const canvas = canvasStore.getCanvas()
|
||||
const buttonHovered = ref(false)
|
||||
const selectedOutputNodes = computed(
|
||||
() =>
|
||||
canvasStore.selectedItems.filter(
|
||||
(item) => isLGraphNode(item) && item.constructor.nodeData?.output_node
|
||||
) as LGraphNode[]
|
||||
)
|
||||
|
||||
const isDisabled = computed(() => selectedOutputNodes.value.length === 0)
|
||||
|
||||
function outputNodeStokeStyle(this: LGraphNode) {
|
||||
if (
|
||||
this.selected &&
|
||||
this.constructor.nodeData?.output_node &&
|
||||
buttonHovered.value
|
||||
) {
|
||||
return { color: 'orange', lineWidth: 2, padding: 10 }
|
||||
}
|
||||
}
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
buttonHovered.value = true
|
||||
for (const node of selectedOutputNodes.value) {
|
||||
node.strokeStyles['outputNode'] = outputNodeStokeStyle
|
||||
}
|
||||
canvas.setDirty(true)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
buttonHovered.value = false
|
||||
canvas.setDirty(true)
|
||||
}
|
||||
|
||||
const handleClick = async () => {
|
||||
await commandStore.execute('Comfy.QueueSelectedOutputNodes')
|
||||
}
|
||||
</script>
|
||||
@@ -1,53 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative w-full text-xs min-h-[28px] max-h-[200px] rounded-lg px-4 py-2 overflow-y-auto"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 break-all flex items-center gap-2">
|
||||
<span v-html="formattedText"></span>
|
||||
<Skeleton v-if="isParentNodeExecuting" class="!flex-1 !h-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NodeId } from '@comfyorg/litegraph'
|
||||
import Skeleton from 'primevue/skeleton'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { linkifyHtml, nl2br } from '@/utils/formatUtil'
|
||||
|
||||
const modelValue = defineModel<string>({ required: true })
|
||||
defineProps<{
|
||||
widget?: object
|
||||
}>()
|
||||
|
||||
const executionStore = useExecutionStore()
|
||||
const isParentNodeExecuting = ref(true)
|
||||
const formattedText = computed(() => nl2br(linkifyHtml(modelValue.value)))
|
||||
|
||||
let executingNodeId: NodeId | null = null
|
||||
onMounted(() => {
|
||||
executingNodeId = executionStore.executingNodeId
|
||||
})
|
||||
|
||||
// Watch for either a new node has starting execution or overall execution ending
|
||||
const stopWatching = watch(
|
||||
[() => executionStore.executingNode, () => executionStore.isIdle],
|
||||
() => {
|
||||
if (
|
||||
executionStore.isIdle ||
|
||||
(executionStore.executingNode &&
|
||||
executionStore.executingNode.id !== executingNodeId)
|
||||
) {
|
||||
isParentNodeExecuting.value = false
|
||||
stopWatching()
|
||||
}
|
||||
if (!executingNodeId) {
|
||||
executingNodeId = executionStore.executingNodeId
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
@@ -75,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { IWidget, LGraphNode } from '@comfyorg/litegraph'
|
||||
import { Tooltip } from 'primevue'
|
||||
import Button from 'primevue/button'
|
||||
|
||||
@@ -101,13 +101,13 @@ const emit = defineEmits<{
|
||||
const resizeNodeMatchOutput = () => {
|
||||
console.log('resizeNodeMatchOutput')
|
||||
|
||||
const outputWidth = node.widgets?.find((w) => w.name === 'width')
|
||||
const outputHeight = node.widgets?.find((w) => w.name === 'height')
|
||||
const outputWidth = node.widgets?.find((w: IWidget) => w.name === 'width')
|
||||
const outputHeight = node.widgets?.find((w: IWidget) => w.name === 'height')
|
||||
|
||||
if (outputWidth && outputHeight && outputHeight.value && outputWidth.value) {
|
||||
const [oldWidth, oldHeight] = node.size
|
||||
|
||||
const scene = node.widgets?.find((w) => w.name === 'image')
|
||||
const scene = node.widgets?.find((w: IWidget) => w.name === 'image')
|
||||
|
||||
const sceneHeight = scene?.computedHeight
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useExtensionStore } from '@/stores/extensionStore'
|
||||
import { ResultItemImpl } from '@/stores/queueStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
@@ -17,16 +16,9 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const { isExtensionInstalled, isExtensionEnabled } = useExtensionStore()
|
||||
|
||||
const vhsAdvancedPreviews = computed(() => {
|
||||
return (
|
||||
isExtensionInstalled('VideoHelperSuite.Core') &&
|
||||
isExtensionEnabled('VideoHelperSuite.Core') &&
|
||||
settingStore.get('VHS.AdvancedPreviews') &&
|
||||
settingStore.get('VHS.AdvancedPreviews') !== 'Never'
|
||||
)
|
||||
})
|
||||
const vhsAdvancedPreviews = computed(() =>
|
||||
settingStore.get('VHS.AdvancedPreviews')
|
||||
)
|
||||
|
||||
const url = computed(() =>
|
||||
vhsAdvancedPreviews.value
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<Button
|
||||
v-if="isLoggedIn"
|
||||
v-if="isAuthenticated"
|
||||
class="user-profile-button p-1"
|
||||
severity="secondary"
|
||||
text
|
||||
@@ -30,14 +30,15 @@ import Popover from 'primevue/popover'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import UserAvatar from '@/components/common/UserAvatar.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
|
||||
import CurrentUserPopover from './CurrentUserPopover.vue'
|
||||
|
||||
const { isLoggedIn, userPhotoUrl } = useCurrentUser()
|
||||
const authStore = useFirebaseAuthStore()
|
||||
|
||||
const popover = ref<InstanceType<typeof Popover> | null>(null)
|
||||
const isAuthenticated = computed(() => authStore.isAuthenticated)
|
||||
const photoURL = computed<string | undefined>(
|
||||
() => userPhotoUrl.value ?? undefined
|
||||
() => authStore.currentUser?.photoURL ?? undefined
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
<div class="flex flex-col items-center">
|
||||
<UserAvatar
|
||||
class="mb-3"
|
||||
:photo-url="userPhotoUrl"
|
||||
:photo-url="user?.photoURL"
|
||||
:pt:icon:class="{
|
||||
'!text-2xl': !userPhotoUrl
|
||||
'!text-2xl': !user?.photoURL
|
||||
}"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<!-- User Details -->
|
||||
<h3 class="text-lg font-semibold truncate my-0 mb-1">
|
||||
{{ userDisplayName || $t('g.user') }}
|
||||
{{ user?.displayName || $t('g.user') }}
|
||||
</h3>
|
||||
<p v-if="userEmail" class="text-sm text-muted truncate my-0">
|
||||
{{ userEmail }}
|
||||
<p v-if="user?.email" class="text-sm text-muted truncate my-0">
|
||||
{{ user.email }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,18 +37,6 @@
|
||||
|
||||
<Divider class="my-2" />
|
||||
|
||||
<Button
|
||||
class="justify-start"
|
||||
:label="$t('credits.apiPricing')"
|
||||
icon="pi pi-external-link"
|
||||
text
|
||||
fluid
|
||||
severity="secondary"
|
||||
@click="handleOpenApiPricing"
|
||||
/>
|
||||
|
||||
<Divider class="my-2" />
|
||||
|
||||
<div class="w-full flex flex-col gap-2 p-2">
|
||||
<div class="text-muted text-sm">
|
||||
{{ $t('credits.yourCreditBalance') }}
|
||||
@@ -64,18 +52,20 @@
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import { onMounted } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
|
||||
import UserAvatar from '@/components/common/UserAvatar.vue'
|
||||
import UserCredit from '@/components/common/UserCredit.vue'
|
||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useFirebaseAuthService } from '@/services/firebaseAuthService'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
|
||||
const { userDisplayName, userEmail, userPhotoUrl } = useCurrentUser()
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const authService = useFirebaseAuthService()
|
||||
const dialogService = useDialogService()
|
||||
|
||||
const user = computed(() => authStore.currentUser)
|
||||
|
||||
const handleOpenUserSettings = () => {
|
||||
dialogService.showSettingsDialog('user')
|
||||
}
|
||||
@@ -84,10 +74,6 @@ const handleTopUp = () => {
|
||||
dialogService.showTopUpCreditsDialog()
|
||||
}
|
||||
|
||||
const handleOpenApiPricing = () => {
|
||||
window.open('https://docs.comfy.org/tutorials/api-nodes/pricing', '_blank')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void authService.fetchBalance()
|
||||
})
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
|
||||
export const useCurrentUser = () => {
|
||||
const authStore = useFirebaseAuthStore()
|
||||
const commandStore = useCommandStore()
|
||||
const apiKeyStore = useApiKeyAuthStore()
|
||||
|
||||
const firebaseUser = computed(() => authStore.currentUser)
|
||||
const isApiKeyLogin = computed(() => apiKeyStore.isAuthenticated)
|
||||
const isLoggedIn = computed(
|
||||
() => !!isApiKeyLogin.value || firebaseUser.value !== null
|
||||
)
|
||||
|
||||
const userDisplayName = computed(() => {
|
||||
if (isApiKeyLogin.value) {
|
||||
return apiKeyStore.currentUser?.name
|
||||
}
|
||||
return firebaseUser.value?.displayName
|
||||
})
|
||||
|
||||
const userEmail = computed(() => {
|
||||
if (isApiKeyLogin.value) {
|
||||
return apiKeyStore.currentUser?.email
|
||||
}
|
||||
return firebaseUser.value?.email
|
||||
})
|
||||
|
||||
const providerName = computed(() => {
|
||||
if (isApiKeyLogin.value) {
|
||||
return 'Comfy API Key'
|
||||
}
|
||||
|
||||
const providerId = firebaseUser.value?.providerData[0]?.providerId
|
||||
if (providerId?.includes('google')) {
|
||||
return 'Google'
|
||||
}
|
||||
if (providerId?.includes('github')) {
|
||||
return 'GitHub'
|
||||
}
|
||||
return providerId
|
||||
})
|
||||
|
||||
const providerIcon = computed(() => {
|
||||
if (isApiKeyLogin.value) {
|
||||
return 'pi pi-key'
|
||||
}
|
||||
|
||||
const providerId = firebaseUser.value?.providerData[0]?.providerId
|
||||
if (providerId?.includes('google')) {
|
||||
return 'pi pi-google'
|
||||
}
|
||||
if (providerId?.includes('github')) {
|
||||
return 'pi pi-github'
|
||||
}
|
||||
return 'pi pi-user'
|
||||
})
|
||||
|
||||
const isEmailProvider = computed(() => {
|
||||
if (isApiKeyLogin.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
const providerId = firebaseUser.value?.providerData[0]?.providerId
|
||||
return providerId === 'password'
|
||||
})
|
||||
|
||||
const userPhotoUrl = computed(() => {
|
||||
if (isApiKeyLogin.value) return null
|
||||
return firebaseUser.value?.photoURL
|
||||
})
|
||||
|
||||
const handleSignOut = async () => {
|
||||
if (isApiKeyLogin.value) {
|
||||
await apiKeyStore.clearStoredApiKey()
|
||||
} else {
|
||||
await commandStore.execute('Comfy.User.SignOut')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSignIn = async () => {
|
||||
await commandStore.execute('Comfy.User.OpenSignInDialog')
|
||||
}
|
||||
|
||||
return {
|
||||
loading: authStore.loading,
|
||||
isLoggedIn,
|
||||
isApiKeyLogin,
|
||||
isEmailProvider,
|
||||
userDisplayName,
|
||||
userEmail,
|
||||
userPhotoUrl,
|
||||
providerName,
|
||||
providerIcon,
|
||||
handleSignOut,
|
||||
handleSignIn
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { LGraphNode } from '@comfyorg/litegraph'
|
||||
|
||||
import { useTextPreviewWidget } from '@/composables/widgets/useProgressTextWidget'
|
||||
|
||||
const TEXT_PREVIEW_WIDGET_NAME = '$$node-text-preview'
|
||||
|
||||
/**
|
||||
* Composable for handling node text previews
|
||||
*/
|
||||
export function useNodeProgressText() {
|
||||
const textPreviewWidget = useTextPreviewWidget()
|
||||
|
||||
const findTextPreviewWidget = (node: LGraphNode) =>
|
||||
node.widgets?.find((w) => w.name === TEXT_PREVIEW_WIDGET_NAME)
|
||||
|
||||
const addTextPreviewWidget = (node: LGraphNode) =>
|
||||
textPreviewWidget(node, {
|
||||
name: TEXT_PREVIEW_WIDGET_NAME,
|
||||
type: 'progressText'
|
||||
})
|
||||
|
||||
/**
|
||||
* Shows text preview for a node
|
||||
* @param node The graph node to show the preview for
|
||||
*/
|
||||
function showTextPreview(node: LGraphNode, text: string) {
|
||||
const widget = findTextPreviewWidget(node) ?? addTextPreviewWidget(node)
|
||||
widget.value = text
|
||||
node.setDirtyCanvas?.(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes text preview from a node
|
||||
* @param node The graph node to remove the preview from
|
||||
*/
|
||||
function removeTextPreview(node: LGraphNode) {
|
||||
if (!node.widgets) return
|
||||
|
||||
const widgetIdx = node.widgets.findIndex(
|
||||
(w) => w.name === TEXT_PREVIEW_WIDGET_NAME
|
||||
)
|
||||
|
||||
if (widgetIdx > -1) {
|
||||
node.widgets[widgetIdx].onRemove?.()
|
||||
node.widgets.splice(widgetIdx, 1)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
showTextPreview,
|
||||
removeTextPreview
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,13 @@ import {
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import { SettingTreeNode, useSettingStore } from '@/stores/settingStore'
|
||||
import type { SettingParams } from '@/types/settingTypes'
|
||||
import { isElectron } from '@/utils/envUtil'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { buildTree } from '@/utils/treeUtil'
|
||||
|
||||
import { useCurrentUser } from '../auth/useCurrentUser'
|
||||
|
||||
interface SettingPanelItem {
|
||||
node: SettingTreeNode
|
||||
component: Component
|
||||
@@ -30,7 +29,7 @@ export function useSettingUI(
|
||||
| 'credits'
|
||||
) {
|
||||
const { t } = useI18n()
|
||||
const { isLoggedIn } = useCurrentUser()
|
||||
const firebaseAuthStore = useFirebaseAuthStore()
|
||||
const settingStore = useSettingStore()
|
||||
const activeCategory = ref<SettingTreeNode | null>(null)
|
||||
|
||||
@@ -166,7 +165,7 @@ export function useSettingUI(
|
||||
label: 'Account',
|
||||
children: [
|
||||
userPanel.node,
|
||||
...(isLoggedIn.value ? [creditsPanel.node] : [])
|
||||
...(firebaseAuthStore.isAuthenticated ? [creditsPanel.node] : [])
|
||||
].map(translateCategory)
|
||||
},
|
||||
// Normal settings stored in the settingStore
|
||||
|
||||
@@ -312,28 +312,6 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
await app.queuePrompt(-1, batchCount)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.QueueSelectedOutputNodes',
|
||||
icon: 'pi pi-play',
|
||||
label: 'Queue Selected Output Nodes',
|
||||
versionAdded: '1.19.6',
|
||||
function: async () => {
|
||||
const batchCount = useQueueSettingsStore().batchCount
|
||||
const queueNodeIds = getSelectedNodes()
|
||||
.filter((node) => node.constructor.nodeData?.output_node)
|
||||
.map((node) => node.id)
|
||||
if (queueNodeIds.length === 0) {
|
||||
toastStore.add({
|
||||
severity: 'error',
|
||||
summary: t('toastMessages.nothingToQueue'),
|
||||
detail: t('toastMessages.pleaseSelectOutputNodes'),
|
||||
life: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
await app.queuePrompt(0, batchCount, queueNodeIds)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.ShowSettingsDialog',
|
||||
icon: 'pi pi-cog',
|
||||
|
||||
@@ -128,10 +128,4 @@ export const useLitegraphSettings = () => {
|
||||
'LiteGraph.Pointer.TrackpadGestures'
|
||||
)
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
LiteGraph.saveViewportWithGraph = settingStore.get(
|
||||
'Comfy.EnableWorkflowViewRestore'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import type { IWidget, LGraphNode } from '@comfyorg/litegraph'
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
@@ -9,11 +8,9 @@ interface RefreshableItem {
|
||||
refresh: () => Promise<void> | void
|
||||
}
|
||||
|
||||
type RefreshableWidget = IBaseWidget & RefreshableItem
|
||||
type RefreshableWidget = IWidget & RefreshableItem
|
||||
|
||||
const isRefreshableWidget = (
|
||||
widget: IBaseWidget
|
||||
): widget is RefreshableWidget =>
|
||||
const isRefreshableWidget = (widget: IWidget): widget is RefreshableWidget =>
|
||||
'refresh' in widget && typeof widget.refresh === 'function'
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,14 @@ import type { ISerialisedGraph } from '@comfyorg/litegraph/dist/types/serialisat
|
||||
import type { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
|
||||
import { validateComfyWorkflow } from '@/schemas/comfyWorkflowSchema'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { fixBadLinks } from '@/utils/linkFixer'
|
||||
import { WorkflowLinkFixer } from '@/utils/linkFixer'
|
||||
|
||||
export interface ValidationResult {
|
||||
graphData: ComfyWorkflowJSON | null
|
||||
linksFixes?: {
|
||||
patched: number
|
||||
deleted: number
|
||||
}
|
||||
}
|
||||
|
||||
export function useWorkflowValidation() {
|
||||
@@ -15,26 +19,26 @@ export function useWorkflowValidation() {
|
||||
function tryFixLinks(
|
||||
graphData: ComfyWorkflowJSON,
|
||||
options: { silent?: boolean } = {}
|
||||
) {
|
||||
): ComfyWorkflowJSON | null {
|
||||
const { silent = false } = options
|
||||
|
||||
// Collect all logs in an array
|
||||
const logs: string[] = []
|
||||
// Then validate and fix links if schema validation passed
|
||||
const linkValidation = fixBadLinks(
|
||||
graphData as unknown as ISerialisedGraph,
|
||||
{
|
||||
fix: true,
|
||||
silent,
|
||||
logger: {
|
||||
log: (message: string) => {
|
||||
logs.push(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
const fixer = WorkflowLinkFixer.create(
|
||||
graphData as unknown as ISerialisedGraph
|
||||
)
|
||||
fixer.logger = {
|
||||
log: (...args: any[]) => {
|
||||
logs.push(...args)
|
||||
}
|
||||
}
|
||||
const checkBadLinksResult = fixer.check()
|
||||
// Graph has no bad links, so we can return it as is
|
||||
if (!checkBadLinksResult.hasBadLinks) {
|
||||
return graphData
|
||||
}
|
||||
|
||||
if (!silent && logs.length > 0) {
|
||||
if (!silent) {
|
||||
toastStore.add({
|
||||
severity: 'warn',
|
||||
summary: 'Workflow Validation',
|
||||
@@ -42,18 +46,17 @@ export function useWorkflowValidation() {
|
||||
})
|
||||
}
|
||||
|
||||
// If links were fixed, notify the user
|
||||
if (linkValidation.fixed) {
|
||||
const fixBadLinksResult = fixer.fix()
|
||||
if (!fixBadLinksResult.hasBadLinks) {
|
||||
if (!silent) {
|
||||
toastStore.add({
|
||||
severity: 'success',
|
||||
summary: 'Workflow Links Fixed',
|
||||
detail: `Fixed ${linkValidation.patched} node connections and removed ${linkValidation.deleted} invalid links.`
|
||||
summary: 'Workflow Links Fixed'
|
||||
})
|
||||
}
|
||||
return fixBadLinksResult.graph as unknown as ComfyWorkflowJSON
|
||||
}
|
||||
|
||||
return linkValidation.graph as unknown as ComfyWorkflowJSON
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +86,7 @@ export function useWorkflowValidation() {
|
||||
try {
|
||||
validatedData = tryFixLinks(validatedGraphData, { silent })
|
||||
} catch (err) {
|
||||
// Link fixer itself is throwing an error
|
||||
// Link fixer itself is throwing an error. Log it and return the original graph
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,7 @@ export const useFloatWidget = () => {
|
||||
Math.max(0, -Math.floor(Math.log10(step)))
|
||||
const enableRounding = !settingStore.get('Comfy.DisableFloatRounding')
|
||||
|
||||
/** Assertion {@link inputSpec.default} */
|
||||
const defaultValue = (inputSpec.default as number | undefined) ?? 0
|
||||
const defaultValue = inputSpec.default ?? 0
|
||||
return node.addWidget(
|
||||
widgetType,
|
||||
inputSpec.name,
|
||||
|
||||
@@ -55,8 +55,7 @@ export const useIntWidget = () => {
|
||||
: 'number'
|
||||
|
||||
const step = inputSpec.step ?? 1
|
||||
/** Assertion {@link inputSpec.default} */
|
||||
const defaultValue = (inputSpec.default as number | undefined) ?? 0
|
||||
const defaultValue = inputSpec.default ?? 0
|
||||
const widget = node.addWidget(
|
||||
widgetType,
|
||||
inputSpec.name,
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import TextPreviewWidget from '@/components/graph/widgets/TextPreviewWidget.vue'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
|
||||
import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
|
||||
|
||||
const PADDING = 16
|
||||
|
||||
export const useTextPreviewWidget = () => {
|
||||
const widgetConstructor: ComfyWidgetConstructorV2 = (
|
||||
node: LGraphNode,
|
||||
inputSpec: InputSpec
|
||||
) => {
|
||||
const widgetValue = ref<string>('')
|
||||
const widget = new ComponentWidgetImpl<string | object>({
|
||||
node,
|
||||
name: inputSpec.name,
|
||||
component: TextPreviewWidget,
|
||||
inputSpec,
|
||||
options: {
|
||||
getValue: () => widgetValue.value,
|
||||
setValue: (value: string | object) => {
|
||||
widgetValue.value = typeof value === 'string' ? value : String(value)
|
||||
},
|
||||
getMinHeight: () => 42 + PADDING
|
||||
}
|
||||
})
|
||||
addWidget(node, widget)
|
||||
return widget
|
||||
}
|
||||
|
||||
return widgetConstructor
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
export const COMFY_API_BASE_URL = __USE_PROD_CONFIG__
|
||||
? 'https://api.comfy.org'
|
||||
: 'https://stagingapi.comfy.org'
|
||||
|
||||
export const COMFY_PLATFORM_BASE_URL = __USE_PROD_CONFIG__
|
||||
? 'https://platform.comfy.org'
|
||||
: 'https://stagingplatform.comfy.org'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LGraphCanvas, LiteGraph, isComboWidget } from '@comfyorg/litegraph'
|
||||
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
|
||||
|
||||
import { app } from '../../scripts/app'
|
||||
|
||||
@@ -33,10 +33,10 @@ const ext = {
|
||||
const clickedComboValue = currentNode?.widgets
|
||||
?.filter(
|
||||
(w) =>
|
||||
isComboWidget(w) && w.options.values?.length === values.length
|
||||
w.type === 'combo' && w.options.values?.length === values.length
|
||||
)
|
||||
.find((w) =>
|
||||
// @ts-expect-error Poorly typed; filter above "should" mitigate exceptions
|
||||
// @ts-ignore Poorly typed; filter above "should" mitigate exceptions
|
||||
w.options.values?.every((v, i) => v === values[i])
|
||||
)?.value
|
||||
|
||||
|
||||
@@ -797,7 +797,7 @@ export class GroupNodeConfig {
|
||||
|
||||
export class GroupNodeHandler {
|
||||
node: LGraphNode
|
||||
groupData: any
|
||||
groupData
|
||||
innerNodes: any
|
||||
|
||||
constructor(node: LGraphNode) {
|
||||
@@ -1231,6 +1231,7 @@ export class GroupNodeHandler {
|
||||
const widgetName = self.groupData.oldToNewWidgetMap[n][w]
|
||||
const widget = this.widgets.find((w) => w.name === widgetName)
|
||||
if (widget) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
widget.type = 'hidden'
|
||||
widget.computeSize = () => [0, -4]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type {
|
||||
IComboWidget,
|
||||
IStringWidget
|
||||
} from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import { IWidget } from '@comfyorg/litegraph'
|
||||
import { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
@@ -118,8 +116,8 @@ useExtensionService().registerExtension({
|
||||
fileInput.onchange = async () => {
|
||||
if (fileInput.files?.length) {
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w) => w.name === 'model_file'
|
||||
) as IComboWidget & { options: { values: string[] } }
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
) as IStringWidget
|
||||
|
||||
node.properties['Texture'] = undefined
|
||||
|
||||
@@ -141,6 +139,7 @@ useExtensionService().registerExtension({
|
||||
|
||||
if (uploadPath && modelWidget) {
|
||||
if (!modelWidget.options?.values?.includes(uploadPath)) {
|
||||
// @ts-ignore Fails due to earlier type-assertion of IStringWidget
|
||||
modelWidget.options?.values?.push(uploadPath)
|
||||
}
|
||||
|
||||
@@ -156,7 +155,9 @@ useExtensionService().registerExtension({
|
||||
node.addWidget('button', 'clear', 'clear', () => {
|
||||
useLoad3dService().getLoad3d(node)?.clearModel()
|
||||
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
if (modelWidget) {
|
||||
modelWidget.value = ''
|
||||
|
||||
@@ -202,10 +203,12 @@ useExtensionService().registerExtension({
|
||||
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||
const width = node.widgets?.find((w) => w.name === 'width')
|
||||
const height = node.widgets?.find((w) => w.name === 'height')
|
||||
const sceneWidget = node.widgets?.find((w) => w.name === 'image')
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
const width = node.widgets?.find((w: IWidget) => w.name === 'width')
|
||||
const height = node.widgets?.find((w: IWidget) => w.name === 'height')
|
||||
const sceneWidget = node.widgets?.find((w: IWidget) => w.name === 'image')
|
||||
|
||||
if (modelWidget && width && height && sceneWidget) {
|
||||
config.configure('input', modelWidget, cameraState, width, height)
|
||||
@@ -273,7 +276,7 @@ useExtensionService().registerExtension({
|
||||
fileInput.onchange = async () => {
|
||||
if (fileInput.files?.length) {
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w) => w.name === 'model_file'
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
) as IStringWidget
|
||||
|
||||
const uploadPath = await Load3dUtils.uploadFile(
|
||||
@@ -294,6 +297,7 @@ useExtensionService().registerExtension({
|
||||
|
||||
if (uploadPath && modelWidget) {
|
||||
if (!modelWidget.options?.values?.includes(uploadPath)) {
|
||||
// @ts-ignore Fails due to earlier type-assertion of IStringWidget
|
||||
modelWidget.options?.values?.push(uploadPath)
|
||||
}
|
||||
|
||||
@@ -309,7 +313,9 @@ useExtensionService().registerExtension({
|
||||
node.addWidget('button', 'clear', 'clear', () => {
|
||||
useLoad3dService().getLoad3d(node)?.clearModel()
|
||||
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
if (modelWidget) {
|
||||
modelWidget.value = ''
|
||||
}
|
||||
@@ -346,16 +352,18 @@ useExtensionService().registerExtension({
|
||||
|
||||
await nextTick()
|
||||
|
||||
const sceneWidget = node.widgets?.find((w) => w.name === 'image')
|
||||
const sceneWidget = node.widgets?.find((w: IWidget) => w.name === 'image')
|
||||
|
||||
const load3d = useLoad3dService().getLoad3d(node) as Load3dAnimation
|
||||
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
|
||||
let cameraState = node.properties['Camera Info']
|
||||
|
||||
const width = node.widgets?.find((w) => w.name === 'width')
|
||||
const height = node.widgets?.find((w) => w.name === 'height')
|
||||
const width = node.widgets?.find((w: IWidget) => w.name === 'width')
|
||||
const height = node.widgets?.find((w: IWidget) => w.name === 'height')
|
||||
|
||||
if (modelWidget && width && height && sceneWidget && load3d) {
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
@@ -471,7 +479,9 @@ useExtensionService().registerExtension({
|
||||
|
||||
let cameraState = message.result[1]
|
||||
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
|
||||
if (load3d && modelWidget) {
|
||||
modelWidget.value = filePath.replaceAll('\\', '/')
|
||||
@@ -545,7 +555,9 @@ useExtensionService().registerExtension({
|
||||
|
||||
const load3d = useLoad3dService().getLoad3d(node)
|
||||
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||
const modelWidget = node.widgets?.find(
|
||||
(w: IWidget) => w.name === 'model_file'
|
||||
)
|
||||
if (load3d && modelWidget) {
|
||||
modelWidget.value = filePath.replaceAll('\\', '/')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import type { IWidget } from '@comfyorg/litegraph'
|
||||
|
||||
import Load3d from '@/extensions/core/load3d/Load3d'
|
||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||
@@ -15,20 +15,17 @@ class Load3DConfiguration {
|
||||
|
||||
configure(
|
||||
loadFolder: 'input' | 'output',
|
||||
modelWidget: IBaseWidget,
|
||||
modelWidget: IWidget,
|
||||
cameraState?: any,
|
||||
width: IBaseWidget | null = null,
|
||||
height: IBaseWidget | null = null
|
||||
width: IWidget | null = null,
|
||||
height: IWidget | null = null
|
||||
) {
|
||||
this.setupModelHandling(modelWidget, loadFolder, cameraState)
|
||||
this.setupTargetSize(width, height)
|
||||
this.setupDefaultProperties()
|
||||
}
|
||||
|
||||
private setupTargetSize(
|
||||
width: IBaseWidget | null,
|
||||
height: IBaseWidget | null
|
||||
) {
|
||||
private setupTargetSize(width: IWidget | null, height: IWidget | null) {
|
||||
if (width && height) {
|
||||
this.load3d.setTargetSize(width.value as number, height.value as number)
|
||||
|
||||
@@ -54,7 +51,7 @@ class Load3DConfiguration {
|
||||
}
|
||||
|
||||
private setupModelHandling(
|
||||
modelWidget: IBaseWidget,
|
||||
modelWidget: IWidget,
|
||||
loadFolder: 'input' | 'output',
|
||||
cameraState?: any
|
||||
) {
|
||||
|
||||
@@ -401,7 +401,6 @@ app.registerExtension({
|
||||
// @ts-expect-error
|
||||
data.groupNodes = {}
|
||||
}
|
||||
if (nodeData == null) throw new TypeError('nodeData is not set')
|
||||
// @ts-expect-error
|
||||
data.groupNodes[nodeData.name] = groupData
|
||||
// @ts-expect-error
|
||||
|
||||
@@ -3,6 +3,8 @@ Preview Any - original implement from
|
||||
https://github.com/rgthree/rgthree-comfy/blob/main/py/display_any.py
|
||||
upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes
|
||||
*/
|
||||
import { IWidget } from '@comfyorg/litegraph'
|
||||
|
||||
import { DOMWidget } from '@/scripts/domWidget'
|
||||
import { ComfyWidgets } from '@/scripts/widgets'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
@@ -35,7 +37,9 @@ useExtensionService().registerExtension({
|
||||
? void 0
|
||||
: onExecuted.apply(this, [message])
|
||||
|
||||
const previewWidget = this.widgets?.find((w) => w.name === 'preview')
|
||||
const previewWidget = this.widgets?.find(
|
||||
(w: IWidget) => w.name === 'preview'
|
||||
)
|
||||
|
||||
if (previewWidget) {
|
||||
previewWidget.value = message.text[0]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IWidget } from '@comfyorg/litegraph'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import Load3D from '@/components/load3d/Load3D.vue'
|
||||
@@ -60,7 +61,7 @@ useExtensionService().registerExtension({
|
||||
|
||||
const load3d = useLoad3dService().getLoad3d(node)
|
||||
|
||||
const modelWidget = node.widgets?.find((w) => w.name === 'image')
|
||||
const modelWidget = node.widgets?.find((w: IWidget) => w.name === 'image')
|
||||
|
||||
if (load3d && modelWidget) {
|
||||
const filePath = fileInfo['subfolder'] + '/' + fileInfo['filename']
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import type { IWidget, LGraphNode } from '@comfyorg/litegraph'
|
||||
import type { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
|
||||
import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop'
|
||||
@@ -90,13 +90,7 @@ app.registerExtension({
|
||||
name: 'Comfy.AudioWidget',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (
|
||||
[
|
||||
'LoadAudio',
|
||||
'SaveAudio',
|
||||
'PreviewAudio',
|
||||
'SaveAudioMP3',
|
||||
'SaveAudioOpus'
|
||||
].includes(
|
||||
['LoadAudio', 'SaveAudio', 'PreviewAudio'].includes(
|
||||
// @ts-expect-error fixme ts strict error
|
||||
nodeType.prototype.comfyClass
|
||||
)
|
||||
@@ -117,10 +111,7 @@ app.registerExtension({
|
||||
node.addDOMWidget(inputName, /* name=*/ 'audioUI', audio)
|
||||
audioUIWidget.serialize = false
|
||||
|
||||
const { nodeData } = node.constructor
|
||||
if (nodeData == null) throw new TypeError('nodeData is null')
|
||||
|
||||
const isOutputNode = nodeData.output_node
|
||||
const isOutputNode = node.constructor.nodeData.output_node
|
||||
if (isOutputNode) {
|
||||
// Hide the audio widget when there is no audio initially.
|
||||
audioUIWidget.element.classList.add('empty-audio-widget')
|
||||
@@ -173,11 +164,11 @@ app.registerExtension({
|
||||
// The widget that allows user to select file.
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const audioWidget = node.widgets.find(
|
||||
(w) => w.name === 'audio'
|
||||
(w: IWidget) => w.name === 'audio'
|
||||
) as IStringWidget
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const audioUIWidget = node.widgets.find(
|
||||
(w) => w.name === 'audioUI'
|
||||
(w: IWidget) => w.name === 'audioUI'
|
||||
) as unknown as DOMWidget<HTMLAudioElement, string>
|
||||
|
||||
const onAudioWidgetUpdate = () => {
|
||||
|
||||
@@ -3,11 +3,11 @@ import type {
|
||||
INodeInputSlot,
|
||||
INodeOutputSlot,
|
||||
ISlotType,
|
||||
IWidget,
|
||||
LLink,
|
||||
Vector2
|
||||
} from '@comfyorg/litegraph'
|
||||
import type { CanvasMouseEvent } from '@comfyorg/litegraph/dist/types/events'
|
||||
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
|
||||
import {
|
||||
type CallbackParams,
|
||||
@@ -226,7 +226,7 @@ export class PrimitiveNode extends LGraphNode {
|
||||
|
||||
// Store current size as addWidget resizes the node
|
||||
const [oldWidth, oldHeight] = this.size
|
||||
let widget: IBaseWidget | undefined
|
||||
let widget: IWidget | undefined
|
||||
if (type in ComfyWidgets) {
|
||||
widget = (ComfyWidgets[type](this, 'value', inputData, app) || {}).widget
|
||||
} else {
|
||||
@@ -426,7 +426,7 @@ function getConfig(this: LGraphNode, widgetName: string) {
|
||||
*/
|
||||
export function convertToInput(
|
||||
node: LGraphNode,
|
||||
widget: IBaseWidget
|
||||
widget: IWidget
|
||||
): INodeInputSlot | undefined {
|
||||
console.warn(
|
||||
'Please remove call to convertToInput. Widget to socket conversion is no longer necessary, as they co-exist now.'
|
||||
@@ -506,7 +506,8 @@ export function mergeIfValid(
|
||||
}
|
||||
}
|
||||
|
||||
return { customConfig: customSpec?.[1] ?? {} }
|
||||
// @ts-expect-error fixme ts strict error
|
||||
return { customConfig: customSpec[1] }
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
|
||||
@@ -155,9 +155,6 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Queue Prompt (Front)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Queue Selected Output Nodes"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Redo"
|
||||
},
|
||||
|
||||
@@ -803,7 +803,6 @@
|
||||
"Open": "Open",
|
||||
"Queue Prompt": "Queue Prompt",
|
||||
"Queue Prompt (Front)": "Queue Prompt (Front)",
|
||||
"Queue Selected Output Nodes": "Queue Selected Output Nodes",
|
||||
"Redo": "Redo",
|
||||
"Refresh Node Definitions": "Refresh Node Definitions",
|
||||
"Save": "Save",
|
||||
@@ -1022,7 +1021,6 @@
|
||||
"image": "image",
|
||||
"preprocessors": "preprocessors",
|
||||
"advanced": "advanced",
|
||||
"guidance": "guidance",
|
||||
"loaders": "loaders",
|
||||
"model_merging": "model_merging",
|
||||
"attention_experiments": "attention_experiments",
|
||||
@@ -1035,64 +1033,44 @@
|
||||
"inpaint": "inpaint",
|
||||
"scheduling": "scheduling",
|
||||
"create": "create",
|
||||
"video": "video",
|
||||
"mask": "mask",
|
||||
"deprecated": "deprecated",
|
||||
"latent": "latent",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"3d": "3d",
|
||||
"ltxv": "ltxv",
|
||||
"sd3": "sd3",
|
||||
"sigmas": "sigmas",
|
||||
"api node": "api node",
|
||||
"BFL": "BFL",
|
||||
"model_patches": "model_patches",
|
||||
"unet": "unet",
|
||||
"gligen": "gligen",
|
||||
"video_models": "video_models",
|
||||
"Ideogram": "Ideogram",
|
||||
"v1": "v1",
|
||||
"v2": "v2",
|
||||
"v3": "v3",
|
||||
"postprocessing": "postprocessing",
|
||||
"transform": "transform",
|
||||
"batch": "batch",
|
||||
"upscaling": "upscaling",
|
||||
"instructpix2pix": "instructpix2pix",
|
||||
"compositing": "compositing",
|
||||
"Kling": "Kling",
|
||||
"samplers": "samplers",
|
||||
"operations": "operations",
|
||||
"lotus": "lotus",
|
||||
"Luma": "Luma",
|
||||
"MiniMax": "MiniMax",
|
||||
"debug": "debug",
|
||||
"model": "model",
|
||||
"model_specific": "model_specific",
|
||||
"OpenAI": "OpenAI",
|
||||
"cond pair": "cond pair",
|
||||
"photomaker": "photomaker",
|
||||
"Pika": "Pika",
|
||||
"PixVerse": "PixVerse",
|
||||
"utils": "utils",
|
||||
"primitive": "primitive",
|
||||
"Recraft": "Recraft",
|
||||
"animation": "animation",
|
||||
"api": "api",
|
||||
"upscale_diffusion": "upscale_diffusion",
|
||||
"clip": "clip",
|
||||
"Stability AI": "Stability AI",
|
||||
"guidance": "guidance",
|
||||
"stable_cascade": "stable_cascade",
|
||||
"3d_models": "3d_models",
|
||||
"style_model": "style_model",
|
||||
"sd": "sd",
|
||||
"Veo": "Veo"
|
||||
"style_model": "style_model"
|
||||
},
|
||||
"dataTypes": {
|
||||
"*": "*",
|
||||
"AUDIO": "AUDIO",
|
||||
"BOOLEAN": "BOOLEAN",
|
||||
"CAMERA_CONTROL": "CAMERA_CONTROL",
|
||||
"CLIP": "CLIP",
|
||||
"CLIP_VISION": "CLIP_VISION",
|
||||
"CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT",
|
||||
@@ -1111,27 +1089,18 @@
|
||||
"LATENT_OPERATION": "LATENT_OPERATION",
|
||||
"LOAD_3D": "LOAD_3D",
|
||||
"LOAD_3D_ANIMATION": "LOAD_3D_ANIMATION",
|
||||
"LOAD3D_CAMERA": "LOAD3D_CAMERA",
|
||||
"LUMA_CONCEPTS": "LUMA_CONCEPTS",
|
||||
"LUMA_REF": "LUMA_REF",
|
||||
"MASK": "MASK",
|
||||
"MESH": "MESH",
|
||||
"MODEL": "MODEL",
|
||||
"NOISE": "NOISE",
|
||||
"PHOTOMAKER": "PHOTOMAKER",
|
||||
"PIXVERSE_TEMPLATE": "PIXVERSE_TEMPLATE",
|
||||
"RECRAFT_COLOR": "RECRAFT_COLOR",
|
||||
"RECRAFT_CONTROLS": "RECRAFT_CONTROLS",
|
||||
"RECRAFT_V3_STYLE": "RECRAFT_V3_STYLE",
|
||||
"SAMPLER": "SAMPLER",
|
||||
"SIGMAS": "SIGMAS",
|
||||
"STRING": "STRING",
|
||||
"STYLE_MODEL": "STYLE_MODEL",
|
||||
"SVG": "SVG",
|
||||
"TIMESTEPS_RANGE": "TIMESTEPS_RANGE",
|
||||
"UPSCALE_MODEL": "UPSCALE_MODEL",
|
||||
"VAE": "VAE",
|
||||
"VIDEO": "VIDEO",
|
||||
"VOXEL": "VOXEL",
|
||||
"WEBCAM": "WEBCAM"
|
||||
},
|
||||
@@ -1228,8 +1197,6 @@
|
||||
"resizeNodeMatchOutput": "Resize Node to match output"
|
||||
},
|
||||
"toastMessages": {
|
||||
"nothingToQueue": "Nothing to queue",
|
||||
"pleaseSelectOutputNodes": "Please select output nodes",
|
||||
"no3dScene": "No 3D scene to apply texture",
|
||||
"failedToApplyTexture": "Failed to apply texture",
|
||||
"no3dSceneToExport": "No 3D scene to export",
|
||||
@@ -1262,27 +1229,8 @@
|
||||
"unauthorizedDomain": "Your domain {domain} is not authorized to use this service. Please contact {email} to add your domain to the whitelist."
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
"title": "API Key",
|
||||
"label": "API Key",
|
||||
"description": "Use your Comfy API key to enable API Nodes",
|
||||
"placeholder": "Enter your API Key",
|
||||
"error": "Invalid API Key",
|
||||
"storageFailed": "Failed to store API Key",
|
||||
"storageFailedDetail": "Please try again.",
|
||||
"stored": "API Key stored",
|
||||
"storedDetail": "Your API Key has been stored successfully",
|
||||
"cleared": "API Key cleared",
|
||||
"clearedDetail": "Your API Key has been cleared successfully",
|
||||
"invalid": "Invalid API Key",
|
||||
"invalidDetail": "Please enter a valid API Key",
|
||||
"helpText": "Need an API key?",
|
||||
"generateKey": "Get one here",
|
||||
"whitelistInfo": "About non-whitelisted sites"
|
||||
},
|
||||
"login": {
|
||||
"title": "Log in to your account",
|
||||
"useApiKey": "Comfy API Key",
|
||||
"signInOrSignUp": "Sign In / Sign Up",
|
||||
"forgotPasswordError": "Failed to send password reset email",
|
||||
"passwordResetSent": "Password reset email sent",
|
||||
@@ -1308,8 +1256,7 @@
|
||||
"success": "Login successful",
|
||||
"failed": "Login failed",
|
||||
"insecureContextWarning": "This connection is insecure (HTTP) - your credentials may be intercepted by attackers if you proceed to login.",
|
||||
"questionsContactPrefix": "Questions? Contact us at",
|
||||
"noAssociatedUser": "There is no Comfy user associated with the provided API key"
|
||||
"questionsContactPrefix": "Questions? Contact us at"
|
||||
},
|
||||
"signup": {
|
||||
"title": "Create an account",
|
||||
@@ -1340,8 +1287,6 @@
|
||||
"required": "Required",
|
||||
"minLength": "Must be at least {length} characters",
|
||||
"maxLength": "Must be no more than {length} characters",
|
||||
"prefix": "Must start with {prefix}",
|
||||
"length": "Must be {length} characters",
|
||||
"password": {
|
||||
"requirements": "Password requirements",
|
||||
"minLength": "Must be between 8 and 32 characters",
|
||||
@@ -1358,7 +1303,6 @@
|
||||
"yourCreditBalance": "Your credit balance",
|
||||
"purchaseCredits": "Purchase Credits",
|
||||
"invoiceHistory": "Invoice History",
|
||||
"apiPricing": "API Pricing",
|
||||
"faqs": "FAQs",
|
||||
"messageSupport": "Message Support",
|
||||
"lastUpdated": "Last updated",
|
||||
@@ -1399,11 +1343,5 @@
|
||||
"title": "Run!"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"tooltip": "Execute to selected output nodes (Highlighted with orange border)",
|
||||
"disabledTooltip": "No output nodes selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,9 +155,6 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Prompt de Cola (Frente)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Encolar nodos de salida seleccionados"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Rehacer"
|
||||
},
|
||||
|
||||
@@ -29,24 +29,6 @@
|
||||
"title": "Se requiere iniciar sesión para usar los nodos de API"
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
"cleared": "Clave API eliminada",
|
||||
"clearedDetail": "Tu clave API se ha eliminado correctamente",
|
||||
"description": "Usa tu clave API de Comfy para habilitar los nodos de API",
|
||||
"error": "Clave API no válida",
|
||||
"generateKey": "Consíguela aquí",
|
||||
"helpText": "¿Necesitas una clave API?",
|
||||
"invalid": "Clave API no válida",
|
||||
"invalidDetail": "Por favor, introduce una clave API válida",
|
||||
"label": "Clave API",
|
||||
"placeholder": "Introduce tu clave API",
|
||||
"storageFailed": "No se pudo guardar la clave API",
|
||||
"storageFailedDetail": "Por favor, inténtalo de nuevo.",
|
||||
"stored": "Clave API guardada",
|
||||
"storedDetail": "Tu clave API se ha guardado correctamente",
|
||||
"title": "Clave API",
|
||||
"whitelistInfo": "Acerca de los sitios no incluidos en la lista blanca"
|
||||
},
|
||||
"login": {
|
||||
"andText": "y",
|
||||
"confirmPasswordLabel": "Confirmar contraseña",
|
||||
@@ -61,7 +43,6 @@
|
||||
"loginWithGithub": "Iniciar sesión con Github",
|
||||
"loginWithGoogle": "Iniciar sesión con Google",
|
||||
"newUser": "¿Eres nuevo aquí?",
|
||||
"noAssociatedUser": "No hay ningún usuario de Comfy asociado con la clave API proporcionada",
|
||||
"orContinueWith": "O continuar con",
|
||||
"passwordLabel": "Contraseña",
|
||||
"passwordPlaceholder": "Ingresa tu contraseña",
|
||||
@@ -75,7 +56,6 @@
|
||||
"termsLink": "Términos de uso",
|
||||
"termsText": "Al hacer clic en \"Siguiente\" o \"Registrarse\", aceptas nuestros",
|
||||
"title": "Inicia sesión en tu cuenta",
|
||||
"useApiKey": "Clave API de Comfy",
|
||||
"userAvatar": "Avatar de usuario"
|
||||
},
|
||||
"passwordUpdate": {
|
||||
@@ -151,7 +131,6 @@
|
||||
"Unpin": "Desanclar"
|
||||
},
|
||||
"credits": {
|
||||
"apiPricing": "Precios de la API",
|
||||
"credits": "Créditos",
|
||||
"faqs": "Preguntas frecuentes",
|
||||
"invoiceHistory": "Historial de facturas",
|
||||
@@ -170,10 +149,8 @@
|
||||
"yourCreditBalance": "Tu saldo de créditos"
|
||||
},
|
||||
"dataTypes": {
|
||||
"*": "*",
|
||||
"AUDIO": "AUDIO",
|
||||
"BOOLEAN": "BOOLEANO",
|
||||
"CAMERA_CONTROL": "CONTROL DE CÁMARA",
|
||||
"CLIP": "CLIP",
|
||||
"CLIP_VISION": "CLIP_VISION",
|
||||
"CLIP_VISION_OUTPUT": "SALIDA_CLIP_VISION",
|
||||
@@ -190,29 +167,20 @@
|
||||
"INT": "ENTERO",
|
||||
"LATENT": "LATENTE",
|
||||
"LATENT_OPERATION": "OPERACIÓN_LATENTE",
|
||||
"LOAD3D_CAMERA": "CARGAR CÁMARA 3D",
|
||||
"LOAD_3D": "CARGAR_3D",
|
||||
"LOAD_3D_ANIMATION": "CARGAR_ANIMACIÓN_3D",
|
||||
"LUMA_CONCEPTS": "CONCEPTOS LUMA",
|
||||
"LUMA_REF": "REFERENCIA LUMA",
|
||||
"MASK": "MASK",
|
||||
"MESH": "MALLA",
|
||||
"MODEL": "MODELO",
|
||||
"NOISE": "RUIDO",
|
||||
"PHOTOMAKER": "PHOTOMAKER",
|
||||
"PIXVERSE_TEMPLATE": "PLANTILLA PIXVERSE",
|
||||
"RECRAFT_COLOR": "COLOR RECRAFT",
|
||||
"RECRAFT_CONTROLS": "CONTROLES RECRAFT",
|
||||
"RECRAFT_V3_STYLE": "ESTILO RECRAFT V3",
|
||||
"SAMPLER": "MUESTREADOR",
|
||||
"SIGMAS": "SIGMAS",
|
||||
"STRING": "CADENA",
|
||||
"STYLE_MODEL": "MODELO_DE_ESTILO",
|
||||
"SVG": "SVG",
|
||||
"TIMESTEPS_RANGE": "RANGO_DE_PASOS_DE_TIEMPO",
|
||||
"UPSCALE_MODEL": "MODELO_DE_ESCALADO",
|
||||
"VAE": "VAE",
|
||||
"VIDEO": "VÍDEO",
|
||||
"VOXEL": "VOXEL",
|
||||
"WEBCAM": "WEBCAM"
|
||||
},
|
||||
@@ -727,7 +695,6 @@
|
||||
"Previous Opened Workflow": "Flujo de trabajo abierto anterior",
|
||||
"Queue Prompt": "Indicador de cola",
|
||||
"Queue Prompt (Front)": "Indicador de cola (Frente)",
|
||||
"Queue Selected Output Nodes": "Encolar nodos de salida seleccionados",
|
||||
"Quit": "Salir",
|
||||
"Redo": "Rehacer",
|
||||
"Refresh Node Definitions": "Actualizar definiciones de nodo",
|
||||
@@ -764,22 +731,10 @@
|
||||
"nodeCategories": {
|
||||
"3d": "3d",
|
||||
"3d_models": "modelos_3d",
|
||||
"BFL": "BFL",
|
||||
"Ideogram": "Ideogram",
|
||||
"Kling": "Kling",
|
||||
"Luma": "Luma",
|
||||
"MiniMax": "MiniMax",
|
||||
"OpenAI": "OpenAI",
|
||||
"Pika": "Pika",
|
||||
"PixVerse": "PixVerse",
|
||||
"Recraft": "Recraft",
|
||||
"Stability AI": "Stability AI",
|
||||
"Veo": "Veo",
|
||||
"_for_testing": "_para_pruebas",
|
||||
"advanced": "avanzado",
|
||||
"animation": "animación",
|
||||
"api": "api",
|
||||
"api node": "nodo api",
|
||||
"attention_experiments": "experimentos_de_atención",
|
||||
"audio": "audio",
|
||||
"batch": "lote",
|
||||
@@ -804,7 +759,6 @@
|
||||
"instructpix2pix": "instruirpix2pix",
|
||||
"latent": "latent",
|
||||
"loaders": "cargadores",
|
||||
"lotus": "lotus",
|
||||
"ltxv": "ltxv",
|
||||
"mask": "mask",
|
||||
"model": "modelo",
|
||||
@@ -816,12 +770,10 @@
|
||||
"photomaker": "photomaker",
|
||||
"postprocessing": "postprocesamiento",
|
||||
"preprocessors": "preprocesadores",
|
||||
"primitive": "primitivo",
|
||||
"samplers": "muestreadores",
|
||||
"sampling": "muestreo",
|
||||
"schedulers": "programadores",
|
||||
"scheduling": "programación",
|
||||
"sd": "sd",
|
||||
"sd3": "sd3",
|
||||
"sigmas": "sigmas",
|
||||
"stable_cascade": "stable_cascade",
|
||||
@@ -830,10 +782,6 @@
|
||||
"unet": "unet",
|
||||
"upscale_diffusion": "difusión_de_escalado",
|
||||
"upscaling": "escalado",
|
||||
"utils": "utilidades",
|
||||
"v1": "v1",
|
||||
"v2": "v2",
|
||||
"v3": "v3",
|
||||
"video": "video",
|
||||
"video_models": "modelos_de_video"
|
||||
},
|
||||
@@ -853,12 +801,6 @@
|
||||
},
|
||||
"title": "Tu dispositivo no es compatible"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "No hay nodos de salida seleccionados",
|
||||
"tooltip": "Ejecutar en los nodos de salida seleccionados (resaltados con borde naranja)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "Has modificado las siguientes configuraciones del servidor. Reinicia para aplicar los cambios.",
|
||||
"restart": "Reiniciar",
|
||||
@@ -1355,10 +1297,8 @@
|
||||
"noTemplatesToExport": "No hay plantillas para exportar",
|
||||
"nodeDefinitionsUpdated": "Definiciones de nodos actualizadas",
|
||||
"nothingToGroup": "Nada para agrupar",
|
||||
"nothingToQueue": "Nada para poner en cola",
|
||||
"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",
|
||||
"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",
|
||||
@@ -1381,7 +1321,6 @@
|
||||
},
|
||||
"validation": {
|
||||
"invalidEmail": "Dirección de correo electrónico inválida",
|
||||
"length": "Debe tener {length} caracteres",
|
||||
"maxLength": "No debe tener más de {length} caracteres",
|
||||
"minLength": "Debe tener al menos {length} caracteres",
|
||||
"password": {
|
||||
@@ -1394,7 +1333,6 @@
|
||||
"uppercase": "Debe contener al menos una letra mayúscula"
|
||||
},
|
||||
"personalDataConsentRequired": "Debes aceptar el procesamiento de tus datos personales.",
|
||||
"prefix": "Debe comenzar con {prefix}",
|
||||
"required": "Requerido"
|
||||
},
|
||||
"welcome": {
|
||||
|
||||
@@ -155,9 +155,6 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Invite de file d'attente (avant)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Mettre en file d’attente les nœuds de sortie sélectionnés"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Refaire"
|
||||
},
|
||||
|
||||
@@ -29,24 +29,6 @@
|
||||
"title": "Connexion requise pour utiliser les nœuds API"
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
"cleared": "Clé API supprimée",
|
||||
"clearedDetail": "Votre clé API a été supprimée avec succès",
|
||||
"description": "Utilisez votre clé API Comfy pour activer les nœuds API",
|
||||
"error": "Clé API invalide",
|
||||
"generateKey": "Obtenez-en une ici",
|
||||
"helpText": "Besoin d'une clé API ?",
|
||||
"invalid": "Clé API invalide",
|
||||
"invalidDetail": "Veuillez entrer une clé API valide",
|
||||
"label": "Clé API",
|
||||
"placeholder": "Entrez votre clé API",
|
||||
"storageFailed": "Échec de l’enregistrement de la clé API",
|
||||
"storageFailedDetail": "Veuillez réessayer.",
|
||||
"stored": "Clé API enregistrée",
|
||||
"storedDetail": "Votre clé API a été enregistrée avec succès",
|
||||
"title": "Clé API",
|
||||
"whitelistInfo": "À propos des sites non autorisés"
|
||||
},
|
||||
"login": {
|
||||
"andText": "et",
|
||||
"confirmPasswordLabel": "Confirmer le mot de passe",
|
||||
@@ -61,7 +43,6 @@
|
||||
"loginWithGithub": "Se connecter avec Github",
|
||||
"loginWithGoogle": "Se connecter avec Google",
|
||||
"newUser": "Nouveau ici?",
|
||||
"noAssociatedUser": "Aucun utilisateur Comfy n'est associé à la clé API fournie",
|
||||
"orContinueWith": "Ou continuer avec",
|
||||
"passwordLabel": "Mot de passe",
|
||||
"passwordPlaceholder": "Entrez votre mot de passe",
|
||||
@@ -75,7 +56,6 @@
|
||||
"termsLink": "Conditions d'utilisation",
|
||||
"termsText": "En cliquant sur \"Suivant\" ou \"S'inscrire\", vous acceptez nos",
|
||||
"title": "Connectez-vous à votre compte",
|
||||
"useApiKey": "Clé API Comfy",
|
||||
"userAvatar": "Avatar utilisateur"
|
||||
},
|
||||
"passwordUpdate": {
|
||||
@@ -151,7 +131,6 @@
|
||||
"Unpin": "Désépingler"
|
||||
},
|
||||
"credits": {
|
||||
"apiPricing": "Tarification de l’API",
|
||||
"credits": "Crédits",
|
||||
"faqs": "FAQ",
|
||||
"invoiceHistory": "Historique des factures",
|
||||
@@ -170,10 +149,8 @@
|
||||
"yourCreditBalance": "Votre solde de crédits"
|
||||
},
|
||||
"dataTypes": {
|
||||
"*": "*",
|
||||
"AUDIO": "AUDIO",
|
||||
"BOOLEAN": "BOOLEAN",
|
||||
"CAMERA_CONTROL": "Contrôle de la caméra",
|
||||
"CLIP": "CLIP",
|
||||
"CLIP_VISION": "CLIP_VISION",
|
||||
"CLIP_VISION_OUTPUT": "SORTIE_CLIP_VISION",
|
||||
@@ -190,29 +167,20 @@
|
||||
"INT": "ENTIER",
|
||||
"LATENT": "LATENT",
|
||||
"LATENT_OPERATION": "OPERATION_LATENTE",
|
||||
"LOAD3D_CAMERA": "Charger la caméra 3D",
|
||||
"LOAD_3D": "CHARGER_3D",
|
||||
"LOAD_3D_ANIMATION": "CHARGER_ANIMATION_3D",
|
||||
"LUMA_CONCEPTS": "Concepts Luma",
|
||||
"LUMA_REF": "Référence Luma",
|
||||
"MASK": "MASQUE",
|
||||
"MESH": "MAILLAGE",
|
||||
"MODEL": "MODÈLE",
|
||||
"NOISE": "BRUIT",
|
||||
"PHOTOMAKER": "PHOTOMAKER",
|
||||
"PIXVERSE_TEMPLATE": "Modèle Pixverse",
|
||||
"RECRAFT_COLOR": "Couleur Recraft",
|
||||
"RECRAFT_CONTROLS": "Contrôles Recraft",
|
||||
"RECRAFT_V3_STYLE": "Style Recraft V3",
|
||||
"SAMPLER": "ÉCHANTILLONNEUR",
|
||||
"SIGMAS": "SIGMAS",
|
||||
"STRING": "CHAÎNE",
|
||||
"STYLE_MODEL": "MODÈLE_DE_STYLE",
|
||||
"SVG": "SVG",
|
||||
"TIMESTEPS_RANGE": "PLAGE_DES_ÉTAPES_TEMPORELLES",
|
||||
"UPSCALE_MODEL": "MODÈLE_DE_MISE_À_L'ÉCHELLE",
|
||||
"VAE": "VAE",
|
||||
"VIDEO": "Vidéo",
|
||||
"VOXEL": "VOXEL",
|
||||
"WEBCAM": "WEBCAM"
|
||||
},
|
||||
@@ -727,7 +695,6 @@
|
||||
"Previous Opened Workflow": "Flux de travail ouvert précédent",
|
||||
"Queue Prompt": "Invite de file d'attente",
|
||||
"Queue Prompt (Front)": "Invite de file d'attente (Front)",
|
||||
"Queue Selected Output Nodes": "Mettre en file d’attente les nœuds de sortie sélectionnés",
|
||||
"Quit": "Quitter",
|
||||
"Redo": "Refaire",
|
||||
"Refresh Node Definitions": "Actualiser les définitions de nœud",
|
||||
@@ -764,22 +731,10 @@
|
||||
"nodeCategories": {
|
||||
"3d": "3d",
|
||||
"3d_models": "modèles_3d",
|
||||
"BFL": "BFL",
|
||||
"Ideogram": "Ideogram",
|
||||
"Kling": "Kling",
|
||||
"Luma": "Luma",
|
||||
"MiniMax": "MiniMax",
|
||||
"OpenAI": "OpenAI",
|
||||
"Pika": "Pika",
|
||||
"PixVerse": "PixVerse",
|
||||
"Recraft": "Recraft",
|
||||
"Stability AI": "Stability AI",
|
||||
"Veo": "Veo",
|
||||
"_for_testing": "_pour_test",
|
||||
"advanced": "avancé",
|
||||
"animation": "animation",
|
||||
"api": "api",
|
||||
"api node": "nœud api",
|
||||
"attention_experiments": "expériences_d'attention",
|
||||
"audio": "audio",
|
||||
"batch": "lot",
|
||||
@@ -804,7 +759,6 @@
|
||||
"instructpix2pix": "instructpix2pix",
|
||||
"latent": "latent",
|
||||
"loaders": "chargeurs",
|
||||
"lotus": "lotus",
|
||||
"ltxv": "ltxv",
|
||||
"mask": "masque",
|
||||
"model": "modèle",
|
||||
@@ -816,12 +770,10 @@
|
||||
"photomaker": "photomaker",
|
||||
"postprocessing": "post-traitement",
|
||||
"preprocessors": "préprocesseurs",
|
||||
"primitive": "primitif",
|
||||
"samplers": "échantillonneurs",
|
||||
"sampling": "échantillonnage",
|
||||
"schedulers": "planificateurs",
|
||||
"scheduling": "planification",
|
||||
"sd": "sd",
|
||||
"sd3": "sd3",
|
||||
"sigmas": "sigmas",
|
||||
"stable_cascade": "stable_cascade",
|
||||
@@ -830,10 +782,6 @@
|
||||
"unet": "unet",
|
||||
"upscale_diffusion": "diffusion_de_mise_à_l'échelle",
|
||||
"upscaling": "mise_à_l'échelle",
|
||||
"utils": "utilitaires",
|
||||
"v1": "v1",
|
||||
"v2": "v2",
|
||||
"v3": "v3",
|
||||
"video": "vidéo",
|
||||
"video_models": "modèles_vidéo"
|
||||
},
|
||||
@@ -853,12 +801,6 @@
|
||||
},
|
||||
"title": "Votre appareil n'est pas pris en charge"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "Aucun nœud de sortie sélectionné",
|
||||
"tooltip": "Exécuter vers les nœuds de sortie sélectionnés (surlignés avec une bordure orange)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "Vous avez modifié les configurations suivantes du serveur. Redémarrez pour appliquer les modifications.",
|
||||
"restart": "Redémarrer",
|
||||
@@ -1355,10 +1297,8 @@
|
||||
"noTemplatesToExport": "Aucun modèle à exporter",
|
||||
"nodeDefinitionsUpdated": "Définitions de nœuds mises à jour",
|
||||
"nothingToGroup": "Rien à regrouper",
|
||||
"nothingToQueue": "Rien à ajouter à la file d’attente",
|
||||
"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",
|
||||
"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",
|
||||
@@ -1381,7 +1321,6 @@
|
||||
},
|
||||
"validation": {
|
||||
"invalidEmail": "Adresse e-mail invalide",
|
||||
"length": "Doit comporter {length} caractères",
|
||||
"maxLength": "Ne doit pas dépasser {length} caractères",
|
||||
"minLength": "Doit contenir au moins {length} caractères",
|
||||
"password": {
|
||||
@@ -1394,7 +1333,6 @@
|
||||
"uppercase": "Doit contenir au moins une lettre majuscule"
|
||||
},
|
||||
"personalDataConsentRequired": "Vous devez accepter le traitement de vos données personnelles.",
|
||||
"prefix": "Doit commencer par {prefix}",
|
||||
"required": "Requis"
|
||||
},
|
||||
"welcome": {
|
||||
|
||||
@@ -155,9 +155,6 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "キュープロンプト(フロント)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "選択した出力ノードをキューに追加"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "やり直す"
|
||||
},
|
||||
|
||||
@@ -29,24 +29,6 @@
|
||||
"title": "APIノードを使用するためにはサインインが必要です"
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
"cleared": "APIキーが削除されました",
|
||||
"clearedDetail": "APIキーが正常に削除されました",
|
||||
"description": "Comfy APIキーを使用してAPIノードを有効にします",
|
||||
"error": "無効なAPIキーです",
|
||||
"generateKey": "こちらから取得",
|
||||
"helpText": "APIキーが必要ですか?",
|
||||
"invalid": "無効なAPIキーです",
|
||||
"invalidDetail": "有効なAPIキーを入力してください",
|
||||
"label": "APIキー",
|
||||
"placeholder": "APIキーを入力してください",
|
||||
"storageFailed": "APIキーの保存に失敗しました",
|
||||
"storageFailedDetail": "もう一度お試しください。",
|
||||
"stored": "APIキーが保存されました",
|
||||
"storedDetail": "APIキーが正常に保存されました",
|
||||
"title": "APIキー",
|
||||
"whitelistInfo": "ホワイトリストに登録されていないサイトについて"
|
||||
},
|
||||
"login": {
|
||||
"andText": "および",
|
||||
"confirmPasswordLabel": "パスワードの確認",
|
||||
@@ -61,7 +43,6 @@
|
||||
"loginWithGithub": "Githubでログイン",
|
||||
"loginWithGoogle": "Googleでログイン",
|
||||
"newUser": "新規ユーザーですか?",
|
||||
"noAssociatedUser": "指定されたAPIキーに関連付けられたComfyユーザーが存在しません",
|
||||
"orContinueWith": "または以下で続ける",
|
||||
"passwordLabel": "パスワード",
|
||||
"passwordPlaceholder": "パスワードを入力してください",
|
||||
@@ -75,7 +56,6 @@
|
||||
"termsLink": "利用規約",
|
||||
"termsText": "「次へ」または「サインアップ」をクリックすると、私たちの",
|
||||
"title": "アカウントにログインする",
|
||||
"useApiKey": "Comfy APIキー",
|
||||
"userAvatar": "ユーザーアバター"
|
||||
},
|
||||
"passwordUpdate": {
|
||||
@@ -151,7 +131,6 @@
|
||||
"Unpin": "ピンを解除"
|
||||
},
|
||||
"credits": {
|
||||
"apiPricing": "API料金",
|
||||
"credits": "クレジット",
|
||||
"faqs": "よくある質問",
|
||||
"invoiceHistory": "請求履歴",
|
||||
@@ -170,10 +149,8 @@
|
||||
"yourCreditBalance": "あなたのクレジット残高"
|
||||
},
|
||||
"dataTypes": {
|
||||
"*": "*",
|
||||
"AUDIO": "オーディオ",
|
||||
"BOOLEAN": "ブール",
|
||||
"CAMERA_CONTROL": "カメラコントロール",
|
||||
"CLIP": "CLIP",
|
||||
"CLIP_VISION": "CLIP_VISION",
|
||||
"CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT",
|
||||
@@ -190,29 +167,20 @@
|
||||
"INT": "整数",
|
||||
"LATENT": "潜在",
|
||||
"LATENT_OPERATION": "潜在操作",
|
||||
"LOAD3D_CAMERA": "3Dカメラの読み込み",
|
||||
"LOAD_3D": "3Dをロード",
|
||||
"LOAD_3D_ANIMATION": "3Dアニメーションをロード",
|
||||
"LUMA_CONCEPTS": "Lumaコンセプト",
|
||||
"LUMA_REF": "Luma参照",
|
||||
"MASK": "マスク",
|
||||
"MESH": "メッシュ",
|
||||
"MODEL": "モデル",
|
||||
"NOISE": "ノイズ",
|
||||
"PHOTOMAKER": "PHOTOMAKER",
|
||||
"PIXVERSE_TEMPLATE": "Pixverseテンプレート",
|
||||
"RECRAFT_COLOR": "Recraftカラー",
|
||||
"RECRAFT_CONTROLS": "Recraftコントロール",
|
||||
"RECRAFT_V3_STYLE": "Recraft V3スタイル",
|
||||
"SAMPLER": "サンプラー",
|
||||
"SIGMAS": "シグマ",
|
||||
"STRING": "文字列",
|
||||
"STYLE_MODEL": "スタイルモデル",
|
||||
"SVG": "SVG",
|
||||
"TIMESTEPS_RANGE": "タイムステップの範囲",
|
||||
"UPSCALE_MODEL": "アップスケールモデル",
|
||||
"VAE": "VAE",
|
||||
"VIDEO": "ビデオ",
|
||||
"VOXEL": "ボクセル",
|
||||
"WEBCAM": "ウェブカメラ"
|
||||
},
|
||||
@@ -727,7 +695,6 @@
|
||||
"Previous Opened Workflow": "前に開いたワークフロー",
|
||||
"Queue Prompt": "キューのプロンプト",
|
||||
"Queue Prompt (Front)": "キューのプロンプト (前面)",
|
||||
"Queue Selected Output Nodes": "選択した出力ノードをキューに追加",
|
||||
"Quit": "終了",
|
||||
"Redo": "やり直す",
|
||||
"Refresh Node Definitions": "ノード定義を更新",
|
||||
@@ -764,22 +731,10 @@
|
||||
"nodeCategories": {
|
||||
"3d": "3d",
|
||||
"3d_models": "3Dモデル",
|
||||
"BFL": "BFL",
|
||||
"Ideogram": "Ideogram",
|
||||
"Kling": "Kling",
|
||||
"Luma": "Luma",
|
||||
"MiniMax": "MiniMax",
|
||||
"OpenAI": "OpenAI",
|
||||
"Pika": "Pika",
|
||||
"PixVerse": "PixVerse",
|
||||
"Recraft": "Recraft",
|
||||
"Stability AI": "Stability AI",
|
||||
"Veo": "Veo",
|
||||
"_for_testing": "_テスト用",
|
||||
"advanced": "高度な機能",
|
||||
"animation": "アニメーション",
|
||||
"api": "API",
|
||||
"api node": "apiノード",
|
||||
"attention_experiments": "アテンション実験",
|
||||
"audio": "オーディオ",
|
||||
"batch": "バッチ",
|
||||
@@ -804,7 +759,6 @@
|
||||
"instructpix2pix": "インストラクションピクス2ピクス",
|
||||
"latent": "潜在",
|
||||
"loaders": "ローダー",
|
||||
"lotus": "lotus",
|
||||
"ltxv": "LTXV",
|
||||
"mask": "マスク",
|
||||
"model": "モデル",
|
||||
@@ -816,12 +770,10 @@
|
||||
"photomaker": "photomaker",
|
||||
"postprocessing": "ポストプロセッシング",
|
||||
"preprocessors": "前処理",
|
||||
"primitive": "プリミティブ",
|
||||
"samplers": "サンプラー",
|
||||
"sampling": "サンプリング",
|
||||
"schedulers": "スケジューラー",
|
||||
"scheduling": "スケジューリング",
|
||||
"sd": "sd",
|
||||
"sd3": "SD3",
|
||||
"sigmas": "シグマ",
|
||||
"stable_cascade": "安定したカスケード",
|
||||
@@ -830,10 +782,6 @@
|
||||
"unet": "U-Net",
|
||||
"upscale_diffusion": "アップスケール拡散",
|
||||
"upscaling": "アップスケーリング",
|
||||
"utils": "ユーティリティ",
|
||||
"v1": "v1",
|
||||
"v2": "v2",
|
||||
"v3": "v3",
|
||||
"video": "ビデオ",
|
||||
"video_models": "ビデオモデル"
|
||||
},
|
||||
@@ -853,12 +801,6 @@
|
||||
},
|
||||
"title": "お使いのデバイスはサポートされていません"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "出力ノードが選択されていません",
|
||||
"tooltip": "選択した出力ノードに実行(オレンジ色の枠でハイライト表示)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "以下のサーバー設定を変更しました。変更を適用するには再起動してください。",
|
||||
"restart": "再起動",
|
||||
@@ -1355,10 +1297,8 @@
|
||||
"noTemplatesToExport": "エクスポートするテンプレートがありません",
|
||||
"nodeDefinitionsUpdated": "ノード定義が更新されました",
|
||||
"nothingToGroup": "グループ化するものがありません",
|
||||
"nothingToQueue": "キューに追加する項目がありません",
|
||||
"pendingTasksDeleted": "保留中のタスクが削除されました",
|
||||
"pleaseSelectNodesToGroup": "グループを作成するためのノード(または他のグループ)を選択してください",
|
||||
"pleaseSelectOutputNodes": "出力ノードを選択してください",
|
||||
"unableToGetModelFilePath": "モデルファイルのパスを取得できません",
|
||||
"unauthorizedDomain": "あなたのドメイン {domain} はこのサービスを利用する権限がありません。ご利用のドメインをホワイトリストに追加するには、{email} までご連絡ください。",
|
||||
"updateRequested": "更新が要求されました",
|
||||
@@ -1381,7 +1321,6 @@
|
||||
},
|
||||
"validation": {
|
||||
"invalidEmail": "無効なメールアドレス",
|
||||
"length": "{length}文字でなければなりません",
|
||||
"maxLength": "{length}文字以下でなければなりません",
|
||||
"minLength": "{length}文字以上でなければなりません",
|
||||
"password": {
|
||||
@@ -1394,7 +1333,6 @@
|
||||
"uppercase": "少なくとも1つの大文字を含む必要があります"
|
||||
},
|
||||
"personalDataConsentRequired": "個人データの処理に同意する必要があります。",
|
||||
"prefix": "{prefix}で始める必要があります",
|
||||
"required": "必須"
|
||||
},
|
||||
"welcome": {
|
||||
|
||||
@@ -155,9 +155,6 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "실행 큐 맨 앞에 프롬프트 추가"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "선택한 출력 노드 대기열에 추가"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "다시 실행"
|
||||
},
|
||||
|
||||
@@ -29,24 +29,6 @@
|
||||
"title": "API 노드 사용에 필요한 로그인"
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
"cleared": "API 키 삭제됨",
|
||||
"clearedDetail": "API 키가 성공적으로 삭제되었습니다",
|
||||
"description": "Comfy API 키를 사용하여 API 노드를 활성화하세요",
|
||||
"error": "유효하지 않은 API 키",
|
||||
"generateKey": "여기에서 받기",
|
||||
"helpText": "API 키가 필요하신가요?",
|
||||
"invalid": "유효하지 않은 API 키",
|
||||
"invalidDetail": "유효한 API 키를 입력해 주세요",
|
||||
"label": "API 키",
|
||||
"placeholder": "API 키를 입력하세요",
|
||||
"storageFailed": "API 키 저장 실패",
|
||||
"storageFailedDetail": "다시 시도해 주세요.",
|
||||
"stored": "API 키 저장됨",
|
||||
"storedDetail": "API 키가 성공적으로 저장되었습니다",
|
||||
"title": "API 키",
|
||||
"whitelistInfo": "비허용 사이트에 대하여"
|
||||
},
|
||||
"login": {
|
||||
"andText": "및",
|
||||
"confirmPasswordLabel": "비밀번호 확인",
|
||||
@@ -61,7 +43,6 @@
|
||||
"loginWithGithub": "Github로 로그인",
|
||||
"loginWithGoogle": "구글로 로그인",
|
||||
"newUser": "처음이신가요?",
|
||||
"noAssociatedUser": "제공된 API 키와 연결된 Comfy 사용자가 없습니다",
|
||||
"orContinueWith": "또는 다음으로 계속",
|
||||
"passwordLabel": "비밀번호",
|
||||
"passwordPlaceholder": "비밀번호를 입력하세요",
|
||||
@@ -75,7 +56,6 @@
|
||||
"termsLink": "이용 약관",
|
||||
"termsText": "\"다음\" 또는 \"가입하기\"를 클릭하면 우리의",
|
||||
"title": "계정에 로그인",
|
||||
"useApiKey": "Comfy API 키",
|
||||
"userAvatar": "사용자 아바타"
|
||||
},
|
||||
"passwordUpdate": {
|
||||
@@ -151,7 +131,6 @@
|
||||
"Unpin": "고정 해제"
|
||||
},
|
||||
"credits": {
|
||||
"apiPricing": "API 가격",
|
||||
"credits": "크레딧",
|
||||
"faqs": "자주 묻는 질문",
|
||||
"invoiceHistory": "청구서 내역",
|
||||
@@ -170,10 +149,8 @@
|
||||
"yourCreditBalance": "보유 크레딧 잔액"
|
||||
},
|
||||
"dataTypes": {
|
||||
"*": "*",
|
||||
"AUDIO": "오디오",
|
||||
"BOOLEAN": "논리값",
|
||||
"CAMERA_CONTROL": "카메라 제어",
|
||||
"CLIP": "CLIP",
|
||||
"CLIP_VISION": "CLIP_VISION",
|
||||
"CLIP_VISION_OUTPUT": "CLIP_VISION 출력",
|
||||
@@ -190,29 +167,20 @@
|
||||
"INT": "정수",
|
||||
"LATENT": "잠재 데이터",
|
||||
"LATENT_OPERATION": "잠재 연산",
|
||||
"LOAD3D_CAMERA": "3D 카메라 불러오기",
|
||||
"LOAD_3D": "3D 로드",
|
||||
"LOAD_3D_ANIMATION": "3D 애니메이션 로드",
|
||||
"LUMA_CONCEPTS": "Luma 컨셉",
|
||||
"LUMA_REF": "Luma 참조",
|
||||
"MASK": "마스크",
|
||||
"MESH": "메시",
|
||||
"MODEL": "모델",
|
||||
"NOISE": "노이즈",
|
||||
"PHOTOMAKER": "PHOTOMAKER",
|
||||
"PIXVERSE_TEMPLATE": "Pixverse 템플릿",
|
||||
"RECRAFT_COLOR": "Recraft 색상",
|
||||
"RECRAFT_CONTROLS": "Recraft 컨트롤",
|
||||
"RECRAFT_V3_STYLE": "Recraft V3 스타일",
|
||||
"SAMPLER": "샘플러",
|
||||
"SIGMAS": "시그마",
|
||||
"STRING": "문자열",
|
||||
"STYLE_MODEL": "스타일 모델",
|
||||
"SVG": "SVG",
|
||||
"TIMESTEPS_RANGE": "타임스텝 범위",
|
||||
"UPSCALE_MODEL": "업스케일 모델",
|
||||
"VAE": "VAE",
|
||||
"VIDEO": "비디오",
|
||||
"VOXEL": "복셀",
|
||||
"WEBCAM": "웹캠"
|
||||
},
|
||||
@@ -655,20 +623,20 @@
|
||||
"Tolerance": "허용 오차"
|
||||
},
|
||||
"menu": {
|
||||
"autoQueue": "자동 실행 대기열",
|
||||
"autoQueue": "자동 실행 큐",
|
||||
"batchCount": "배치 수",
|
||||
"batchCountTooltip": "워크플로 작업을 실행 대기열에 반복 추가할 횟수",
|
||||
"batchCountTooltip": "워크플로 작업을 실행 큐에 반복 추가할 횟수",
|
||||
"clear": "워크플로 비우기",
|
||||
"clipspace": "클립스페이스 열기",
|
||||
"disabled": "비활성화됨",
|
||||
"disabledTooltip": "워크플로 작업을 자동으로 실행 대기열에 추가하지 않습니다.",
|
||||
"disabledTooltip": "워크플로 작업을 자동으로 실행 큐에 추가하지 않습니다.",
|
||||
"execute": "실행",
|
||||
"hideMenu": "메뉴 숨기기",
|
||||
"instant": "즉시",
|
||||
"instantTooltip": "워크플로 실행이 완료되면 즉시 실행 대기열에 추가합니다.",
|
||||
"instantTooltip": "워크플로 실행이 완료되면 즉시 실행 큐에 추가합니다.",
|
||||
"interrupt": "현재 실행 취소",
|
||||
"onChange": "변경 시",
|
||||
"onChangeTooltip": "변경이 있는 경우에만 워크플로를 실행 대기열에 추가합니다.",
|
||||
"onChangeTooltip": "변경이 있는 경우에만 워크플로를 실행 큐에 추가합니다.",
|
||||
"refresh": "노드 정의 새로 고침",
|
||||
"resetView": "캔버스 보기 재설정",
|
||||
"run": "실행",
|
||||
@@ -725,9 +693,8 @@
|
||||
"Pin/Unpin Selected Items": "선택한 항목 고정/고정 해제",
|
||||
"Pin/Unpin Selected Nodes": "선택한 노드 고정/고정 해제",
|
||||
"Previous Opened Workflow": "이전 열린 워크플로",
|
||||
"Queue Prompt": "실행 대기열에 프롬프트 추가",
|
||||
"Queue Prompt (Front)": "실행 대기열 맨 앞에 프롬프트 추가",
|
||||
"Queue Selected Output Nodes": "선택한 출력 노드 대기열에 추가",
|
||||
"Queue Prompt": "실행 큐에 프롬프트 추가",
|
||||
"Queue Prompt (Front)": "실행 큐 맨 앞에 프롬프트 추가",
|
||||
"Quit": "종료",
|
||||
"Redo": "다시 실행",
|
||||
"Refresh Node Definitions": "노드 정의 새로 고침",
|
||||
@@ -745,7 +712,7 @@
|
||||
"Toggle Model Library Sidebar": "모델 라이브러리 사이드바 전환",
|
||||
"Toggle Node Library Sidebar": "노드 라이브러리 사이드바 전환",
|
||||
"Toggle Progress Dialog": "진행 상황 대화 상자 전환",
|
||||
"Toggle Queue Sidebar": "실행 대기열 사이드바 전환",
|
||||
"Toggle Queue Sidebar": "실행 큐 사이드바 전환",
|
||||
"Toggle Search Box": "검색 상자 전환",
|
||||
"Toggle Terminal Bottom Panel": "터미널 하단 패널 전환",
|
||||
"Toggle Theme (Dark/Light)": "테마 전환 (어두운/밝은)",
|
||||
@@ -764,22 +731,10 @@
|
||||
"nodeCategories": {
|
||||
"3d": "3d",
|
||||
"3d_models": "3D 모델",
|
||||
"BFL": "BFL",
|
||||
"Ideogram": "Ideogram",
|
||||
"Kling": "Kling",
|
||||
"Luma": "Luma",
|
||||
"MiniMax": "MiniMax",
|
||||
"OpenAI": "OpenAI",
|
||||
"Pika": "Pika",
|
||||
"PixVerse": "PixVerse",
|
||||
"Recraft": "Recraft",
|
||||
"Stability AI": "Stability AI",
|
||||
"Veo": "Veo",
|
||||
"_for_testing": "_테스트용",
|
||||
"advanced": "고급",
|
||||
"animation": "애니메이션",
|
||||
"api": "API",
|
||||
"api node": "api 노드",
|
||||
"attention_experiments": "어텐션 실험",
|
||||
"audio": "오디오",
|
||||
"batch": "배치",
|
||||
@@ -804,7 +759,6 @@
|
||||
"instructpix2pix": "InstructPix2Pix",
|
||||
"latent": "잠재 데이터",
|
||||
"loaders": "로더",
|
||||
"lotus": "lotus",
|
||||
"ltxv": "ltxv",
|
||||
"mask": "마스크",
|
||||
"model": "모델",
|
||||
@@ -816,12 +770,10 @@
|
||||
"photomaker": "포토메이커",
|
||||
"postprocessing": "후처리",
|
||||
"preprocessors": "전처리기",
|
||||
"primitive": "기본 입력",
|
||||
"samplers": "샘플러",
|
||||
"sampling": "샘플링",
|
||||
"schedulers": "스케줄러",
|
||||
"scheduling": "스케줄링",
|
||||
"sd": "sd",
|
||||
"sd3": "sd3",
|
||||
"sigmas": "시그마",
|
||||
"stable_cascade": "Stable Cascade",
|
||||
@@ -830,10 +782,6 @@
|
||||
"unet": "UNet",
|
||||
"upscale_diffusion": "업스케일 확산",
|
||||
"upscaling": "업스케일링",
|
||||
"utils": "유틸리티",
|
||||
"v1": "v1",
|
||||
"v2": "v2",
|
||||
"v3": "v3",
|
||||
"video": "비디오",
|
||||
"video_models": "비디오 모델"
|
||||
},
|
||||
@@ -853,12 +801,6 @@
|
||||
},
|
||||
"title": "이 장치는 지원되지 않습니다."
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "선택된 출력 노드가 없습니다",
|
||||
"tooltip": "선택한 출력 노드에 실행 (주황색 테두리로 강조 표시됨)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "다음 서버 구성을 수정했습니다. 변경 사항을 적용하려면 다시 시작하세오.",
|
||||
"restart": "다시 시작",
|
||||
@@ -1041,8 +983,8 @@
|
||||
"Node Widget": "노드 위젯",
|
||||
"NodeLibrary": "노드 라이브러리",
|
||||
"Pointer": "포인터",
|
||||
"Queue": "실행 대기열",
|
||||
"QueueButton": "실행 대기열 버튼",
|
||||
"Queue": "실행 큐",
|
||||
"QueueButton": "실행 큐 버튼",
|
||||
"Reroute": "경유점",
|
||||
"RerouteBeta": "경유점 (베타)",
|
||||
"Scene": "장면",
|
||||
@@ -1068,7 +1010,7 @@
|
||||
"sortOrder": "정렬 순서"
|
||||
},
|
||||
"openWorkflow": "로컬 파일 시스템에서 워크플로 열기",
|
||||
"queue": "실행 대기열",
|
||||
"queue": "실행 큐",
|
||||
"queueTab": {
|
||||
"backToAllTasks": "모든 작업으로 돌아가기",
|
||||
"clearPendingTasks": "보류 중인 작업 지우기",
|
||||
@@ -1259,28 +1201,28 @@
|
||||
"mixing_controlnets": "여러 ControlNet 모델을 결합합니다."
|
||||
},
|
||||
"Flux": {
|
||||
"flux_canny_model_example": "검출된 경계선으로 이미지를 생성합니다.",
|
||||
"flux_depth_lora_example": "깊이 인식 LoRA 를 이용해 이미지를 생성합니다.",
|
||||
"flux_dev_checkpoint_example": "FLUX Dev 모델로 이미지를 생성합니다.",
|
||||
"flux_canny_model_example": "에지 감지로부터 이미지를 생성합니다.",
|
||||
"flux_depth_lora_example": "깊이 인식 LoRA로 이미지를 생성합니다.",
|
||||
"flux_dev_checkpoint_example": "Flux 개발 모델로 이미지를 생성합니다.",
|
||||
"flux_fill_inpaint_example": "이미지의 누락된 부분을 채웁니다.",
|
||||
"flux_fill_outpaint_example": "FLUX 아웃페인팅으로 이미지를 확장합니다.",
|
||||
"flux_fill_outpaint_example": "Flux 아웃페인팅으로 이미지를 확장합니다.",
|
||||
"flux_redux_model_example": "참조 이미지의 스타일을 가이드 이미지 생성에 적용합니다.",
|
||||
"flux_schnell": "FLUX Schnell 모델로 이미지를 빠르게 생성합니다."
|
||||
"flux_schnell": "Flux Schnell로 이미지를 빠르게 생성합니다."
|
||||
},
|
||||
"Image": {
|
||||
"hidream_e1_full": "HiDream E1 모델로 이미지를 편집합니다.",
|
||||
"hidream_i1_dev": "HiDream I1 Dev 모델로 이미지를 생성합니다.",
|
||||
"hidream_i1_fast": "HiDream I1 Fast 모델로 이미지를 빠르게 생성합니다.",
|
||||
"hidream_i1_full": "HiDream I1 Full 모델로 이미지를 생성합니다.",
|
||||
"sd3_5_large_blur": "SD 3.5 모델로 흐릿한 참조 이미지에서 이미지를 생성합니다.",
|
||||
"sd3_5_large_canny_controlnet_example": "Canny 에지 이미지를 통해 SD 3.5 모델 이미지 생성을 가이드합니다.",
|
||||
"sd3_5_large_depth": "깊이 인식 이미지를 통해 SD 3.5 모델 이미지 생성을 가이드합니다.",
|
||||
"sd3_5_simple_example": "SD 3.5 모델로 이미지를 생성합니다.",
|
||||
"hidream_e1_full": "HiDream E1로 이미지를 편집합니다.",
|
||||
"hidream_i1_dev": "HiDream I1 Dev로 이미지를 생성합니다.",
|
||||
"hidream_i1_fast": "HiDream I1로 이미지를 빠르게 생성합니다.",
|
||||
"hidream_i1_full": "HiDream I1로 이미지를 생성합니다.",
|
||||
"sd3_5_large_blur": "SD 3.5로 흐릿한 참조 이미지에서 이미지를 생성합니다.",
|
||||
"sd3_5_large_canny_controlnet_example": "SD 3.5에서 에지 감지로 이미지 생성을 가이드합니다.",
|
||||
"sd3_5_large_depth": "SD 3.5로 깊이 인식 이미지를 생성합니다.",
|
||||
"sd3_5_simple_example": "SD 3.5로 이미지를 생성합니다.",
|
||||
"sdxl_refiner_prompt_example": "SDXL 결과물을 리파이너로 향상시킵니다.",
|
||||
"sdxl_revision_text_prompts": "참조 이미지의 개념을 SDXL 이미지 생성에 적용합니다.",
|
||||
"sdxl_revision_zero_positive": "참조 이미지와 함께 텍스트 프롬프트를 추가하여 SDXL 이미지 생성을 가이드합니다.",
|
||||
"sdxl_simple_example": "SDXL 모델로 고품질 이미지를 생성합니다.",
|
||||
"sdxlturbo_example": "SDXL Turbo 모델로 1 스텝으로 이미지를 생성합니다."
|
||||
"sdxl_simple_example": "SDXL로 고품질 이미지를 생성합니다.",
|
||||
"sdxlturbo_example": "SDXL Turbo로 한 번에 이미지를 생성합니다."
|
||||
},
|
||||
"Image API": {
|
||||
"api-openai-dall-e-2-inpaint": "Dall-E 2 API로 이미지를 인페인팅합니다.",
|
||||
@@ -1355,10 +1297,8 @@
|
||||
"noTemplatesToExport": "내보낼 템플릿이 없습니다",
|
||||
"nodeDefinitionsUpdated": "노드 정의가 업데이트되었습니다",
|
||||
"nothingToGroup": "그룹화할 항목이 없습니다",
|
||||
"nothingToQueue": "대기열에 추가할 항목이 없습니다",
|
||||
"pendingTasksDeleted": "보류 중인 작업이 삭제되었습니다",
|
||||
"pleaseSelectNodesToGroup": "그룹을 만들기 위해 노드(또는 다른 그룹)를 선택해 주세요",
|
||||
"pleaseSelectOutputNodes": "출력 노드를 선택해 주세요",
|
||||
"unableToGetModelFilePath": "모델 파일 경로를 가져올 수 없습니다",
|
||||
"unauthorizedDomain": "귀하의 도메인 {domain}은(는) 이 서비스를 사용할 수 있는 권한이 없습니다. 도메인을 허용 목록에 추가하려면 {email}로 문의해 주세요.",
|
||||
"updateRequested": "업데이트 요청됨",
|
||||
@@ -1381,7 +1321,6 @@
|
||||
},
|
||||
"validation": {
|
||||
"invalidEmail": "유효하지 않은 이메일 주소",
|
||||
"length": "{length}자여야 합니다",
|
||||
"maxLength": "{length}자를 초과할 수 없습니다",
|
||||
"minLength": "{length}자 이상이어야 합니다",
|
||||
"password": {
|
||||
@@ -1394,7 +1333,6 @@
|
||||
"uppercase": "적어도 하나의 대문자를 포함해야 합니다"
|
||||
},
|
||||
"personalDataConsentRequired": "개인 데이터 처리에 동의해야 합니다.",
|
||||
"prefix": "{prefix}(으)로 시작해야 합니다",
|
||||
"required": "필수"
|
||||
},
|
||||
"welcome": {
|
||||
|
||||
@@ -155,9 +155,6 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "Очередь запросов (передняя)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "Добавить выбранные выходные узлы в очередь"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "Повторить"
|
||||
},
|
||||
|
||||
@@ -29,24 +29,6 @@
|
||||
"title": "Требуется вход для использования API Nodes"
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
"cleared": "API-ключ удалён",
|
||||
"clearedDetail": "Ваш API-ключ был успешно удалён",
|
||||
"description": "Используйте ваш Comfy API-ключ для активации API-узлов",
|
||||
"error": "Недействительный API-ключ",
|
||||
"generateKey": "Получить здесь",
|
||||
"helpText": "Нужен API-ключ?",
|
||||
"invalid": "Недействительный API-ключ",
|
||||
"invalidDetail": "Пожалуйста, введите действительный API-ключ",
|
||||
"label": "API-ключ",
|
||||
"placeholder": "Введите ваш API-ключ",
|
||||
"storageFailed": "Не удалось сохранить API-ключ",
|
||||
"storageFailedDetail": "Пожалуйста, попробуйте еще раз.",
|
||||
"stored": "API-ключ сохранён",
|
||||
"storedDetail": "Ваш API-ключ был успешно сохранён",
|
||||
"title": "API-ключ",
|
||||
"whitelistInfo": "О не включённых в белый список сайтах"
|
||||
},
|
||||
"login": {
|
||||
"andText": "и",
|
||||
"confirmPasswordLabel": "Подтвердите пароль",
|
||||
@@ -61,7 +43,6 @@
|
||||
"loginWithGithub": "Войти через Github",
|
||||
"loginWithGoogle": "Войти через Google",
|
||||
"newUser": "Вы здесь впервые?",
|
||||
"noAssociatedUser": "С предоставленным API-ключом не связан ни один пользователь Comfy",
|
||||
"orContinueWith": "Или продолжить с",
|
||||
"passwordLabel": "Пароль",
|
||||
"passwordPlaceholder": "Введите ваш пароль",
|
||||
@@ -75,7 +56,6 @@
|
||||
"termsLink": "Условиями использования",
|
||||
"termsText": "Нажимая \"Далее\" или \"Зарегистрироваться\", вы соглашаетесь с нашими",
|
||||
"title": "Войдите в свой аккаунт",
|
||||
"useApiKey": "Comfy API-ключ",
|
||||
"userAvatar": "Аватар пользователя"
|
||||
},
|
||||
"passwordUpdate": {
|
||||
@@ -151,7 +131,6 @@
|
||||
"Unpin": "Открепить"
|
||||
},
|
||||
"credits": {
|
||||
"apiPricing": "Цены на API",
|
||||
"credits": "Кредиты",
|
||||
"faqs": "Часто задаваемые вопросы",
|
||||
"invoiceHistory": "История счетов",
|
||||
@@ -170,10 +149,8 @@
|
||||
"yourCreditBalance": "Ваш баланс кредитов"
|
||||
},
|
||||
"dataTypes": {
|
||||
"*": "*",
|
||||
"AUDIO": "АУДИО",
|
||||
"BOOLEAN": "БУЛЕВО",
|
||||
"CAMERA_CONTROL": "УПРАВЛЕНИЕ_КАМЕРОЙ",
|
||||
"CLIP": "CLIP",
|
||||
"CLIP_VISION": "CLIP_VISION",
|
||||
"CLIP_VISION_OUTPUT": "CLIP_VISION_OUTPUT",
|
||||
@@ -190,29 +167,20 @@
|
||||
"INT": "ЦЕЛОЕ",
|
||||
"LATENT": "ЛАТЕНТНЫЙ",
|
||||
"LATENT_OPERATION": "ЛАТЕНТНАЯ_ОПЕРАЦИЯ",
|
||||
"LOAD3D_CAMERA": "ЗАГРУЗИТЬ3D_КАМЕРУ",
|
||||
"LOAD_3D": "ЗАГРУЗИТЬ_3D",
|
||||
"LOAD_3D_ANIMATION": "ЗАГРУЗИТЬ_3D_АНИМАЦИЮ",
|
||||
"LUMA_CONCEPTS": "LUMA_CONCEPTS",
|
||||
"LUMA_REF": "LUMA_REF",
|
||||
"MASK": "МАСКА",
|
||||
"MESH": "СЕТКА",
|
||||
"MODEL": "МОДЕЛЬ",
|
||||
"NOISE": "ШУМ",
|
||||
"PHOTOMAKER": "PHOTOMAKER",
|
||||
"PIXVERSE_TEMPLATE": "ШАБЛОН_PIXVERSE",
|
||||
"RECRAFT_COLOR": "RECRAFT_ЦВЕТ",
|
||||
"RECRAFT_CONTROLS": "RECRAFT_УПРАВЛЕНИЯ",
|
||||
"RECRAFT_V3_STYLE": "RECRAFT_V3_СТИЛЬ",
|
||||
"SAMPLER": "СЭМПЛЕР",
|
||||
"SIGMAS": "СИГМЫ",
|
||||
"STRING": "СТРОКА",
|
||||
"STYLE_MODEL": "МОДЕЛЬ_СТИЛЯ",
|
||||
"SVG": "SVG",
|
||||
"TIMESTEPS_RANGE": "ДИАПАЗОН_ВРЕМЕННЫХ_ШАГОВ",
|
||||
"UPSCALE_MODEL": "МОДЕЛЬ_АПСКЕЙЛА",
|
||||
"VAE": "VAE",
|
||||
"VIDEO": "ВИДЕО",
|
||||
"VOXEL": "ВОКСЕЛ",
|
||||
"WEBCAM": "ВЕБ-КАМЕРА"
|
||||
},
|
||||
@@ -727,7 +695,6 @@
|
||||
"Previous Opened Workflow": "Предыдущий открытый рабочий процесс",
|
||||
"Queue Prompt": "Запрос в очереди",
|
||||
"Queue Prompt (Front)": "Запрос в очереди (спереди)",
|
||||
"Queue Selected Output Nodes": "Добавить выбранные выходные узлы в очередь",
|
||||
"Quit": "Выйти",
|
||||
"Redo": "Повторить",
|
||||
"Refresh Node Definitions": "Обновить определения нод",
|
||||
@@ -764,22 +731,10 @@
|
||||
"nodeCategories": {
|
||||
"3d": "3d",
|
||||
"3d_models": "3d_модели",
|
||||
"BFL": "BFL",
|
||||
"Ideogram": "Ideogram",
|
||||
"Kling": "Kling",
|
||||
"Luma": "Luma",
|
||||
"MiniMax": "MiniMax",
|
||||
"OpenAI": "OpenAI",
|
||||
"Pika": "Pika",
|
||||
"PixVerse": "PixVerse",
|
||||
"Recraft": "Recraft",
|
||||
"Stability AI": "Stability AI",
|
||||
"Veo": "Veo",
|
||||
"_for_testing": "_для_тестирования",
|
||||
"advanced": "расширенный",
|
||||
"animation": "анимация",
|
||||
"api": "api",
|
||||
"api node": "api-узел",
|
||||
"attention_experiments": "эксперименты_внимания",
|
||||
"audio": "аудио",
|
||||
"batch": "пакет",
|
||||
@@ -804,7 +759,6 @@
|
||||
"instructpix2pix": "instructpix2pix",
|
||||
"latent": "латентный",
|
||||
"loaders": "загрузчики",
|
||||
"lotus": "lotus",
|
||||
"ltxv": "ltxv",
|
||||
"mask": "маска",
|
||||
"model": "модель",
|
||||
@@ -816,12 +770,10 @@
|
||||
"photomaker": "photomaker",
|
||||
"postprocessing": "постобработка",
|
||||
"preprocessors": "предобработчики",
|
||||
"primitive": "примитив",
|
||||
"samplers": "семплеры",
|
||||
"sampling": "выборка",
|
||||
"schedulers": "schedulers",
|
||||
"scheduling": "scheduling",
|
||||
"sd": "sd",
|
||||
"sd3": "sd3",
|
||||
"sigmas": "сигмы",
|
||||
"stable_cascade": "стабильная_каскадная",
|
||||
@@ -830,10 +782,6 @@
|
||||
"unet": "unet",
|
||||
"upscale_diffusion": "диффузии_апскейла",
|
||||
"upscaling": "апскейл",
|
||||
"utils": "утилиты",
|
||||
"v1": "v1",
|
||||
"v2": "v2",
|
||||
"v3": "v3",
|
||||
"video": "видео",
|
||||
"video_models": "видеомодели"
|
||||
},
|
||||
@@ -853,12 +801,6 @@
|
||||
},
|
||||
"title": "Ваше устройство не поддерживается"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "Выходные узлы не выбраны",
|
||||
"tooltip": "Выполнить для выбранных выходных узлов (выделены оранжевой рамкой)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "Вы изменили следующие конфигурации сервера. Перезапустите, чтобы применить изменения.",
|
||||
"restart": "Перезапустить",
|
||||
@@ -1355,10 +1297,8 @@
|
||||
"noTemplatesToExport": "Нет шаблонов для экспорта",
|
||||
"nodeDefinitionsUpdated": "Определения узлов обновлены",
|
||||
"nothingToGroup": "Нечего группировать",
|
||||
"nothingToQueue": "Нет заданий в очереди",
|
||||
"pendingTasksDeleted": "Ожидающие задачи удалены",
|
||||
"pleaseSelectNodesToGroup": "Пожалуйста, выберите узлы (или другие группы) для создания группы",
|
||||
"pleaseSelectOutputNodes": "Пожалуйста, выберите выходные узлы",
|
||||
"unableToGetModelFilePath": "Не удалось получить путь к файлу модели",
|
||||
"unauthorizedDomain": "Ваш домен {domain} не авторизован для использования этого сервиса. Пожалуйста, свяжитесь с {email}, чтобы добавить ваш домен в белый список.",
|
||||
"updateRequested": "Запрошено обновление",
|
||||
@@ -1381,7 +1321,6 @@
|
||||
},
|
||||
"validation": {
|
||||
"invalidEmail": "Недействительный адрес электронной почты",
|
||||
"length": "Должно быть {length} символов",
|
||||
"maxLength": "Должно быть не более {length} символов",
|
||||
"minLength": "Должно быть не менее {length} символов",
|
||||
"password": {
|
||||
@@ -1394,7 +1333,6 @@
|
||||
"uppercase": "Должен содержать хотя бы одну заглавную букву"
|
||||
},
|
||||
"personalDataConsentRequired": "Вы должны согласиться на обработку ваших персональных данных.",
|
||||
"prefix": "Должно начинаться с {prefix}",
|
||||
"required": "Обязательно"
|
||||
},
|
||||
"welcome": {
|
||||
|
||||
@@ -155,9 +155,6 @@
|
||||
"Comfy_QueuePromptFront": {
|
||||
"label": "执行提示词(前端)"
|
||||
},
|
||||
"Comfy_QueueSelectedOutputNodes": {
|
||||
"label": "队列所选输出节点"
|
||||
},
|
||||
"Comfy_Redo": {
|
||||
"label": "重做"
|
||||
},
|
||||
|
||||
@@ -29,24 +29,6 @@
|
||||
"title": "使用API节点需要登录"
|
||||
},
|
||||
"auth": {
|
||||
"apiKey": {
|
||||
"cleared": "API 密钥已清除",
|
||||
"clearedDetail": "您的 API 密钥已成功清除",
|
||||
"description": "使用您的 Comfy API 密钥以启用 API 节点",
|
||||
"error": "无效的 API 密钥",
|
||||
"generateKey": "在这里获取",
|
||||
"helpText": "需要 API 密钥?",
|
||||
"invalid": "无效的 API 密钥",
|
||||
"invalidDetail": "请输入有效的 API 密钥",
|
||||
"label": "API 密钥",
|
||||
"placeholder": "请输入您的 API 密钥",
|
||||
"storageFailed": "API 密钥存储失败",
|
||||
"storageFailedDetail": "请重试。",
|
||||
"stored": "API 密钥已存储",
|
||||
"storedDetail": "您的 API 密钥已成功存储",
|
||||
"title": "API 密钥",
|
||||
"whitelistInfo": "关于非白名单网站"
|
||||
},
|
||||
"login": {
|
||||
"andText": "和",
|
||||
"confirmPasswordLabel": "确认密码",
|
||||
@@ -61,7 +43,6 @@
|
||||
"loginWithGithub": "使用Github登录",
|
||||
"loginWithGoogle": "使用Google登录",
|
||||
"newUser": "新来的?",
|
||||
"noAssociatedUser": "所提供的 API 密钥未关联任何 Comfy 用户",
|
||||
"orContinueWith": "或者继续使用",
|
||||
"passwordLabel": "密码",
|
||||
"passwordPlaceholder": "输入您的密码",
|
||||
@@ -75,7 +56,6 @@
|
||||
"termsLink": "使用条款",
|
||||
"termsText": "点击“下一步”或“注册”即表示您同意我们的",
|
||||
"title": "登录您的账户",
|
||||
"useApiKey": "Comfy API 密钥",
|
||||
"userAvatar": "用户头像"
|
||||
},
|
||||
"passwordUpdate": {
|
||||
@@ -151,7 +131,6 @@
|
||||
"Unpin": "取消固定"
|
||||
},
|
||||
"credits": {
|
||||
"apiPricing": "API 价格",
|
||||
"credits": "积分",
|
||||
"faqs": "常见问题",
|
||||
"invoiceHistory": "发票历史",
|
||||
@@ -170,10 +149,8 @@
|
||||
"yourCreditBalance": "您的积分余额"
|
||||
},
|
||||
"dataTypes": {
|
||||
"*": "*",
|
||||
"AUDIO": "音频",
|
||||
"BOOLEAN": "布尔",
|
||||
"CAMERA_CONTROL": "相机控制",
|
||||
"CLIP": "CLIP",
|
||||
"CLIP_VISION": "CLIP视觉",
|
||||
"CLIP_VISION_OUTPUT": "CLIP视觉输出",
|
||||
@@ -190,29 +167,20 @@
|
||||
"INT": "整数",
|
||||
"LATENT": "Latent",
|
||||
"LATENT_OPERATION": "Latent操作",
|
||||
"LOAD3D_CAMERA": "加载3D相机",
|
||||
"LOAD_3D": "加载3D",
|
||||
"LOAD_3D_ANIMATION": "加载3D动画",
|
||||
"LUMA_CONCEPTS": "Luma 概念",
|
||||
"LUMA_REF": "Luma 参考",
|
||||
"MASK": "遮罩",
|
||||
"MESH": "网格",
|
||||
"MODEL": "模型",
|
||||
"NOISE": "噪波",
|
||||
"PHOTOMAKER": "PhotoMaker",
|
||||
"PIXVERSE_TEMPLATE": "Pixverse 模板",
|
||||
"RECRAFT_COLOR": "Recraft 颜色",
|
||||
"RECRAFT_CONTROLS": "Recraft 控件",
|
||||
"RECRAFT_V3_STYLE": "Recraft V3 风格",
|
||||
"SAMPLER": "采样器",
|
||||
"SIGMAS": "Sigmas",
|
||||
"STRING": "字符串",
|
||||
"STYLE_MODEL": "风格模型",
|
||||
"SVG": "SVG",
|
||||
"TIMESTEPS_RANGE": "时间间隔范围",
|
||||
"UPSCALE_MODEL": "放大模型",
|
||||
"VAE": "VAE",
|
||||
"VIDEO": "视频",
|
||||
"VOXEL": "体素",
|
||||
"WEBCAM": "摄像头"
|
||||
},
|
||||
@@ -727,7 +695,6 @@
|
||||
"Previous Opened Workflow": "上一个打开的工作流",
|
||||
"Queue Prompt": "执行提示词",
|
||||
"Queue Prompt (Front)": "执行提示词 (优先执行)",
|
||||
"Queue Selected Output Nodes": "将所选输出节点加入队列",
|
||||
"Quit": "退出",
|
||||
"Redo": "重做",
|
||||
"Refresh Node Definitions": "刷新节点定义",
|
||||
@@ -764,22 +731,10 @@
|
||||
"nodeCategories": {
|
||||
"3d": "3d",
|
||||
"3d_models": "3D模型",
|
||||
"BFL": "BFL",
|
||||
"Ideogram": "Ideogram",
|
||||
"Kling": "Kling",
|
||||
"Luma": "Luma",
|
||||
"MiniMax": "MiniMax",
|
||||
"OpenAI": "OpenAI",
|
||||
"Pika": "Pika",
|
||||
"PixVerse": "PixVerse",
|
||||
"Recraft": "Recraft",
|
||||
"Stability AI": "Stability AI",
|
||||
"Veo": "Veo",
|
||||
"_for_testing": "_用于测试",
|
||||
"advanced": "高级",
|
||||
"animation": "动画",
|
||||
"api": "API",
|
||||
"api node": "api 节点",
|
||||
"attention_experiments": "注意力实验",
|
||||
"audio": "音频",
|
||||
"batch": "批处理",
|
||||
@@ -804,7 +759,6 @@
|
||||
"instructpix2pix": "InstructPix2Pix",
|
||||
"latent": "Latent",
|
||||
"loaders": "加载器",
|
||||
"lotus": "lotus",
|
||||
"ltxv": "LTXV",
|
||||
"mask": "遮罩",
|
||||
"model": "模型",
|
||||
@@ -816,12 +770,10 @@
|
||||
"photomaker": "PhotoMaker",
|
||||
"postprocessing": "后处理",
|
||||
"preprocessors": "预处理器",
|
||||
"primitive": "基础",
|
||||
"samplers": "采样器",
|
||||
"sampling": "采样",
|
||||
"schedulers": "调度器",
|
||||
"scheduling": "调度",
|
||||
"sd": "sd",
|
||||
"sd3": "SD3",
|
||||
"sigmas": "Sigmas",
|
||||
"stable_cascade": "StableCascade",
|
||||
@@ -830,10 +782,6 @@
|
||||
"unet": "U-Net",
|
||||
"upscale_diffusion": "放大扩散",
|
||||
"upscaling": "放大",
|
||||
"utils": "工具",
|
||||
"v1": "v1",
|
||||
"v2": "v2",
|
||||
"v3": "v3",
|
||||
"video": "视频",
|
||||
"video_models": "视频模型"
|
||||
},
|
||||
@@ -853,12 +801,6 @@
|
||||
},
|
||||
"title": "您的设备不受支持"
|
||||
},
|
||||
"selectionToolbox": {
|
||||
"executeButton": {
|
||||
"disabledTooltip": "未选择输出节点",
|
||||
"tooltip": "执行到选定的输出节点(用橙色边框高亮显示)"
|
||||
}
|
||||
},
|
||||
"serverConfig": {
|
||||
"modifiedConfigs": "您已修改以下服务器配置。重启以应用更改。",
|
||||
"restart": "重启",
|
||||
@@ -1355,10 +1297,8 @@
|
||||
"noTemplatesToExport": "没有模板可以导出",
|
||||
"nodeDefinitionsUpdated": "节点定义已更新",
|
||||
"nothingToGroup": "没有可分组的内容",
|
||||
"nothingToQueue": "没有可加入队列的内容",
|
||||
"pendingTasksDeleted": "待处理任务已删除",
|
||||
"pleaseSelectNodesToGroup": "请选取节点(或其他组)以创建分组",
|
||||
"pleaseSelectOutputNodes": "请选择输出节点",
|
||||
"unableToGetModelFilePath": "无法获取模型文件路径",
|
||||
"unauthorizedDomain": "您的域名 {domain} 未被授权使用此服务。请联系 {email} 将您的域名添加到白名单。",
|
||||
"updateRequested": "已请求更新",
|
||||
@@ -1381,7 +1321,6 @@
|
||||
},
|
||||
"validation": {
|
||||
"invalidEmail": "无效的电子邮件地址",
|
||||
"length": "必须为{length}个字符",
|
||||
"maxLength": "不能超过{length}个字符",
|
||||
"minLength": "必须至少有{length}个字符",
|
||||
"password": {
|
||||
@@ -1394,7 +1333,6 @@
|
||||
"uppercase": "必须包含至少一个大写字母"
|
||||
},
|
||||
"personalDataConsentRequired": "您必须同意处理您的个人数据。",
|
||||
"prefix": "必须以 {prefix} 开头",
|
||||
"required": "必填"
|
||||
},
|
||||
"welcome": {
|
||||
|
||||
@@ -82,11 +82,6 @@ const zExecutionErrorWsMessage = zExecutionWsMessageBase.extend({
|
||||
current_outputs: z.any()
|
||||
})
|
||||
|
||||
const zProgressTextWsMessage = z.object({
|
||||
nodeId: zNodeId,
|
||||
text: z.string()
|
||||
})
|
||||
|
||||
const zTerminalSize = z.object({
|
||||
cols: z.number(),
|
||||
row: z.number()
|
||||
@@ -119,7 +114,6 @@ export type ExecutionInterruptedWsMessage = z.infer<
|
||||
>
|
||||
export type ExecutionErrorWsMessage = z.infer<typeof zExecutionErrorWsMessage>
|
||||
export type LogsWsMessage = z.infer<typeof zLogsWsMessage>
|
||||
export type ProgressTextWsMessage = z.infer<typeof zProgressTextWsMessage>
|
||||
// End of ws messages
|
||||
|
||||
const zPromptInputItem = z.object({
|
||||
@@ -457,7 +451,7 @@ const zSettings = z.object({
|
||||
'Comfy.Load3D.CameraType': z.enum(['perspective', 'orthographic']),
|
||||
'pysssss.SnapToGrid': z.boolean(),
|
||||
/** VHS setting is used for queue video preview support. */
|
||||
'VHS.AdvancedPreviews': z.string(),
|
||||
'VHS.AdvancedPreviews': z.boolean(),
|
||||
/** Settings used for testing */
|
||||
'test.setting': z.any(),
|
||||
'main.sub.setting.name': z.any(),
|
||||
|
||||
@@ -216,7 +216,6 @@ const zComfyNode = z
|
||||
|
||||
const zGroup = z
|
||||
.object({
|
||||
id: z.number().optional(),
|
||||
title: z.string(),
|
||||
bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
|
||||
color: z.string().optional(),
|
||||
|
||||
@@ -36,7 +36,7 @@ export const zNumericInputOptions = zBaseInputOptions.extend({
|
||||
min: z.number().optional(),
|
||||
max: z.number().optional(),
|
||||
step: z.number().optional(),
|
||||
/** Note: Many node authors are using INT/FLOAT to pass list of INT/FLOAT. */
|
||||
// Note: Many node authors are using INT/FLOAT to pass list of INT/FLOAT.
|
||||
default: z.union([z.number(), z.array(z.number())]).optional(),
|
||||
display: z.enum(['slider', 'number', 'knob']).optional()
|
||||
})
|
||||
|
||||
@@ -2,16 +2,6 @@ import { z } from 'zod'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
|
||||
export const apiKeySchema = z.object({
|
||||
apiKey: z
|
||||
.string()
|
||||
.trim()
|
||||
.startsWith('comfyui-', t('validation.prefix', { prefix: 'comfyui-' }))
|
||||
.length(72, t('validation.length', { length: 72 }))
|
||||
})
|
||||
|
||||
export type ApiKeyData = z.infer<typeof apiKeySchema>
|
||||
|
||||
export const signInSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
|
||||
@@ -14,7 +14,6 @@ import type {
|
||||
LogsRawResponse,
|
||||
LogsWsMessage,
|
||||
PendingTaskItem,
|
||||
ProgressTextWsMessage,
|
||||
ProgressWsMessage,
|
||||
PromptResponse,
|
||||
RunningTaskItem,
|
||||
@@ -59,21 +58,6 @@ interface QueuePromptRequestBody {
|
||||
* ```
|
||||
*/
|
||||
auth_token_comfy_org?: string
|
||||
/**
|
||||
* The auth token for the comfy org account if the user is logged in.
|
||||
*
|
||||
* Backend node can access this token by specifying following input:
|
||||
* ```python
|
||||
* def INPUT_TYPES(s):
|
||||
* return {
|
||||
* "hidden": { "api_key": "API_KEY_COMFY_ORG" }
|
||||
* }
|
||||
*
|
||||
* def execute(self, api_key: str):
|
||||
* print(f"API Key: {api_key}")
|
||||
* ```
|
||||
*/
|
||||
api_key_comfy_org?: string
|
||||
}
|
||||
front?: boolean
|
||||
number?: number
|
||||
@@ -102,7 +86,6 @@ interface BackendApiCalls {
|
||||
logs: LogsWsMessage
|
||||
/** Binary preview/progress data */
|
||||
b_preview: Blob
|
||||
progress_text: ProgressTextWsMessage
|
||||
}
|
||||
|
||||
/** Dictionary of all api calls */
|
||||
@@ -245,10 +228,6 @@ export class ComfyApi extends EventTarget {
|
||||
* custom nodes are patched.
|
||||
*/
|
||||
authToken?: string
|
||||
/**
|
||||
* The API key for the comfy org account if the user logged in via API key.
|
||||
*/
|
||||
apiKey?: string
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
@@ -401,21 +380,12 @@ export class ComfyApi extends EventTarget {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
const view = new DataView(event.data)
|
||||
const eventType = view.getUint32(0)
|
||||
const imageType = view.getUint32(4)
|
||||
const imageData = event.data.slice(8)
|
||||
|
||||
let imageMime
|
||||
switch (eventType) {
|
||||
case 3:
|
||||
const decoder = new TextDecoder()
|
||||
const data = event.data.slice(4)
|
||||
const nodeIdLength = view.getUint32(4)
|
||||
this.dispatchCustomEvent('progress_text', {
|
||||
nodeId: decoder.decode(data.slice(4, 4 + nodeIdLength)),
|
||||
text: decoder.decode(data.slice(4 + nodeIdLength))
|
||||
})
|
||||
break
|
||||
case 1:
|
||||
const imageType = view.getUint32(4)
|
||||
const imageData = event.data.slice(8)
|
||||
switch (imageType) {
|
||||
case 2:
|
||||
imageMime = 'image/png'
|
||||
@@ -575,7 +545,6 @@ export class ComfyApi extends EventTarget {
|
||||
prompt,
|
||||
extra_data: {
|
||||
auth_token_comfy_org: this.authToken,
|
||||
api_key_comfy_org: this.apiKey,
|
||||
extra_pnginfo: { workflow }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import {
|
||||
LGraphNode,
|
||||
LiteGraph
|
||||
} from '@comfyorg/litegraph'
|
||||
import type { Vector2 } from '@comfyorg/litegraph'
|
||||
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import type { IWidget, Vector2 } from '@comfyorg/litegraph'
|
||||
import _ from 'lodash'
|
||||
import type { ToastMessageOptions } from 'primevue/toast'
|
||||
import { reactive } from 'vue'
|
||||
@@ -25,22 +24,14 @@ import {
|
||||
type ModelFile,
|
||||
type NodeId
|
||||
} from '@/schemas/comfyWorkflowSchema'
|
||||
import {
|
||||
type ComfyNodeDef as ComfyNodeDefV1,
|
||||
isComboInputSpecV1,
|
||||
isComboInputSpecV2
|
||||
} from '@/schemas/nodeDefSchema'
|
||||
import type { ComfyNodeDef as ComfyNodeDefV1 } from '@/schemas/nodeDefSchema'
|
||||
import { getFromWebmFile } from '@/scripts/metadata/ebml'
|
||||
import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf'
|
||||
import { getFromIsobmffFile } from '@/scripts/metadata/isobmff'
|
||||
import { getMp3Metadata } from '@/scripts/metadata/mp3'
|
||||
import { getOggMetadata } from '@/scripts/metadata/ogg'
|
||||
import { getSvgMetadata } from '@/scripts/metadata/svg'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useExtensionService } from '@/services/extensionService'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
import { useApiKeyAuthStore } from '@/stores/apiKeyAuthStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useExtensionStore } from '@/stores/extensionStore'
|
||||
@@ -72,6 +63,7 @@ import { deserialiseAndCreate } from '@/utils/vintageClipboard'
|
||||
import { type ComfyApi, PromptExecutionError, api } from './api'
|
||||
import { defaultGraph } from './defaultGraph'
|
||||
import { pruneWidgets } from './domWidget'
|
||||
import { getSvgMetadata } from './metadata/svg'
|
||||
import {
|
||||
getFlacMetadata,
|
||||
getLatentMetadata,
|
||||
@@ -102,7 +94,7 @@ function sanitizeNodeName(string: string) {
|
||||
}
|
||||
|
||||
type Clipspace = {
|
||||
widgets?: Pick<IBaseWidget, 'type' | 'name' | 'value'>[] | null
|
||||
widgets?: Pick<IWidget, 'type' | 'name' | 'value'>[] | null
|
||||
imgs?: HTMLImageElement[] | null
|
||||
original_imgs?: HTMLImageElement[] | null
|
||||
images?: any[] | null
|
||||
@@ -114,11 +106,7 @@ export class ComfyApp {
|
||||
/**
|
||||
* List of entries to queue
|
||||
*/
|
||||
#queueItems: {
|
||||
number: number
|
||||
batchCount: number
|
||||
queueNodeIds?: NodeId[]
|
||||
}[] = []
|
||||
#queueItems: { number: number; batchCount: number }[] = []
|
||||
/**
|
||||
* If the queue is currently being processed
|
||||
*/
|
||||
@@ -399,6 +387,7 @@ export class ComfyApp {
|
||||
const index = node.widgets.findIndex((obj) => obj.name === 'image')
|
||||
if (index >= 0) {
|
||||
if (
|
||||
// @ts-expect-error custom widget type
|
||||
node.widgets[index].type != 'image' &&
|
||||
typeof node.widgets[index].value == 'string' &&
|
||||
clip_image.filename
|
||||
@@ -420,6 +409,7 @@ export class ComfyApp {
|
||||
)
|
||||
if (prop && prop.type != 'button') {
|
||||
if (
|
||||
// @ts-expect-error Custom widget type
|
||||
prop.type != 'image' &&
|
||||
typeof prop.value == 'string' &&
|
||||
// @ts-expect-error Custom widget value
|
||||
@@ -431,6 +421,7 @@ export class ComfyApp {
|
||||
resultItem.filename +
|
||||
(resultItem.type ? ` [${resultItem.type}]` : '')
|
||||
} else {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
prop.value = value
|
||||
prop.callback?.(value)
|
||||
}
|
||||
@@ -1069,21 +1060,13 @@ export class ComfyApp {
|
||||
try {
|
||||
// @ts-expect-error Discrepancies between zod and litegraph - in progress
|
||||
this.graph.configure(graphData)
|
||||
if (restore_view) {
|
||||
if (
|
||||
useSettingStore().get('Comfy.EnableWorkflowViewRestore') &&
|
||||
graphData.extra?.ds
|
||||
) {
|
||||
this.canvas.ds.offset = graphData.extra.ds.offset
|
||||
this.canvas.ds.scale = graphData.extra.ds.scale
|
||||
} else {
|
||||
// @note: Set view after the graph has been rendered once. fitView uses
|
||||
// boundingRect on nodes to calculate the view bounds, which only become
|
||||
// available after the first render.
|
||||
requestAnimationFrame(() => {
|
||||
useLitegraphService().fitView()
|
||||
})
|
||||
}
|
||||
if (
|
||||
restore_view &&
|
||||
useSettingStore().get('Comfy.EnableWorkflowViewRestore') &&
|
||||
graphData.extra?.ds
|
||||
) {
|
||||
this.canvas.ds.offset = graphData.extra.ds.offset
|
||||
this.canvas.ds.scale = graphData.extra.ds.scale
|
||||
}
|
||||
} catch (error) {
|
||||
useDialogService().showErrorDialog(error, {
|
||||
@@ -1118,8 +1101,10 @@ export class ComfyApp {
|
||||
) {
|
||||
if (widget.name == 'control_after_generate') {
|
||||
if (widget.value === true) {
|
||||
// @ts-expect-error string is not assignable to boolean
|
||||
widget.value = 'randomize'
|
||||
} else if (widget.value === false) {
|
||||
// @ts-expect-error string is not assignable to boolean
|
||||
widget.value = 'fixed'
|
||||
}
|
||||
}
|
||||
@@ -1163,22 +1148,14 @@ export class ComfyApp {
|
||||
})
|
||||
}
|
||||
|
||||
async graphToPrompt(
|
||||
graph = this.graph,
|
||||
options: { queueNodeIds?: NodeId[] } = {}
|
||||
) {
|
||||
async graphToPrompt(graph = this.graph) {
|
||||
return graphToPrompt(graph, {
|
||||
sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave'),
|
||||
queueNodeIds: options.queueNodeIds
|
||||
sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave')
|
||||
})
|
||||
}
|
||||
|
||||
async queuePrompt(
|
||||
number: number,
|
||||
batchCount: number = 1,
|
||||
queueNodeIds?: NodeId[]
|
||||
): Promise<boolean> {
|
||||
this.#queueItems.push({ number, batchCount, queueNodeIds })
|
||||
async queuePrompt(number: number, batchCount: number = 1): Promise<boolean> {
|
||||
this.#queueItems.push({ number, batchCount })
|
||||
|
||||
// Only have one action process the items so each one gets a unique seed correctly
|
||||
if (this.#processingQueue) {
|
||||
@@ -1191,24 +1168,21 @@ export class ComfyApp {
|
||||
|
||||
let comfyOrgAuthToken =
|
||||
(await useFirebaseAuthStore().getIdToken()) ?? undefined
|
||||
let comfyOrgApiKey = useApiKeyAuthStore().getApiKey()
|
||||
|
||||
try {
|
||||
while (this.#queueItems.length) {
|
||||
const { number, batchCount, queueNodeIds } = this.#queueItems.pop()!
|
||||
const { number, batchCount } = this.#queueItems.pop()!
|
||||
|
||||
for (let i = 0; i < batchCount; i++) {
|
||||
// Allow widgets to run callbacks before a prompt has been queued
|
||||
// e.g. random seed before every gen
|
||||
executeWidgetsCallback(this.graph.nodes, 'beforeQueued')
|
||||
|
||||
const p = await this.graphToPrompt(this.graph, { queueNodeIds })
|
||||
const p = await this.graphToPrompt()
|
||||
try {
|
||||
api.authToken = comfyOrgAuthToken
|
||||
api.apiKey = comfyOrgApiKey ?? undefined
|
||||
const res = await api.queuePrompt(number, p)
|
||||
delete api.authToken
|
||||
delete api.apiKey
|
||||
executionStore.lastNodeErrors = res.node_errors ?? null
|
||||
if (executionStore.lastNodeErrors?.length) {
|
||||
this.canvas.draw(true, true)
|
||||
@@ -1310,24 +1284,6 @@ export class ComfyApp {
|
||||
} else {
|
||||
this.showErrorOnFileLoad(file)
|
||||
}
|
||||
} else if (file.type === 'audio/mpeg') {
|
||||
const { workflow, prompt } = await getMp3Metadata(file)
|
||||
if (workflow) {
|
||||
this.loadGraphData(workflow, true, true, fileName)
|
||||
} else if (prompt) {
|
||||
this.loadApiJson(prompt, fileName)
|
||||
} else {
|
||||
this.showErrorOnFileLoad(file)
|
||||
}
|
||||
} else if (file.type === 'audio/ogg') {
|
||||
const { workflow, prompt } = await getOggMetadata(file)
|
||||
if (workflow) {
|
||||
this.loadGraphData(workflow, true, true, fileName)
|
||||
} else if (prompt) {
|
||||
this.loadApiJson(prompt, fileName)
|
||||
} else {
|
||||
this.showErrorOnFileLoad(file)
|
||||
}
|
||||
} else if (file.type === 'audio/flac' || file.type === 'audio/x-flac') {
|
||||
const pngInfo = await getFlacMetadata(file)
|
||||
const workflow = pngInfo?.workflow || pngInfo?.Workflow
|
||||
@@ -1400,7 +1356,7 @@ export class ComfyApp {
|
||||
await this.loadGraphData(
|
||||
JSON.parse(readerResult),
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
fileName
|
||||
)
|
||||
}
|
||||
@@ -1576,26 +1532,14 @@ export class ComfyApp {
|
||||
if (!def?.input) continue
|
||||
|
||||
if (node.widgets) {
|
||||
const nodeInputs = def.input
|
||||
for (const widget of node.widgets) {
|
||||
if (widget.type === 'combo') {
|
||||
let inputType: 'required' | 'optional' | undefined
|
||||
if (nodeInputs.required?.[widget.name] !== undefined) {
|
||||
inputType = 'required'
|
||||
} else if (nodeInputs.optional?.[widget.name] !== undefined) {
|
||||
inputType = 'optional'
|
||||
}
|
||||
if (inputType !== undefined) {
|
||||
// Get the input spec associated with the widget
|
||||
const inputSpec = nodeInputs[inputType]?.[widget.name]
|
||||
if (inputSpec) {
|
||||
// Refresh the combo widget's options with the values from the input spec
|
||||
if (isComboInputSpecV2(inputSpec)) {
|
||||
widget.options.values = inputSpec[1]?.options
|
||||
} else if (isComboInputSpecV1(inputSpec)) {
|
||||
widget.options.values = inputSpec[0]
|
||||
}
|
||||
}
|
||||
if (def['input'].required?.[widget.name] !== undefined) {
|
||||
// @ts-expect-error Requires discriminated union
|
||||
widget.options.values = def['input'].required[widget.name][0]
|
||||
} else if (def['input'].optional?.[widget.name] !== undefined) {
|
||||
// @ts-expect-error Requires discriminated union
|
||||
widget.options.values = def['input'].optional[widget.name][0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
|
||||
|
||||
import { api } from './api'
|
||||
import type { ComfyApp } from './app'
|
||||
import { app } from './app'
|
||||
|
||||
function clone<T>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
@@ -37,6 +36,11 @@ export class ChangeTracker {
|
||||
ds?: { scale: number; offset: [number, number] }
|
||||
nodeOutputs?: Record<string, any>
|
||||
|
||||
static app?: ComfyApp
|
||||
get app(): ComfyApp {
|
||||
return ChangeTracker.app!
|
||||
}
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The workflow that this change tracker is tracking
|
||||
@@ -64,18 +68,18 @@ export class ChangeTracker {
|
||||
|
||||
store() {
|
||||
this.ds = {
|
||||
scale: app.canvas.ds.scale,
|
||||
offset: [app.canvas.ds.offset[0], app.canvas.ds.offset[1]]
|
||||
scale: this.app.canvas.ds.scale,
|
||||
offset: [this.app.canvas.ds.offset[0], this.app.canvas.ds.offset[1]]
|
||||
}
|
||||
}
|
||||
|
||||
restore() {
|
||||
if (this.ds) {
|
||||
app.canvas.ds.scale = this.ds.scale
|
||||
app.canvas.ds.offset = this.ds.offset
|
||||
this.app.canvas.ds.scale = this.ds.scale
|
||||
this.app.canvas.ds.offset = this.ds.offset
|
||||
}
|
||||
if (this.nodeOutputs) {
|
||||
app.nodeOutputs = this.nodeOutputs
|
||||
this.app.nodeOutputs = this.nodeOutputs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,8 +105,10 @@ export class ChangeTracker {
|
||||
}
|
||||
|
||||
checkState() {
|
||||
if (!app.graph || this.changeCount) return
|
||||
const currentState = clone(app.graph.serialize()) as ComfyWorkflowJSON
|
||||
if (!this.app.graph || this.changeCount) return
|
||||
// @ts-expect-error zod type issue on ComfyWorkflowJSON. ComfyWorkflowJSON
|
||||
// is stricter than LiteGraph's serialisation schema.
|
||||
const currentState = clone(this.app.graph.serialize()) as ComfyWorkflowJSON
|
||||
if (!this.activeState) {
|
||||
this.activeState = currentState
|
||||
return
|
||||
@@ -126,7 +132,7 @@ export class ChangeTracker {
|
||||
target.push(this.activeState)
|
||||
this.restoringState = true
|
||||
try {
|
||||
await app.loadGraphData(prevState, false, false, this.workflow, {
|
||||
await this.app.loadGraphData(prevState, false, false, this.workflow, {
|
||||
showMissingModelsDialog: false,
|
||||
showMissingNodesDialog: false,
|
||||
checkForRerouteMigration: false
|
||||
@@ -183,11 +189,13 @@ export class ChangeTracker {
|
||||
}
|
||||
}
|
||||
|
||||
static init() {
|
||||
static init(app: ComfyApp) {
|
||||
const getCurrentChangeTracker = () =>
|
||||
useWorkflowStore().activeWorkflow?.changeTracker
|
||||
const checkState = () => getCurrentChangeTracker()?.checkState()
|
||||
|
||||
ChangeTracker.app = app
|
||||
|
||||
let keyIgnored = false
|
||||
window.addEventListener(
|
||||
'keydown',
|
||||
@@ -229,7 +237,7 @@ export class ChangeTracker {
|
||||
if (await changeTracker.undoRedo(e)) return
|
||||
|
||||
// If our active element is some type of input then handle changes after they're done
|
||||
if (ChangeTracker.bindInput(bindInputEl)) return
|
||||
if (ChangeTracker.bindInput(app, bindInputEl)) return
|
||||
logger.debug('checkState on keydown')
|
||||
changeTracker.checkState()
|
||||
})
|
||||
@@ -331,7 +339,7 @@ export class ChangeTracker {
|
||||
})
|
||||
}
|
||||
|
||||
static bindInput(activeEl: Element | null): boolean {
|
||||
static bindInput(_app: ComfyApp, activeEl: Element | null): boolean {
|
||||
if (
|
||||
!activeEl ||
|
||||
activeEl.tagName === 'CANVAS' ||
|
||||
|
||||
@@ -132,12 +132,7 @@ export const defaultGraph: ComfyWorkflowJSON = {
|
||||
],
|
||||
groups: [],
|
||||
config: {},
|
||||
extra: {
|
||||
ds: {
|
||||
offset: [0, 0],
|
||||
scale: 1
|
||||
}
|
||||
},
|
||||
extra: {},
|
||||
version: 0.4
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LGraphNode, LegacyWidget, LiteGraph } from '@comfyorg/litegraph'
|
||||
import { LGraphNode, LiteGraph } from '@comfyorg/litegraph'
|
||||
import type {
|
||||
IBaseWidget,
|
||||
ICustomWidget,
|
||||
IWidget,
|
||||
IWidgetOptions
|
||||
} from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import _ from 'lodash'
|
||||
@@ -12,9 +13,9 @@ import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||
import { generateUUID } from '@/utils/formatUtil'
|
||||
|
||||
export interface BaseDOMWidget<V extends object | string>
|
||||
extends IBaseWidget<V, string, DOMWidgetOptions<V>> {
|
||||
extends ICustomWidget {
|
||||
// ICustomWidget properties
|
||||
type: string
|
||||
type: 'custom'
|
||||
options: DOMWidgetOptions<V>
|
||||
value: V
|
||||
callback?: (value: V) => void
|
||||
@@ -80,23 +81,26 @@ export interface DOMWidgetOptions<V extends object | string>
|
||||
}
|
||||
|
||||
export const isDOMWidget = <T extends HTMLElement, V extends object | string>(
|
||||
widget: IBaseWidget
|
||||
widget: IWidget
|
||||
): widget is DOMWidget<T, V> => 'element' in widget && !!widget.element
|
||||
|
||||
export const isComponentWidget = <V extends object | string>(
|
||||
widget: IBaseWidget
|
||||
widget: IWidget
|
||||
): widget is ComponentWidget<V> => 'component' in widget && !!widget.component
|
||||
|
||||
abstract class BaseDOMWidgetImpl<V extends object | string>
|
||||
extends LegacyWidget<IBaseWidget<V, string, DOMWidgetOptions<V>>>
|
||||
implements BaseDOMWidget<V>
|
||||
{
|
||||
static readonly DEFAULT_MARGIN = 10
|
||||
declare readonly name: string
|
||||
declare readonly options: DOMWidgetOptions<V>
|
||||
declare callback?: (value: V) => void
|
||||
readonly type: 'custom'
|
||||
readonly name: string
|
||||
readonly options: DOMWidgetOptions<V>
|
||||
computedHeight?: number
|
||||
y: number = 0
|
||||
callback?: (value: V) => void
|
||||
|
||||
readonly id: string
|
||||
readonly node: LGraphNode
|
||||
|
||||
constructor(obj: {
|
||||
node: LGraphNode
|
||||
@@ -104,17 +108,20 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
|
||||
type: string
|
||||
options: DOMWidgetOptions<V>
|
||||
}) {
|
||||
const { node, name, type, options } = obj
|
||||
super({ y: 0, name, type, options }, node)
|
||||
// @ts-expect-error custom widget type
|
||||
this.type = obj.type
|
||||
this.name = obj.name
|
||||
this.options = obj.options
|
||||
|
||||
this.id = generateUUID()
|
||||
this.node = obj.node
|
||||
}
|
||||
|
||||
override get value(): V {
|
||||
get value(): V {
|
||||
return this.options.getValue?.() ?? ('' as V)
|
||||
}
|
||||
|
||||
override set value(v: V) {
|
||||
set value(v: V) {
|
||||
this.options.setValue?.(v)
|
||||
this.callback?.(this.value)
|
||||
}
|
||||
@@ -127,7 +134,7 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
|
||||
return !['hidden'].includes(this.type) && this.node.isWidgetVisible(this)
|
||||
}
|
||||
|
||||
override draw(
|
||||
draw(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
_node: LGraphNode,
|
||||
widget_width: number,
|
||||
@@ -152,7 +159,7 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
|
||||
this.options.onDraw?.(this)
|
||||
}
|
||||
|
||||
override onRemove(): void {
|
||||
onRemove(): void {
|
||||
useDomWidgetStore().unregisterWidget(this.id)
|
||||
}
|
||||
}
|
||||
@@ -161,7 +168,7 @@ export class DOMWidgetImpl<T extends HTMLElement, V extends object | string>
|
||||
extends BaseDOMWidgetImpl<V>
|
||||
implements DOMWidget<T, V>
|
||||
{
|
||||
override readonly element: T
|
||||
readonly element: T
|
||||
|
||||
constructor(obj: {
|
||||
node: LGraphNode
|
||||
@@ -175,7 +182,8 @@ export class DOMWidgetImpl<T extends HTMLElement, V extends object | string>
|
||||
}
|
||||
|
||||
/** Extract DOM widget size info */
|
||||
override computeLayoutSize(node: LGraphNode) {
|
||||
computeLayoutSize(node: LGraphNode) {
|
||||
// @ts-expect-error custom widget type
|
||||
if (this.type === 'hidden') {
|
||||
return {
|
||||
minHeight: 0,
|
||||
@@ -239,7 +247,7 @@ export class ComponentWidgetImpl<V extends object | string>
|
||||
this.inputSpec = obj.inputSpec
|
||||
}
|
||||
|
||||
override computeLayoutSize() {
|
||||
computeLayoutSize() {
|
||||
const minHeight = this.options.getMinHeight?.() ?? 50
|
||||
const maxHeight = this.options.getMaxHeight?.()
|
||||
return {
|
||||
@@ -249,7 +257,7 @@ export class ComponentWidgetImpl<V extends object | string>
|
||||
}
|
||||
}
|
||||
|
||||
override serializeValue(): V {
|
||||
serializeValue(): V {
|
||||
return toRaw(this.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { LGraphNode } from '@comfyorg/litegraph'
|
||||
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
|
||||
import { IWidget, LGraphNode } from '@comfyorg/litegraph'
|
||||
|
||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import { useBooleanWidget } from '@/composables/widgets/useBooleanWidget'
|
||||
@@ -11,7 +10,7 @@ const FloatWidget = useFloatWidget()
|
||||
const BooleanWidget = useBooleanWidget()
|
||||
|
||||
function addWidgetFromValue(node: LGraphNode, value: unknown) {
|
||||
let widget: IBaseWidget
|
||||
let widget: IWidget
|
||||
|
||||
if (typeof value === 'string') {
|
||||
widget = StringWidget(node, {
|
||||
|
||||