Compare commits

...

41 Commits

Author SHA1 Message Date
christian-byrne
a7cdb27d48 Remove border style 2025-01-18 16:22:49 -07:00
christian-byrne
a437fe90fc Remove unnecessary dialog ref 2025-01-18 14:55:00 -07:00
christian-byrne
d6c1036c68 Issue report view 2025-01-18 14:51:31 -07:00
Chenlei Hu
1a4e77a3ab [Desktop] Ctrl+w to close workflow tab (#2282) 2025-01-17 20:02:54 -05:00
Chenlei Hu
de570712df 1.7.14 (#2281) 2025-01-17 18:04:43 -05:00
Chenlei Hu
44612e8f97 [Desktop] Add privacy policy link to install view about dialog (#2280)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-17 18:04:08 -05:00
bymyself
3df911c1bf Add consent prompt view (#2268)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-17 17:39:50 -05:00
Chenlei Hu
af26b9ad6d [Desktop] Report completed generation status (#2279) 2025-01-17 17:36:46 -05:00
Chenlei Hu
d503873980 Move queueStore update from QueueSidebarTab to GraphView (#2278) 2025-01-17 17:14:22 -05:00
bymyself
842a9f74fc [BrowserTest] Fix flaky gallery test (#2150) 2025-01-17 17:11:49 -05:00
Chenlei Hu
29551a36b3 Add .cursorrules (#2277) 2025-01-17 16:35:25 -05:00
filtered
d6e5c8950c [Desktop] Loading screen (#2274)
Co-authored-by: huchenlei <huchenlei@proton.me>
2025-01-17 11:12:03 -05:00
Chenlei Hu
ad1c1ce9c2 [chore] Update electron-types to 0.4.9 (#2276) 2025-01-17 10:59:41 -05:00
Benjamin Lu
cb9d2c6bae Caching brush settings in mask editor (#2271)
Co-authored-by: Benjamin Lu <templu1107@proton.me>
2025-01-17 10:38:23 -05:00
Chenlei Hu
7fd41eeaba 1.7.13 (#2270) 2025-01-16 11:51:58 -05:00
filtered
79fee6ac72 Fix collapsed node textarea causes UI inconsistency (#2267) 2025-01-16 11:50:53 -05:00
Benjamin Lu
edd58cd153 Hotfix scoped --sidebar-width uasges in maskeditor.ts (#2269)
Co-authored-by: Benjamin Lu <templu1107@proton.me>
2025-01-16 11:36:11 -05:00
Chenlei Hu
e153508955 1.7.12 (#2265) 2025-01-15 20:19:24 -05:00
Chenlei Hu
237fca0bf1 [CodeHealth] Use scoped CSS for SideToolbar (#2264) 2025-01-15 20:16:39 -05:00
Chenlei Hu
65542b885a [Style] Fix root CSS selector (#2263) 2025-01-15 19:44:46 -05:00
Chenlei Hu
f739f704af [CodeHealth] Use scoped CSS in views (#2262) 2025-01-15 19:35:01 -05:00
Chenlei Hu
37abdbe35d [Desktop] Add install screen stepper change metrics (#2261) 2025-01-15 19:27:05 -05:00
Chenlei Hu
ff445f5c95 Apply min col on logs terminal for colab (#2260) 2025-01-15 17:16:33 -05:00
Chenlei Hu
84b652a281 [CodeHealth] Convert useAutoSize to kwargs (#2259) 2025-01-15 17:06:45 -05:00
Chenlei Hu
184291d21b [Settings] Enable Comfy.Window.UnloadConfirmation by default (#2258) 2025-01-15 16:37:11 -05:00
Chenlei Hu
d7fb25a36a Don't prompt unsaved when there is no unsaved workflow on window close (#2257) 2025-01-15 16:34:05 -05:00
Comfy Org PR Bot
c039a60fcc Update locales for node definitions (#2256)
Co-authored-by: huchenlei <20929282+huchenlei@users.noreply.github.com>
Co-authored-by: huchenlei <huchenlei@proton.me>
2025-01-15 16:23:45 -05:00
Chenlei Hu
3b6108c26e Add work dir to i18n-node-defs.yaml (#2255) 2025-01-15 16:19:13 -05:00
Chenlei Hu
49bb247526 1.7.11 (#2250) 2025-01-14 23:34:58 -05:00
bymyself
dd005f5fa5 Allow parent component to pass tags to issue report panel (#2247) 2025-01-14 23:34:11 -05:00
Chenlei Hu
bf90b458d3 [Desktop] Add clarification of migration from existing install (#2249)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-14 23:33:53 -05:00
bymyself
7e78c5b1dc Fix type error in BaseViewTemplate (#2245) 2025-01-14 20:19:35 -05:00
Gremlation
c13190cd07 Fix execution_interrupted (#2244) 2025-01-14 11:46:42 -05:00
filtered
00f031e382 [Refactor] Remove old workarounds (#2242) 2025-01-14 10:52:34 -05:00
filtered
04153caaf5 Fix prettier output in CI does not match IDE (#2243) 2025-01-14 10:51:32 -05:00
Chenlei Hu
210bfdeb7d 1.7.10 (#2241) 2025-01-13 20:25:36 -05:00
bymyself
ce0726d85e Restore all open workflows on load (#2238) 2025-01-13 20:24:40 -05:00
bymyself
dd69f9dc30 [Style] Update workflow template cards style (#2239) 2025-01-13 20:21:03 -05:00
Chenlei Hu
3f261f0e53 [Desktop] Add user journey events to the metrics collection list (UI) (#2237)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-12 17:16:16 -05:00
Chenlei Hu
3b2cc23f65 [Desktop] Mark window style setting as experimental (#2236) 2025-01-12 16:14:51 -05:00
bymyself
c50a86b258 [CI] Fix vite config condition (#2235) 2025-01-12 16:09:46 -05:00
66 changed files with 1371 additions and 270 deletions

43
.cusorrules Normal file
View File

@@ -0,0 +1,43 @@
// Vue 3 Composition API .cursorrules
// Vue 3 Composition API best practices
const vue3CompositionApiBestPractices = [
"Use setup() function for component logic",
"Utilize ref and reactive for reactive state",
"Implement computed properties with computed()",
"Use watch and watchEffect for side effects",
"Implement lifecycle hooks with onMounted, onUpdated, etc.",
"Utilize provide/inject for dependency injection",
]
// Folder structure
const folderStructure = `
src/
components/
constants/
hooks/
views/
stores/
services/
App.vue
main.ts
`;
// Tailwind CSS best practices
const tailwindCssBestPractices = [
"Use Tailwind CSS for styling",
"Implement responsive design with Tailwind CSS",
]
// Additional instructions
const additionalInstructions = `
1. Leverage VueUse functions for performance-enhancing styles
2. Use lodash for utility functions
3. Use TypeScript for type safety
4. Implement proper props and emits definitions
5. Utilize Vue 3's Teleport component when needed
6. Use Suspense for async components
7. Implement proper error handling
8. Follow Vue 3 style guide and naming conventions
9. Use Vite for fast development and building
`;

View File

@@ -42,6 +42,7 @@ jobs:
Automated PR to update locales for node definitions
This PR was created automatically by the frontend update workflow.
branch: update-locales-node-defs-{{ github.event.inputs.trigger_type }}-{{ github.run_id }}
branch: update-locales-node-defs-${{ github.event.inputs.trigger_type }}-${{ github.run_id }}
base: main
labels: dependencies
labels: dependencies
path: ComfyUI_frontend

View File

@@ -6,5 +6,13 @@
"printWidth": 80,
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true
}
"importOrderSortSpecifiers": true,
"overrides": [
{
"files": "*.{js,cjs,mjs,ts,cts,mts,tsx,vue}",
"options": {
"plugins": ["@trivago/prettier-plugin-sort-imports"]
}
}
]
}

View File

@@ -0,0 +1,37 @@
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "CLIPTextEncode",
"pos": [20, 50],
"size": [400, 200],
"flags": { "collapsed": true },
"order": 0,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": null,
"localized_name": "clip"
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": null,
"localized_name": "CONDITIONING"
}
],
"properties": {},
"widgets_values": ["Should not be displayed."]
}
],
"links": [],
"groups": [],
"config": {},
"version": 0.4
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,27 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('DOM Widget', () => {
test('Collapsed multiline textarea is not visible', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('collapsed_multiline')
expect(comfyPage.page.locator('.comfy-multiline-input')).not.toBeVisible()
})
test('Multiline textarea correctly collapses', async ({ comfyPage }) => {
const multilineTextAreas = comfyPage.page.locator('.comfy-multiline-input')
const firstMultiline = multilineTextAreas.first()
const lastMultiline = multilineTextAreas.last()
await expect(firstMultiline).toBeVisible()
await expect(lastMultiline).toBeVisible()
const nodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
for (const node of nodes) {
await node.click('collapse')
}
await expect(firstMultiline).not.toBeVisible()
await expect(lastMultiline).not.toBeVisible()
})
})

View File

@@ -194,6 +194,10 @@ export class QueueSidebarTab extends SidebarTab {
return this.root.locator('.no-results-placeholder')
}
get galleryImage() {
return this.page.locator('.galleria-image')
}
private getToggleExpandButton(isExpanded: boolean) {
const iconSelector = isExpanded ? '.pi-image' : '.pi-images'
return this.root.locator(`.toggle-expanded-button ${iconSelector}`)
@@ -256,14 +260,24 @@ export class QueueSidebarTab extends SidebarTab {
async openTaskPreview(taskIndex: number) {
const previewButton = this.getTaskPreviewButton(taskIndex)
await previewButton.hover()
await previewButton.click()
return this.getGalleryImage(taskIndex).waitFor({ state: 'visible' })
return this.galleryImage.waitFor({ state: 'visible' })
}
getGalleryImage(galleryItemIndex: number) {
// Aria labels of Galleria items are 1-based indices
const galleryLabel = `${galleryItemIndex + 1}`
return this.page.getByLabel(galleryLabel).locator('.galleria-image')
getGalleryImage(imageFilename: string) {
return this.galleryImage.and(this.page.getByAltText(imageFilename))
}
getTaskImage(imageFilename: string) {
return this.tasks.getByAltText(imageFilename)
}
/** Trigger the queue store and tasks to update */
async triggerTasksUpdate() {
await this.page.evaluate(() => {
window['app']['api'].dispatchCustomEvent('status', {
exec_info: { queue_remaining: 0 }
})
})
}
}

View File

@@ -9,6 +9,12 @@ export class Topbar {
.allInnerTexts()
}
async getActiveTabName(): Promise<string> {
return this.page
.locator('.workflow-tabs .p-togglebutton-checked')
.innerText()
}
async openSubmenuMobile() {
await this.page.locator('.p-menubar-mobile .p-menubar-button').click()
}

View File

@@ -132,11 +132,12 @@ export default class TaskHistory {
private addTask(task: HistoryTaskItem) {
setPromptId(task)
setQueueIndex(task)
this.tasks.push(task)
this.tasks.unshift(task) // Tasks are added to the front of the queue
}
clearTasks() {
clearTasks(): this {
this.tasks = []
return this
}
withTask(
@@ -155,7 +156,7 @@ export default class TaskHistory {
/** Repeats the last task in the task history a specified number of times. */
repeat(n: number): this {
for (let i = 0; i < n; i++)
this.addTask(structuredClone(this.tasks.at(-1)) as HistoryTaskItem)
this.addTask(structuredClone(this.tasks.at(0)) as HistoryTaskItem)
return this
}
}

View File

@@ -615,6 +615,67 @@ test.describe('Load workflow', () => {
'single_ksampler_modified.png'
)
})
test.describe('Restore all open workflows on reload', () => {
let workflowA: string
let workflowB: string
const generateUniqueFilename = (extension = '') =>
`${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}${extension}`
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
workflowA = generateUniqueFilename()
await comfyPage.menu.topbar.saveWorkflow(workflowA)
workflowB = generateUniqueFilename()
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
await comfyPage.menu.topbar.saveWorkflow(workflowB)
// Wait for localStorage to persist the workflow paths before reloading
await comfyPage.page.waitForFunction(
() => !!window.localStorage.getItem('Comfy.OpenWorkflowsPaths')
)
await comfyPage.setup({ clearStorage: false })
})
test('Restores topbar workflow tabs after reload', async ({
comfyPage
}) => {
await comfyPage.setSetting(
'Comfy.Workflow.WorkflowTabsPosition',
'Topbar'
)
const tabs = await comfyPage.menu.topbar.getTabNames()
const activeWorkflowName = await comfyPage.menu.topbar.getActiveTabName()
expect(tabs).toEqual(expect.arrayContaining([workflowA, workflowB]))
expect(tabs.indexOf(workflowA)).toBeLessThan(tabs.indexOf(workflowB))
expect(activeWorkflowName).toEqual(workflowB)
})
test('Restores sidebar workflows after reload', async ({ comfyPage }) => {
await comfyPage.setSetting(
'Comfy.Workflow.WorkflowTabsPosition',
'Sidebar'
)
await comfyPage.menu.workflowsTab.open()
const openWorkflows =
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
const activeWorkflowName =
await comfyPage.menu.workflowsTab.getActiveWorkflowName()
const workflowPathA = `${workflowA}.json`
const workflowPathB = `${workflowB}.json`
expect(openWorkflows).toEqual(
expect.arrayContaining([workflowPathA, workflowPathB])
)
expect(openWorkflows.indexOf(workflowPathA)).toBeLessThan(
openWorkflows.indexOf(workflowPathB)
)
expect(activeWorkflowName).toEqual(workflowPathB)
})
})
})
test.describe('Load duplicate workflow', () => {

View File

@@ -1,9 +1,6 @@
import { expect, mergeTests } from '@playwright/test'
import { expect } from '@playwright/test'
import { comfyPageFixture } from './fixtures/ComfyPage'
import { webSocketFixture } from './fixtures/ws'
const test = mergeTests(comfyPageFixture, webSocketFixture)
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Menu', () => {
test.beforeEach(async ({ comfyPage }) => {
@@ -948,66 +945,61 @@ test.describe.skip('Queue sidebar', () => {
})
test.describe('Gallery', () => {
const firstImage = 'example.webp'
const secondImage = 'image32x32.webp'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask(['example.webp'])
.repeat(1)
.withTask([secondImage])
.withTask([firstImage])
.setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
await comfyPage.menu.queueTab.openTaskPreview(0)
})
test('displays gallery image after opening task preview', async ({
comfyPage
}) => {
await comfyPage.menu.queueTab.openTaskPreview(0)
expect(comfyPage.menu.queueTab.getGalleryImage(0)).toBeVisible()
await comfyPage.nextFrame()
expect(comfyPage.menu.queueTab.getGalleryImage(firstImage)).toBeVisible()
})
test('should maintain active gallery item when new tasks are added', async ({
comfyPage,
ws
test('maintains active gallery item when new tasks are added', async ({
comfyPage
}) => {
const initialIndex = 0
await comfyPage.menu.queueTab.openTaskPreview(initialIndex)
// Add a new task while the gallery is still open
comfyPage.setupHistory().withTask(['example.webp'])
await ws.trigger({
type: 'status',
data: {
status: { exec_info: { queue_remaining: 0 } }
}
})
await comfyPage.menu.queueTab.waitForTasks()
// The index of all tasks increments when a new task is prepended
const expectIndex = initialIndex + 1
expect(comfyPage.menu.queueTab.getGalleryImage(expectIndex)).toBeVisible()
const newImage = 'image64x64.webp'
comfyPage.setupHistory().withTask([newImage])
await comfyPage.menu.queueTab.triggerTasksUpdate()
await comfyPage.page.waitForTimeout(500)
const newTask = comfyPage.menu.queueTab.tasks.getByAltText(newImage)
await newTask.waitFor({ state: 'visible' })
// The active gallery item should still be the initial image
expect(comfyPage.menu.queueTab.getGalleryImage(firstImage)).toBeVisible()
})
test.describe('Gallery navigation', () => {
const paths: {
description: string
path: ('Right' | 'Left')[]
expectIndex: number
end: string
}[] = [
{ description: 'Right', path: ['Right'], expectIndex: 1 },
{ description: 'Left', path: ['Right', 'Left'], expectIndex: 0 },
{ description: 'Right wrap', path: ['Right', 'Right'], expectIndex: 0 },
{ description: 'Left wrap', path: ['Left'], expectIndex: 1 }
{ description: 'Right', path: ['Right'], end: secondImage },
{ description: 'Left', path: ['Right', 'Left'], end: firstImage },
{ description: 'Left wrap', path: ['Left'], end: secondImage },
{ description: 'Right wrap', path: ['Right', 'Right'], end: firstImage }
]
paths.forEach(({ description, path, expectIndex }) => {
paths.forEach(({ description, path, end }) => {
test(`can navigate gallery ${description}`, async ({ comfyPage }) => {
await comfyPage.menu.queueTab.openTaskPreview(0)
for (const direction of path)
await comfyPage.page.keyboard.press(`Arrow${direction}`)
expect(
comfyPage.menu.queueTab.getGalleryImage(expectIndex)
).toBeVisible()
await comfyPage.page.keyboard.press(`Arrow${direction}`, {
delay: 256
})
await comfyPage.nextFrame()
expect(comfyPage.menu.queueTab.getGalleryImage(end)).toBeVisible()
})
})
})

13
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.7.9",
"version": "1.7.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
"version": "1.7.9",
"version": "1.7.14",
"license": "GPL-3.0-only",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.7",
"@comfyorg/comfyui-electron-types": "^0.4.10",
"@comfyorg/litegraph": "^0.8.60",
"@primevue/themes": "^4.0.5",
"@sentry/vue": "^8.48.0",
@@ -1936,10 +1936,9 @@
"dev": true
},
"node_modules/@comfyorg/comfyui-electron-types": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/@comfyorg/comfyui-electron-types/-/comfyui-electron-types-0.4.7.tgz",
"integrity": "sha512-APC3C4VZOo9W6h0xiAGxnsU9iNp3T8rN9w/5KmOCI0GUoKtKg5U2OaicTmnMwcDSQe5Jxflmej53GyJ1nH9oRw==",
"license": "GPL-3.0-only"
"version": "0.4.10",
"resolved": "https://registry.npmjs.org/@comfyorg/comfyui-electron-types/-/comfyui-electron-types-0.4.10.tgz",
"integrity": "sha512-UWBgyuWeV7vussYZVUYhCe0jj+XbIq2nglrCUy6IgFgXp9pbE8Ktg5D36WxE0RWj6SvVXErlCL9wWnMktaRbCA=="
},
"node_modules/@comfyorg/litegraph": {
"version": "0.8.60",

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.7.9",
"version": "1.7.14",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -17,8 +17,8 @@
"update-litegraph": "node scripts/update-litegraph.js",
"zipdist": "node scripts/zipdist.js",
"typecheck": "vue-tsc --noEmit && tsc --noEmit && tsc-strict",
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}' --plugin @trivago/prettier-plugin-sort-imports",
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}' --plugin @trivago/prettier-plugin-sort-imports",
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}'",
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
"test:jest": "jest --config jest.config.ts",
"test:generate": "npx tsx tests-ui/setup",
"test:browser": "npx playwright test",
@@ -83,7 +83,7 @@
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.7",
"@comfyorg/comfyui-electron-types": "^0.4.10",
"@comfyorg/litegraph": "^0.8.60",
"@primevue/themes": "^4.0.5",
"@sentry/vue": "^8.48.0",

View File

@@ -20,11 +20,16 @@ const terminalCreated = (
let offData: IDisposable
let offOutput: () => void
useAutoSize(root, true, true, () => {
// If we aren't visible, don't resize
if (!terminal.element?.offsetParent) return
useAutoSize({
root,
autoRows: true,
autoCols: true,
onResize: () => {
// If we aren't visible, don't resize
if (!terminal.element?.offsetParent) return
terminalApi.resize(terminal.cols, terminal.rows)
terminalApi.resize(terminal.cols, terminal.rows)
}
})
onMounted(async () => {

View File

@@ -29,7 +29,12 @@ const terminalCreated = (
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement>
) => {
useAutoSize(root, true, false)
// `autoCols` is false because we don't want the progress bar in the terminal
// to render incorrectly as the progress bar is rendered based on the
// server's terminal size.
// Apply a min cols of 80 for colab environments
// See https://github.com/comfyanonymous/ComfyUI/issues/6396
useAutoSize({ root, autoRows: true, autoCols: false, minCols: 80 })
const update = (entries: Array<LogEntry>, size?: TerminalSize) => {
if (size) {

View File

@@ -13,11 +13,16 @@
import { onBeforeUnmount, onMounted } from 'vue'
import { useSettingStore } from '@/stores/settingStore'
import { useWorkflowStore } from '@/stores/workflowStore'
const settingStore = useSettingStore()
const workflowStore = useWorkflowStore()
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
if (settingStore.get('Comfy.Window.UnloadConfirmation')) {
if (
settingStore.get('Comfy.Window.UnloadConfirmation') &&
workflowStore.modifiedWorkflows.length > 0
) {
event.preventDefault()
return true
}

View File

@@ -30,6 +30,7 @@
v-if="sendReportOpen"
error-type="graphExecutionError"
:extra-fields="[stackTraceField]"
:tags="{ exceptionMessage: props.error.exception_message }"
/>
<div class="action-container">
<FindIssueButton

View File

@@ -75,9 +75,12 @@ const props = defineProps<{
errorType: string
defaultFields?: DefaultField[]
extraFields?: ReportField[]
tags?: Record<string, string>
}>()
const { defaultFields = ['Workflow', 'Logs', 'SystemStats', 'Settings'] } =
props
const {
defaultFields = ['Workflow', 'Logs', 'SystemStats', 'Settings'],
tags = {}
} = props
const { t } = useI18n()
const toast = useToast()
@@ -168,7 +171,8 @@ const createCaptureContext = async (): Promise<CaptureContext> => {
user: getUserInfo(),
level: 'error',
tags: {
errorType: props.errorType
errorType: props.errorType,
...tags
},
extra: {
...createFeedback(),

View File

@@ -57,7 +57,7 @@ import { usePragmaticDroppable } from '@/hooks/dndHooks'
import { api } from '@/scripts/api'
import { app as comfyApp } from '@/scripts/app'
import { ChangeTracker } from '@/scripts/changeTracker'
import { setStorageValue } from '@/scripts/utils'
import { getStorageValue, setStorageValue } from '@/scripts/utils'
import { IS_CONTROL_WIDGET, updateControlWidgetLabel } from '@/scripts/widgets'
import { useColorPaletteService } from '@/services/colorPaletteService'
import { useLitegraphService } from '@/services/litegraphService'
@@ -95,6 +95,29 @@ const canvasMenuEnabled = computed(() =>
)
const tooltipEnabled = computed(() => settingStore.get('Comfy.EnableTooltips'))
const storedWorkflows = JSON.parse(
getStorageValue('Comfy.OpenWorkflowsPaths') || '[]'
)
const storedActiveIndex = JSON.parse(
getStorageValue('Comfy.ActiveWorkflowIndex') || '-1'
)
const openWorkflows = computed(() => workspaceStore?.workflow?.openWorkflows)
const activeWorkflow = computed(() => workspaceStore?.workflow?.activeWorkflow)
const restoreState = computed<{ paths: string[]; activeIndex: number }>(() => {
if (!openWorkflows.value || !activeWorkflow.value) {
return { paths: [], activeIndex: -1 }
}
const paths = openWorkflows.value
.filter((workflow) => workflow?.isPersisted && !workflow.isModified)
.map((workflow) => workflow.path)
const activeIndex = openWorkflows.value.findIndex(
(workflow) => workflow.path === activeWorkflow.value?.path
)
return { paths, activeIndex }
})
watchEffect(() => {
const canvasInfoEnabled = settingStore.get('Comfy.Graph.CanvasInfo')
if (canvasStore.canvas) {
@@ -364,6 +387,18 @@ onMounted(async () => {
'Comfy.CustomColorPalettes'
)
const isRestorable = storedWorkflows?.length > 0 && storedActiveIndex >= 0
if (isRestorable)
workflowStore.openWorkflowsInBackground({
left: storedWorkflows.slice(0, storedActiveIndex),
right: storedWorkflows.slice(storedActiveIndex)
})
watch(restoreState, ({ paths, activeIndex }) => {
setStorageValue('Comfy.OpenWorkflowsPaths', JSON.stringify(paths))
setStorageValue('Comfy.ActiveWorkflowIndex', JSON.stringify(activeIndex))
})
// Start watching for locale change after the initial value is loaded.
watch(
() => settingStore.get('Comfy.Locale'),

View File

@@ -59,10 +59,19 @@
</h4>
<ul class="list-disc pl-6 space-y-1">
<li>
{{ $t('install.settings.dataCollectionDialog.errorReports') }}
{{
$t('install.settings.dataCollectionDialog.collect.errorReports')
}}
</li>
<li>
{{ $t('install.settings.dataCollectionDialog.systemInfo') }}
{{ $t('install.settings.dataCollectionDialog.collect.systemInfo') }}
</li>
<li>
{{
$t(
'install.settings.dataCollectionDialog.collect.userJourneyEvents'
)
}}
</li>
</ul>
@@ -72,25 +81,43 @@
<ul class="list-disc pl-6 space-y-1">
<li>
{{
$t('install.settings.dataCollectionDialog.personalInformation')
}}
</li>
<li>
{{ $t('install.settings.dataCollectionDialog.workflowContents') }}
</li>
<li>
{{
$t('install.settings.dataCollectionDialog.fileSystemInformation')
$t(
'install.settings.dataCollectionDialog.doNotCollect.personalInformation'
)
}}
</li>
<li>
{{
$t(
'install.settings.dataCollectionDialog.customNodeConfigurations'
'install.settings.dataCollectionDialog.doNotCollect.workflowContents'
)
}}
</li>
<li>
{{
$t(
'install.settings.dataCollectionDialog.doNotCollect.fileSystemInformation'
)
}}
</li>
<li>
{{
$t(
'install.settings.dataCollectionDialog.doNotCollect.customNodeConfigurations'
)
}}
</li>
</ul>
<div class="mt-4">
<a
href="https://comfy.org/privacy"
target="_blank"
class="text-blue-400 hover:text-blue-300 underline"
>
{{ $t('install.settings.dataCollectionDialog.viewFullPolicy') }}
</a>
</div>
</div>
</Dialog>
</div>

View File

@@ -130,12 +130,13 @@
</template>
<script setup lang="ts">
import type { TorchDeviceType } from '@comfyorg/comfyui-electron-types'
import Tag from 'primevue/tag'
import ToggleSwitch from 'primevue/toggleswitch'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { TorchDeviceType, electronAPI } from '@/utils/envUtil'
import { electronAPI } from '@/utils/envUtil'
const { t } = useI18n()
@@ -158,8 +159,8 @@ const pickGpu = (value: typeof selected.value) => {
}
</script>
<style lang="postcss">
:root {
<style scoped>
.p-tag {
--p-tag-gap: 0.5rem;
}

View File

@@ -1,6 +1,6 @@
<template>
<teleport :to="teleportTarget">
<nav :class="'side-tool-bar-container' + (isSmall ? ' small-sidebar' : '')">
<nav class="side-tool-bar-container" :class="{ 'small-sidebar': isSmall }">
<SidebarIcon
v-for="tab in tabs"
:key="tab.id"
@@ -69,17 +69,6 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
}
</script>
<style>
:root {
--sidebar-width: 4rem;
--sidebar-icon-size: 1.5rem;
}
:root .small-sidebar {
--sidebar-width: 2.5rem;
--sidebar-icon-size: 1rem;
}
</style>
<style scoped>
.side-tool-bar-container {
display: flex;
@@ -94,6 +83,14 @@ const getTabTooltipSuffix = (tab: SidebarTabExtension) => {
background-color: var(--comfy-menu-secondary-bg);
color: var(--fg-color);
box-shadow: var(--bar-shadow);
--sidebar-width: 4rem;
--sidebar-icon-size: 1.5rem;
}
.side-tool-bar-container.small-sidebar {
--sidebar-width: 2.5rem;
--sidebar-icon-size: 1rem;
}
.side-tool-bar-end {

View File

@@ -99,7 +99,7 @@ import type { MenuItem } from 'primevue/menuitem'
import ProgressSpinner from 'primevue/progressspinner'
import { useConfirm } from 'primevue/useconfirm'
import { useToast } from 'primevue/usetoast'
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'
import { computed, ref, shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
@@ -194,10 +194,6 @@ const confirmRemoveAll = (event: Event) => {
})
}
const onStatus = async () => {
await queueStore.update()
}
const menu = ref(null)
const menuTargetTask = ref<TaskItemImpl | null>(null)
const menuTargetNode = ref<ComfyNode | null>(null)
@@ -267,13 +263,4 @@ watch(allTasks, () => {
const newIndex = galleryActiveIndex.value + lengthChange
galleryActiveIndex.value = Math.max(0, newIndex)
})
onMounted(() => {
api.addEventListener('status', onStatus)
queueStore.update()
})
onUnmounted(() => {
api.removeEventListener('status', onStatus)
})
</script>

View File

@@ -3,7 +3,7 @@
<template #header>
<div class="flex items-center justify-center">
<div
class="relative overflow-hidden rounded-lg cursor-pointer w-64 h-64"
class="relative overflow-hidden rounded-t-lg cursor-pointer w-64 h-64"
>
<img
v-if="!imageError"
@@ -13,7 +13,7 @@
: `api/workflow_templates/${props.moduleName}/${props.workflowName}.jpg`
"
@error="imageError = true"
class="w-64 h-64 rounded-lg object-cover thumbnail"
class="w-64 h-64 rounded-t-lg object-cover thumbnail"
/>
<div v-else class="w-64 h-64 content-center text-center">
<i class="pi pi-file" style="font-size: 4rem"></i>

View File

@@ -24,7 +24,7 @@
:key="selectedTab.moduleName"
>
<template #item="slotProps">
<div @click="loadWorkflow(slotProps.data)">
<div @click="loadWorkflow(slotProps.data)" class="p-2">
<TemplateWorkflowCard
:moduleName="selectedTab.moduleName"
:workflowName="slotProps.data"

View File

@@ -230,7 +230,8 @@ export const CORE_SETTINGS: SettingParams[] = [
id: 'Comfy.Window.UnloadConfirmation',
name: 'Show confirmation when closing window',
type: 'boolean',
defaultValue: false
defaultValue: true,
versionModified: '1.7.12'
},
{
id: 'Comfy.TreeExplorer.ItemPadding',

View File

@@ -30,7 +30,7 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
{
id: 'Comfy-Desktop.SendStatistics',
category: ['Comfy-Desktop', 'General', 'Send Statistics'],
name: 'Send anonymous crash reports',
name: 'Send anonymous usage metrics',
type: 'boolean',
defaultValue: true,
onChange: onChangeRestartApp
@@ -41,6 +41,7 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
name: 'Window Style',
tooltip: 'Choose custom option to hide the system title bar',
type: 'combo',
experimental: true,
defaultValue: 'default',
options: ['default', 'custom'],
onChange: (
@@ -180,6 +181,16 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
}
],
keybindings: [
{
commandId: 'Workspace.CloseWorkflow',
combo: {
key: 'w',
ctrl: true
}
}
],
aboutPageBadges: [
{
label: 'ComfyUI_desktop v' + desktopAppVersion,

View File

@@ -1,7 +1,10 @@
import { debounce } from 'lodash'
import { api } from '../../scripts/api'
import { app } from '../../scripts/app'
import { ComfyApp } from '../../scripts/app'
import { $el, ComfyDialog } from '../../scripts/ui'
import { getStorageValue, setStorageValue } from '../../scripts/utils'
import { ClipspaceDialog } from './clipspace'
import { MaskEditorDialogOld } from './maskEditorOld'
@@ -262,15 +265,15 @@ var styles = `
}
#maskEditor_toolPanel {
height: 100%;
width: var(--sidebar-width);
width: 4rem;
z-index: 8888;
background: var(--comfy-menu-bg);
display: flex;
flex-direction: column;
}
.maskEditor_toolPanelContainer {
width: var(--sidebar-width);
height: var(--sidebar-width);
width: 4rem;
height: 4rem;
display: flex;
justify-content: center;
align-items: center;
@@ -331,7 +334,7 @@ var styles = `
margin-bottom: 5px;
}
#maskEditor_pointerZone {
width: calc(100% - var(--sidebar-width) - 220px);
width: calc(100% - 4rem - 220px);
height: 100%;
}
#maskEditor_uiContainer {
@@ -703,8 +706,8 @@ var styles = `
}
.maskEditor_toolPanelZoomIndicator {
width: var(--sidebar-width);
height: var(--sidebar-width);
width: 4rem;
height: 4rem;
display: flex;
flex-direction: column;
justify-content: center;
@@ -776,10 +779,37 @@ interface Offset {
}
export interface Brush {
type: BrushShape
size: number
opacity: number
hardness: number
type: BrushShape
smoothingPrecision: number
}
const saveBrushToCache = debounce(function (key: string, brush: Brush): void {
try {
const brushString = JSON.stringify(brush)
setStorageValue(key, brushString)
} catch (error) {
console.error('Failed to save brush to cache:', error)
}
}, 300)
function loadBrushFromCache(key: string): Brush | null {
try {
const brushString = getStorageValue(key)
if (brushString) {
const brush = JSON.parse(brushString) as Brush
console.log('Loaded brush from cache:', brush)
return brush
} else {
console.log('No brush found in cache.')
return null
}
} catch (error) {
console.error('Failed to load brush from cache:', error)
return null
}
}
type Callback = (data?: any) => void
@@ -1952,12 +1982,19 @@ class BrushTool {
'Comfy.MaskEditor.BrushAdjustmentSpeed'
)
this.brushSettings = {
size: 10,
opacity: 100,
hardness: 1,
type: BrushShape.Arc
const cachedBrushSettings = loadBrushFromCache('maskeditor_brush_settings')
if (cachedBrushSettings) {
this.brushSettings = cachedBrushSettings
} else {
this.brushSettings = {
type: BrushShape.Arc,
size: 10,
opacity: 0.7,
hardness: 1,
smoothingPrecision: 10
}
}
this.maskBlendMode = MaskBlendMode.Black
}
@@ -2016,6 +2053,10 @@ class BrushTool {
'brushType',
async () => this.brushSettings.type
)
this.messageBroker.createPullTopic(
'brushSmoothingPrecision',
async () => this.brushSettings.smoothingPrecision
)
this.messageBroker.createPullTopic(
'maskBlendMode',
async () => this.maskBlendMode
@@ -2143,7 +2184,7 @@ class BrushTool {
}
const distanceBetweenPoints =
(this.brushSettings.size / this.smoothingPrecision) * 6
(this.brushSettings.size / this.brushSettings.smoothingPrecision) * 6
const stepNr = Math.ceil(totalLength / distanceBetweenPoints)
let interpolatedPoints = points
@@ -2190,7 +2231,7 @@ class BrushTool {
const brush_size = await this.messageBroker.pull('brushSize')
const distance = Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2)
const steps = Math.ceil(
distance / ((brush_size / this.smoothingPrecision) * 4)
distance / ((brush_size / this.brushSettings.smoothingPrecision) * 4)
) // Adjust for smoother lines
const interpolatedOpacity =
1 / (1 + Math.exp(-6 * (this.brushSettings.opacity - 0.5))) -
@@ -2545,23 +2586,27 @@ class BrushTool {
private setBrushSize(size: number) {
this.brushSettings.size = size
saveBrushToCache('maskeditor_brush_settings', this.brushSettings)
}
private setBrushOpacity(opacity: number) {
this.brushSettings.opacity = opacity
saveBrushToCache('maskeditor_brush_settings', this.brushSettings)
}
private setBrushHardness(hardness: number) {
this.brushSettings.hardness = hardness
saveBrushToCache('maskeditor_brush_settings', this.brushSettings)
}
private setBrushType(type: BrushShape) {
this.brushSettings.type = type
saveBrushToCache('maskeditor_brush_settings', this.brushSettings)
}
private setBrushSmoothingPrecision(precision: number) {
//console.log('precision', precision)
this.smoothingPrecision = precision
this.brushSettings.smoothingPrecision = precision
saveBrushToCache('maskeditor_brush_settings', this.brushSettings)
}
}
@@ -2825,7 +2870,6 @@ class UIManager {
const circle_shape = document.createElement('div')
circle_shape.id = 'maskEditor_sidePanelBrushShapeCircle'
circle_shape.classList.add(shapeColor)
circle_shape.style.background = 'var(--p-button-text-primary-color)'
circle_shape.addEventListener('click', () => {
this.messageBroker.publish('setBrushShape', BrushShape.Arc)
this.setBrushBorderRadius()
@@ -2836,7 +2880,6 @@ class UIManager {
const square_shape = document.createElement('div')
square_shape.id = 'maskEditor_sidePanelBrushShapeSquare'
square_shape.classList.add(shapeColor)
square_shape.style.background = ''
square_shape.addEventListener('click', () => {
this.messageBroker.publish('setBrushShape', BrushShape.Rect)
this.setBrushBorderRadius()
@@ -2844,6 +2887,16 @@ class UIManager {
circle_shape.style.background = ''
})
if (
(await this.messageBroker.pull('brushSettings')).type === BrushShape.Arc
) {
circle_shape.style.background = 'var(--p-button-text-primary-color)'
square_shape.style.background = ''
} else {
circle_shape.style.background = ''
square_shape.style.background = 'var(--p-button-text-primary-color)'
}
brush_shape_container.appendChild(circle_shape)
brush_shape_container.appendChild(square_shape)
@@ -2855,7 +2908,7 @@ class UIManager {
1,
100,
1,
10,
(await this.messageBroker.pull('brushSettings')).size,
(event, value) => {
this.messageBroker.publish('setBrushSize', parseInt(value))
this.updateBrushPreview()
@@ -2868,7 +2921,7 @@ class UIManager {
0,
1,
0.01,
0.7,
(await this.messageBroker.pull('brushSettings')).opacity,
(event, value) => {
this.messageBroker.publish('setBrushOpacity', parseFloat(value))
this.updateBrushPreview()
@@ -2881,7 +2934,7 @@ class UIManager {
0,
1,
0.01,
1,
(await this.messageBroker.pull('brushSettings')).hardness,
(event, value) => {
this.messageBroker.publish('setBrushHardness', parseFloat(value))
this.updateBrushPreview()
@@ -2894,7 +2947,7 @@ class UIManager {
1,
100,
1,
10,
(await this.messageBroker.pull('brushSettings')).smoothingPrecision,
(event, value) => {
this.messageBroker.publish(
'setBrushSmoothingPrecision',
@@ -2903,7 +2956,31 @@ class UIManager {
}
)
const resetBrushSettingsButton = document.createElement('button')
resetBrushSettingsButton.id = 'resetBrushSettingsButton'
resetBrushSettingsButton.innerText = 'Reset to Default'
resetBrushSettingsButton.addEventListener('click', () => {
this.messageBroker.publish('setBrushShape', BrushShape.Arc)
this.messageBroker.publish('setBrushSize', 10)
this.messageBroker.publish('setBrushOpacity', 0.7)
this.messageBroker.publish('setBrushHardness', 1)
this.messageBroker.publish('setBrushSmoothingPrecision', 10)
circle_shape.style.background = 'var(--p-button-text-primary-color)'
square_shape.style.background = ''
thicknesSliderObj.slider.value = '10'
opacitySliderObj.slider.value = '0.7'
hardnessSliderObj.slider.value = '1'
brushSmoothingPrecisionSliderObj.slider.value = '10'
this.setBrushBorderRadius()
this.updateBrushPreview()
})
brush_settings_container.appendChild(brush_settings_title)
brush_settings_container.appendChild(resetBrushSettingsButton)
brush_settings_container.appendChild(brush_shape_outer_container)
brush_settings_container.appendChild(thicknesSliderObj.container)
brush_settings_container.appendChild(opacitySliderObj.container)

View File

@@ -36,12 +36,21 @@ export function useTerminal(element: Ref<HTMLElement>) {
return {
terminal,
useAutoSize(
root: Ref<HTMLElement>,
autoRows: boolean = true,
autoCols: boolean = true,
useAutoSize({
root,
autoRows = true,
autoCols = true,
minCols = Number.NEGATIVE_INFINITY,
minRows = Number.NEGATIVE_INFINITY,
onResize
}: {
root: Ref<HTMLElement>
autoRows?: boolean
autoCols?: boolean
minCols?: number
minRows?: number
onResize?: () => void
) {
}) {
const ensureValidRows = (rows: number | undefined) => {
if (rows == null || isNaN(rows)) {
return root.value?.clientHeight / 20
@@ -61,8 +70,14 @@ export function useTerminal(element: Ref<HTMLElement>) {
const dims = fitAddon.proposeDimensions()
// Sometimes propose returns NaN, so we may need to estimate.
terminal.resize(
autoCols ? ensureValidCols(dims?.cols) : terminal.cols,
autoRows ? ensureValidRows(dims?.rows) : terminal.rows
Math.max(
autoCols ? ensureValidCols(dims?.cols) : terminal.cols,
minCols
),
Math.max(
autoRows ? ensureValidRows(dims?.rows) : terminal.rows,
minRows
)
)
onResize?.()
}

View File

@@ -70,7 +70,9 @@
"keybinding": "Keybinding",
"upload": "Upload",
"export": "Export",
"workflow": "Workflow"
"workflow": "Workflow",
"success": "Success",
"ok": "OK"
},
"issueReport": {
"submitErrorReport": "Submit Error Report (Optional)",
@@ -151,7 +153,7 @@
"appDataLocationTooltip": "ComfyUI's app data directory. Stores:\n- Logs\n- Server configs",
"appPathLocationTooltip": "ComfyUI's app asset directory. Stores the ComfyUI code and assets",
"migrateFromExistingInstallation": "Migrate from Existing Installation",
"migrationSourcePathDescription": "If you have an existing ComfyUI installation, we can copy/link your existing user files and models to the new installation.",
"migrationSourcePathDescription": "If you have an existing ComfyUI installation, we can copy/link your existing user files and models to the new installation. Your existing ComfyUI installation will not be affected.",
"selectItemsToMigrate": "Select Items to Migrate",
"migrationOptional": "Migration is optional. If you don't have an existing installation, you can skip this step.",
"desktopAppSettings": "Desktop App Settings",
@@ -181,25 +183,38 @@
},
"settings": {
"autoUpdate": "Automatic Updates",
"allowMetrics": "Crash Reports",
"autoUpdateDescription": "Automatically download and install updates when they become available. You'll always be notified before updates are installed.",
"allowMetricsDescription": "Help improve ComfyUI by sending anonymous crash reports. No personal information or workflow content will be collected. This can be disabled at any time in the settings menu.",
"allowMetrics": "Usage Metrics",
"errorUpdatingConsent": "Error Updating Consent",
"errorUpdatingConsentDetail": "Failed to update metrics consent settings",
"autoUpdateDescription": "Automatically download updates when they become available. You will be notified before updates are installed.",
"allowMetricsDescription": "Help improve ComfyUI by sending anonymous usage metrics. No personal information or workflow content will be collected.",
"learnMoreAboutData": "Learn more about data collection",
"dataCollectionDialog": {
"title": "About Data Collection",
"whatWeCollect": "What we collect:",
"whatWeDoNotCollect": "What we don't collect:",
"errorReports": "Error message and stack trace",
"systemInfo": "Hardware, OS type, and app version",
"personalInformation": "Personal information",
"workflowContent": "Workflow content",
"fileSystemInformation": "File system information",
"workflowContents": "Workflow contents",
"customNodeConfigurations": "Custom node configurations"
"collect": {
"errorReports": "Error message and stack trace",
"systemInfo": "Hardware, OS type, and app version",
"userJourneyEvents": "User journey events"
},
"doNotCollect": {
"personalInformation": "Personal information",
"fileSystemInformation": "File system information",
"workflowContents": "Workflow contents",
"customNodeConfigurations": "Custom node configurations"
},
"viewFullPolicy": "View full policy"
}
},
"customNodes": "Custom Nodes",
"customNodesDescription": "Reinstall custom nodes from existing ComfyUI installations."
"customNodesDescription": "Reinstall custom nodes from existing ComfyUI installations.",
"helpImprove": "Please help improve ComfyUI",
"moreInfo": "For more info, please read our",
"privacyPolicy": "privacy policy",
"metricsEnabled": "Metrics Enabled",
"metricsDisabled": "Metrics Disabled",
"updateConsent": "You previously opted in to reporting crashes. We are now tracking event-based metrics to help identify bugs and improve the app. No personal identifiable information is collected."
},
"serverStart": {
"reinstall": "Reinstall",
@@ -595,6 +610,7 @@
"combine": "combine",
"cond single": "cond single",
"controlnet": "controlnet",
"inpaint": "inpaint",
"scheduling": "scheduling",
"create": "create",
"mask": "mask",
@@ -614,7 +630,6 @@
"batch": "batch",
"video_models": "video_models",
"upscaling": "upscaling",
"inpaint": "inpaint",
"instructpix2pix": "instructpix2pix",
"compositing": "compositing",
"samplers": "samplers",

View File

@@ -177,7 +177,7 @@
},
"CLIPLoader": {
"display_name": "Load CLIP",
"description": "[Recipes]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5",
"description": "[Recipes]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5\ncosmos: old t5 xxl",
"inputs": {
"clip_name": {
"name": "clip_name"
@@ -862,6 +862,32 @@
}
}
},
"CosmosImageToVideoLatent": {
"display_name": "CosmosImageToVideoLatent",
"inputs": {
"vae": {
"name": "vae"
},
"width": {
"name": "width"
},
"height": {
"name": "height"
},
"length": {
"name": "length"
},
"batch_size": {
"name": "batch_size"
},
"start_image": {
"name": "start_image"
},
"end_image": {
"name": "end_image"
}
}
},
"CreateHookKeyframe": {
"display_name": "Create Hook Keyframe",
"inputs": {
@@ -1230,6 +1256,23 @@
}
}
},
"EmptyCosmosLatentVideo": {
"display_name": "EmptyCosmosLatentVideo",
"inputs": {
"width": {
"name": "width"
},
"height": {
"name": "height"
},
"length": {
"name": "length"
},
"batch_size": {
"name": "batch_size"
}
}
},
"EmptyHunyuanLatentVideo": {
"display_name": "EmptyHunyuanLatentVideo",
"inputs": {
@@ -4745,6 +4788,17 @@
}
}
},
"SetFirstSigma": {
"display_name": "SetFirstSigma",
"inputs": {
"sigmas": {
"name": "sigmas"
},
"sigma": {
"name": "sigma"
}
}
},
"SetHookKeyframes": {
"display_name": "Set Hook Keyframes",
"inputs": {

View File

@@ -3,7 +3,7 @@
"name": "Automatically check for updates"
},
"Comfy-Desktop_SendStatistics": {
"name": "Send anonymous crash reports"
"name": "Send anonymous usage metrics"
},
"Comfy-Desktop_WindowStyle": {
"name": "Window Style",

View File

@@ -107,6 +107,7 @@
"noTasksFound": "Aucune tâche trouvée",
"noTasksFoundMessage": "Il n'y a pas de tâches dans la file d'attente.",
"noWorkflowsFound": "Aucun flux de travail trouvé.",
"ok": "OK",
"openNewIssue": "Ouvrir un nouveau problème",
"overwrite": "Écraser",
"reconnected": "Reconnecté",
@@ -129,6 +130,7 @@
"searchWorkflows": "Rechercher des flux de travail",
"settings": "Paramètres",
"showReport": "Afficher le rapport",
"success": "Succès",
"systemInfo": "Informations système",
"terminal": "Terminal",
"upload": "Téléverser",
@@ -186,6 +188,7 @@
"selectGpu": "Sélectionnez le GPU",
"selectGpuDescription": "Sélectionnez le type de GPU que vous avez"
},
"helpImprove": "Veuillez aider à améliorer ComfyUI",
"installLocation": "Emplacement d'installation",
"installLocationDescription": "Sélectionnez le répertoire pour les données utilisateur de ComfyUI. Un environnement python sera installé à l'emplacement sélectionné. Veuillez vous assurer que le disque sélectionné a suffisamment d'espace (~15GB) restant.",
"installLocationTooltip": "Répertoire des données utilisateur de ComfyUI. Stocke :\n- Environnement Python\n- Modèles\n- Nœuds personnalisés\n",
@@ -197,35 +200,47 @@
"title": "Configuration manuelle",
"virtualEnvironmentPath": "Chemin de l'environnement virtuel"
},
"metricsDisabled": "Métriques désactivées",
"metricsEnabled": "Métriques activées",
"migrateFromExistingInstallation": "Migrer à partir d'une installation existante",
"migration": "Migration",
"migrationOptional": "La migration est facultative. Si vous n'avez pas d'installation existante, vous pouvez sauter cette étape.",
"migrationSourcePathDescription": "Si vous avez une installation existante de ComfyUI, nous pouvons copier/lier vos fichiers utilisateur et modèles existants à la nouvelle installation.",
"migrationSourcePathDescription": "Si vous avez une installation existante de ComfyUI, nous pouvons copier/lier vos fichiers utilisateur et modèles existants à la nouvelle installation. Votre installation existante de ComfyUI ne sera pas affectée.",
"moreInfo": "Pour plus d'informations, veuillez lire notre",
"parentMissing": "Le chemin n'existe pas - créez d'abord le répertoire contenant",
"pathExists": "Le répertoire existe déjà - veuillez vous assurer que vous avez sauvegardé toutes les données",
"pathValidationFailed": "Échec de la validation du chemin",
"privacyPolicy": "politique de confidentialité",
"selectItemsToMigrate": "Sélectionnez les éléments à migrer",
"settings": {
"allowMetrics": "Rapports de plantage",
"allowMetricsDescription": "Aidez à améliorer ComfyUI en envoyant des rapports de plantage anonymes. Aucune information personnelle ou contenu de flux de travail ne sera collecté. Cela peut être désactivé à tout moment dans le menu des paramètres.",
"allowMetrics": "Métriques d'utilisation",
"allowMetricsDescription": "Aidez à améliorer ComfyUI en envoyant des métriques d'utilisation anonymes. Aucune information personnelle ou contenu de flux de travail ne sera collecté.",
"autoUpdate": "Mises à jour automatiques",
"autoUpdateDescription": "Téléchargez et installez automatiquement les mises à jour lorsqu'elles deviennent disponibles. Vous serez toujours informé avant l'installation des mises à jour.",
"dataCollectionDialog": {
"customNodeConfigurations": "Configurations de nœuds personnalisés",
"errorReports": "Message d'erreur et trace de la pile",
"fileSystemInformation": "Informations sur le système de fichiers",
"personalInformation": "Informations personnelles",
"systemInfo": "Matériel, type d'OS et version de l'application",
"collect": {
"errorReports": "Message d'erreur et trace de la pile",
"systemInfo": "Matériel, type de système d'exploitation et version de l'application",
"userJourneyEvents": "Événements du parcours utilisateur"
},
"doNotCollect": {
"customNodeConfigurations": "Configurations de nœud personnalisées",
"fileSystemInformation": "Informations sur le système de fichiers",
"personalInformation": "Informations personnelles",
"workflowContents": "Contenus du flux de travail"
},
"title": "À propos de la collecte de données",
"viewFullPolicy": "Voir la politique complète",
"whatWeCollect": "Ce que nous collectons :",
"whatWeDoNotCollect": "Ce que nous ne collectons pas :",
"workflowContent": "Contenu du flux de travail",
"workflowContents": "Contenus du flux de travail"
"whatWeDoNotCollect": "Ce que nous ne collectons pas :"
},
"errorUpdatingConsent": "Erreur de mise à jour du consentement",
"errorUpdatingConsentDetail": "Échec de la mise à jour des paramètres de consentement aux métriques",
"learnMoreAboutData": "En savoir plus sur la collecte de données"
},
"systemLocations": "Emplacements système",
"unhandledError": "Erreur inconnue"
"unhandledError": "Erreur inconnue",
"updateConsent": "Vous avez précédemment accepté de signaler les plantages. Nous suivons maintenant des métriques basées sur les événements pour aider à identifier les bugs et améliorer l'application. Aucune information personnelle identifiable n'est collectée."
},
"issueReport": {
"contactFollowUp": "Contactez-moi pour un suivi",

View File

@@ -113,7 +113,7 @@
}
},
"CLIPLoader": {
"description": "[Recettes]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5",
"description": "[Recettes]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5\ncosmos: old t5 xxl",
"display_name": "Charger CLIP",
"inputs": {
"clip_name": {
@@ -862,6 +862,32 @@
}
}
},
"CosmosImageToVideoLatent": {
"display_name": "CosmosImageVersVidéoLatent",
"inputs": {
"batch_size": {
"name": "taille_du_lot"
},
"end_image": {
"name": "image_de_fin"
},
"height": {
"name": "hauteur"
},
"length": {
"name": "longueur"
},
"start_image": {
"name": "image_de_départ"
},
"vae": {
"name": "vae"
},
"width": {
"name": "largeur"
}
}
},
"CreateHookKeyframe": {
"display_name": "Créer une image clé de crochet",
"inputs": {
@@ -1230,6 +1256,23 @@
}
}
},
"EmptyCosmosLatentVideo": {
"display_name": "VidéoLatenteCosmosVide",
"inputs": {
"batch_size": {
"name": "taille_du_lot"
},
"height": {
"name": "hauteur"
},
"length": {
"name": "longueur"
},
"width": {
"name": "largeur"
}
}
},
"EmptyHunyuanLatentVideo": {
"display_name": "EmptyHunyuanLatentVideo",
"inputs": {
@@ -4825,6 +4868,17 @@
}
}
},
"SetFirstSigma": {
"display_name": "DéfinirPremierSigma",
"inputs": {
"sigma": {
"name": "sigma"
},
"sigmas": {
"name": "sigmas"
}
}
},
"SetHookKeyframes": {
"display_name": "Définir les Images Clés de Crochet",
"inputs": {

View File

@@ -3,7 +3,7 @@
"name": "Vérifier automatiquement les mises à jour"
},
"Comfy-Desktop_SendStatistics": {
"name": "Envoyer des rapports de plantage anonymes"
"name": "Envoyer des métriques d'utilisation anonymes"
},
"Comfy-Desktop_WindowStyle": {
"name": "Style de fenêtre",

View File

@@ -107,6 +107,7 @@
"noTasksFound": "タスクが見つかりません",
"noTasksFoundMessage": "キューにタスクがありません。",
"noWorkflowsFound": "ワークフローが見つかりません。",
"ok": "OK",
"openNewIssue": "新しい問題を開く",
"overwrite": "上書き",
"reconnected": "再接続されました",
@@ -129,6 +130,7 @@
"searchWorkflows": "ワークフローを検索",
"settings": "設定",
"showReport": "レポートを表示",
"success": "成功",
"systemInfo": "システム情報",
"terminal": "ターミナル",
"upload": "アップロード",
@@ -186,6 +188,7 @@
"selectGpu": "GPUを選択",
"selectGpuDescription": "所有しているGPUのタイプを選択してください"
},
"helpImprove": "ComfyUIの改善にご協力ください",
"installLocation": "インストール先",
"installLocationDescription": "ComfyUIのユーザーデータを保存するディレクトリを選択してください。Python環境が選択した場所にインストールされます。選択したディスクに約15GBの空き容量が必要です。",
"installLocationTooltip": "ComfyUIのユーザーデータディレクトリ。保存内容:\n- Python環境\n- モデル\n- カスタムノード\n",
@@ -197,35 +200,47 @@
"title": "マニュアル設定",
"virtualEnvironmentPath": "仮想環境のパス"
},
"metricsDisabled": "メトリクス無効",
"metricsEnabled": "メトリクス有効",
"migrateFromExistingInstallation": "既存のインストールから移行",
"migration": "移行",
"migrationOptional": "移行は任意です。既存のインストールがない場合、このステップをスキップできます。",
"migrationSourcePathDescription": "既存のComfyUIインストールがある場合、既存のユーザーファイルモデルを新しいインストールにコピー/リンクできます。",
"migrationSourcePathDescription": "既存のComfyUIインストールがある場合、既存のユーザーファイルモデルを新しいインストールにコピー/リンクすることができます。既存のComfyUIインストールは影響を受けません。",
"moreInfo": "詳細は、私たちの",
"parentMissing": "パスが存在しません - 最初に含まれるディレクトリを作成してください",
"pathExists": "ディレクトリはすでに存在します - すべてのデータをバックアップしたことを確認してください",
"pathValidationFailed": "パスの検証に失敗しました",
"privacyPolicy": "プライバシーポリシー",
"selectItemsToMigrate": "移行する項目を選択",
"settings": {
"allowMetrics": "クラッシュレポート",
"allowMetricsDescription": "ComfyUI改善に協力してください。匿名のクラッシュレポートを送信します。個人情報やワークフロー内容は収集されません。この設定はいつでも無効にできます。",
"allowMetrics": "使用状況のメトリクス",
"allowMetricsDescription": "匿名の使用状況メトリクスを送信してComfyUI改善します。個人情報やワークフロー内容は収集されません。",
"autoUpdate": "自動更新",
"autoUpdateDescription": "更新が利用可能になると、自動的にダウンロードおよびインストールを行います。インストール前に通知が表示されます。",
"dataCollectionDialog": {
"customNodeConfigurations": "カスタムノード設定",
"errorReports": "エラーメッセージとスタックトレース",
"fileSystemInformation": "ファイルシステム情報",
"personalInformation": "個人情報",
"systemInfo": "ハードウェア、OSの種類、アプリのバージョン",
"collect": {
"errorReports": "エラーメッセージとスタックトレース",
"systemInfo": "ハードウェア、OSタイプ、アプリバージョン",
"userJourneyEvents": "ユーザージャーニーイベント"
},
"doNotCollect": {
"customNodeConfigurations": "カスタムノードの設定",
"fileSystemInformation": "ファイルシステム情報",
"personalInformation": "個人情報",
"workflowContents": "ワークフローの内容"
},
"title": "データ収集について",
"viewFullPolicy": "完全なポリシーを見る",
"whatWeCollect": "収集内容:",
"whatWeDoNotCollect": "収集しない内容:",
"workflowContent": "ワークフロー内容",
"workflowContents": "ワークフロー内容"
"whatWeDoNotCollect": "収集しない内容:"
},
"errorUpdatingConsent": "同意の更新エラー",
"errorUpdatingConsentDetail": "メトリクスの同意設定の更新に失敗しました",
"learnMoreAboutData": "データ収集の詳細を見る"
},
"systemLocations": "システムの場所",
"unhandledError": "未知のエラー"
"unhandledError": "未知のエラー",
"updateConsent": "以前はクラッシュの報告に同意していました。現在、バグの特定とアプリの改善を助けるためにイベントベースのメトリクスを追跡しています。個人を特定できる情報は収集されません。"
},
"issueReport": {
"contactFollowUp": "フォローアップのために私に連絡する",

View File

@@ -113,7 +113,7 @@
}
},
"CLIPLoader": {
"description": "[レシピ]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5",
"description": "[レシピ]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5\ncosmos: old t5 xxl",
"display_name": "CLIPを読み込む",
"inputs": {
"clip_name": {
@@ -862,6 +862,32 @@
}
}
},
"CosmosImageToVideoLatent": {
"display_name": "CosmosImageToVideoLatent",
"inputs": {
"batch_size": {
"name": "バッチサイズ"
},
"end_image": {
"name": "終了画像"
},
"height": {
"name": "高さ"
},
"length": {
"name": "長さ"
},
"start_image": {
"name": "開始画像"
},
"vae": {
"name": "vae"
},
"width": {
"name": "幅"
}
}
},
"CreateHookKeyframe": {
"display_name": "フックキーフレームを作成",
"inputs": {
@@ -1230,6 +1256,23 @@
}
}
},
"EmptyCosmosLatentVideo": {
"display_name": "EmptyCosmosLatentVideo",
"inputs": {
"batch_size": {
"name": "バッチサイズ"
},
"height": {
"name": "高さ"
},
"length": {
"name": "長さ"
},
"width": {
"name": "幅"
}
}
},
"EmptyHunyuanLatentVideo": {
"display_name": "EmptyHunyuanLatentVideo",
"inputs": {
@@ -4825,6 +4868,17 @@
}
}
},
"SetFirstSigma": {
"display_name": "SetFirstSigma",
"inputs": {
"sigma": {
"name": "シグマ"
},
"sigmas": {
"name": "シグマ"
}
}
},
"SetHookKeyframes": {
"display_name": "フックキーフレームを設定",
"inputs": {

View File

@@ -3,7 +3,7 @@
"name": "自動的に更新を確認する"
},
"Comfy-Desktop_SendStatistics": {
"name": "匿名のクラッシュレポートを送信する"
"name": "匿名の使用統計を送信する"
},
"Comfy-Desktop_WindowStyle": {
"name": "ウィンドウスタイル",

View File

@@ -107,6 +107,7 @@
"noTasksFound": "작업을 찾을 수 없습니다.",
"noTasksFoundMessage": "대기열에 작업이 없습니다.",
"noWorkflowsFound": "워크플로를 찾을 수 없습니다.",
"ok": "확인",
"openNewIssue": "새 문제 열기",
"overwrite": "덮어쓰기",
"reconnected": "재연결됨",
@@ -129,6 +130,7 @@
"searchWorkflows": "워크플로 검색",
"settings": "설정",
"showReport": "보고서 보기",
"success": "성공",
"systemInfo": "시스템 정보",
"terminal": "터미널",
"upload": "업로드",
@@ -186,6 +188,7 @@
"selectGpu": "GPU 선택",
"selectGpuDescription": "소유한 GPU 유형을 선택하세요"
},
"helpImprove": "ComfyUI 개선에 도움을 주세요",
"installLocation": "설치 위치",
"installLocationDescription": "ComfyUI의 사용자 데이터 디렉토리를 선택하십시오. 선택한 위치에 Python 환경이 설치됩니다. 선택한 디스크에 충분한 공간(~15GB)이 남아 있는지 확인하십시오.",
"installLocationTooltip": "ComfyUI의 사용자 데이터 디렉토리. 저장소:\n- Python 환경\n- 모델\n- 사용자 정의 노드\n",
@@ -197,35 +200,47 @@
"title": "수동 구성",
"virtualEnvironmentPath": "가상 환경 경로"
},
"metricsDisabled": "메트릭스 비활성화",
"metricsEnabled": "메트릭스 활성화",
"migrateFromExistingInstallation": "기존 설치에서 마이그레이션",
"migration": "마이그레이션",
"migrationOptional": "마이그레이션은 선택 사항입니다. 기존에 설치된 것이 없다면, 이 단계를 건너뛸 수 있습니다.",
"migrationSourcePathDescription": "기존에 설치된 ComfyUI가 있는 경우, 기존 사용자 파일과 모델을 새 설치 복사하거나 링크할 수 있습니다",
"migrationSourcePathDescription": "기존 ComfyUI 설치가 있으면, 기존 사용자 파일과 모델을 새 설치 복사/링크할 수 있습니다. 기존의 ComfyUI 설치는 영향을 받지 않습니다.",
"moreInfo": "자세한 정보는 우리의",
"parentMissing": "경로가 존재하지 않습니다 - 먼저 포함하는 디렉토리를 생성하세요",
"pathExists": "디렉토리가 이미 존재합니다 - 모든 데이터를 백업했는지 확인해 주세요",
"pathValidationFailed": "경로 유효성 검사 실패",
"privacyPolicy": "개인정보 보호정책",
"selectItemsToMigrate": "마이그레이션 항목 선택",
"settings": {
"allowMetrics": "충돌 보고서",
"allowMetricsDescription": "익명의 충돌 보고서를 보내 ComfyUI 개선 도움을 줍니다. 개인 정보나 워크플로 내용은 수집되지 않습니다. 이는 설정 메뉴에서 언제든지 비활성화할 수 있습니다.",
"allowMetrics": "사용 통계",
"allowMetricsDescription": "익명의 사용 통계를 보내 ComfyUI 개선하는 데 도움을 줍니다. 개인 정보나 워크플로 내용은 수집되지 않습니다.",
"autoUpdate": "자동 업데이트",
"autoUpdateDescription": "업데이트가 가능해지면 자동으로 다운로드하고 설치합니다. 업데이트가 설치되기 전에 항상 알림을 받습니다.",
"dataCollectionDialog": {
"customNodeConfigurations": "사용자 정의 노드 설정",
"errorReports": "오류 메시지 및 스택 추적",
"fileSystemInformation": "파일 시스템 정보",
"personalInformation": "개인 정보",
"systemInfo": "하드웨어, OS 유형 및 앱 버전",
"collect": {
"errorReports": "오류 메시지 및 스택 추적",
"systemInfo": "하드웨어, OS 유형, 앱 버전",
"userJourneyEvents": "사용자 여정 이벤트"
},
"doNotCollect": {
"customNodeConfigurations": "사용자 정의 노드 구성",
"fileSystemInformation": "파일 시스템 정보",
"personalInformation": "개인 정보",
"workflowContents": "워크플로우 내용"
},
"title": "데이터 수집 안내",
"viewFullPolicy": "전체 정책 보기",
"whatWeCollect": "수집하는 정보:",
"whatWeDoNotCollect": "수집하지 않는 정보:",
"workflowContent": "워크플로 내용",
"workflowContents": "워크플로 내용"
"whatWeDoNotCollect": "수집하지 않는 정보:"
},
"errorUpdatingConsent": "동의 업데이트 오류",
"errorUpdatingConsentDetail": "메트릭스 동의 설정 업데이트에 실패했습니다",
"learnMoreAboutData": "데이터 수집에 대해 더 알아보기"
},
"systemLocations": "시스템 위치",
"unhandledError": "알 수 없는 오류"
"unhandledError": "알 수 없는 오류",
"updateConsent": "당신은 이전에 충돌 보고에 동의했습니다. 이제 버그를 식별하고 앱을 개선하기 위해 이벤트 기반 메트릭스를 추적하고 있습니다. 개인 식별 정보는 수집하지 않습니다."
},
"issueReport": {
"contactFollowUp": "추적 조사를 위해 연락해 주세요",

View File

@@ -113,7 +113,7 @@
}
},
"CLIPLoader": {
"description": "[조합법]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5",
"description": "[조합법]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5\ncosmos: old t5 xxl",
"display_name": "CLIP 로드",
"inputs": {
"clip_name": {
@@ -862,6 +862,32 @@
}
}
},
"CosmosImageToVideoLatent": {
"display_name": "CosmosImageToVideoLatent",
"inputs": {
"batch_size": {
"name": "배치 크기"
},
"end_image": {
"name": "끝 이미지"
},
"height": {
"name": "높이"
},
"length": {
"name": "길이"
},
"start_image": {
"name": "시작 이미지"
},
"vae": {
"name": "vae"
},
"width": {
"name": "너비"
}
}
},
"CreateHookKeyframe": {
"display_name": "후크 키프레임 생성",
"inputs": {
@@ -1230,6 +1256,23 @@
}
}
},
"EmptyCosmosLatentVideo": {
"display_name": "EmptyCosmosLatentVideo",
"inputs": {
"batch_size": {
"name": "배치 크기"
},
"height": {
"name": "높이"
},
"length": {
"name": "길이"
},
"width": {
"name": "너비"
}
}
},
"EmptyHunyuanLatentVideo": {
"display_name": "빈 잠재 비디오 (Hunyuan)",
"inputs": {
@@ -4825,6 +4868,17 @@
}
}
},
"SetFirstSigma": {
"display_name": "SetFirstSigma",
"inputs": {
"sigma": {
"name": "시그마"
},
"sigmas": {
"name": "시그마들"
}
}
},
"SetHookKeyframes": {
"display_name": "후크 키프레임 설정",
"inputs": {

View File

@@ -3,7 +3,7 @@
"name": "자동 업데이트 확인"
},
"Comfy-Desktop_SendStatistics": {
"name": "익명으로 충돌 보고서 전송"
"name": "익명 사용 통계 보내기"
},
"Comfy-Desktop_WindowStyle": {
"name": "창 스타일",

View File

@@ -107,6 +107,7 @@
"noTasksFound": "Задачи не найдены",
"noTasksFoundMessage": "В очереди нет задач.",
"noWorkflowsFound": "Рабочие процессы не найдены.",
"ok": "ОК",
"openNewIssue": "Открыть новую проблему",
"overwrite": "Перезаписать",
"reconnected": "Переподключено",
@@ -129,6 +130,7 @@
"searchWorkflows": "Поиск рабочих процессов",
"settings": "Настройки",
"showReport": "Показать отчет",
"success": "Успех",
"systemInfo": "Информация о системе",
"terminal": "Терминал",
"upload": "Загрузить",
@@ -186,6 +188,7 @@
"selectGpu": "Выберите GPU",
"selectGpuDescription": "Выберите тип GPU, который у вас есть"
},
"helpImprove": "Пожалуйста, помогите улучшить ComfyUI",
"installLocation": "Место установки",
"installLocationDescription": "Выберите директорию для пользовательских данных ComfyUI. В выбранном месте будет установлена среда Python. Пожалуйста, убедитесь, что на выбранном диске достаточно места (~15 ГБ).",
"installLocationTooltip": "Директория пользовательских данных ComfyUI. Хранит:\n- Среда Python\n- Модели\n- Пользовательские узлы\n",
@@ -197,35 +200,47 @@
"title": "Ручная Конфигурация",
"virtualEnvironmentPath": "Путь виртуального окружения"
},
"metricsDisabled": "Метрики отключены",
"metricsEnabled": "Метрики включены",
"migrateFromExistingInstallation": "Миграция из существующей установки",
"migration": "Миграция",
"migrationOptional": "Миграция является необязательной. Если у вас нет существующей установки, вы можете пропустить этот шаг.",
"migrationSourcePathDescription": "Если у вас есть существующая установка ComfyUI, мы можем скопировать/связать ваши существующие пользовательские файлы и модели в новую установку.",
"migrationSourcePathDescription": "Если у вас уже есть установка ComfyUI, мы можем скопировать/связать ваши существующие пользовательские файлы и модели с новой установкой. Ваша существующая установка ComfyUI не будет затронута.",
"moreInfo": "Для получения дополнительной информации, пожалуйста, прочтите нашу",
"parentMissing": "Путь не существует - сначала создайте родительский каталог",
"pathExists": "Директория уже существует - пожалуйста, убедитесь, что вы сделали резервное копирование всех данных",
"pathValidationFailed": "Не удалось проверить путь",
"privacyPolicy": "политику конфиденциальности",
"selectItemsToMigrate": "Выберите элементы для миграции",
"settings": {
"allowMetrics": "Отчеты о сбоях",
"allowMetricsDescription": "Помогите улучшить ComfyUI, отправляя анонимные отчеты о сбоях. Личная информация или содержимое рабочего процесса не будут собираться. Это можно отключить в любое время в меню настроек.",
"allowMetrics": "Метрики использования",
"allowMetricsDescription": "Помогите улучшить ComfyUI, отправляя анонимные метрики использования. Личная информация или содержание рабочего процесса не будут собираться.",
"autoUpdate": "Автоматические обновления",
"autoUpdateDescription": "Автоматически загружать и устанавливать обновления, когда они становятся доступными. Вы всегда будете уведомлены перед установкой обновлений.",
"dataCollectionDialog": {
"customNodeConfigurations": "Конфигурации пользовательских узлов",
"errorReports": "Сообщения об ошибках и трассировка стека",
"fileSystemInformation": "Информация о файловой системе",
"personalInformation": "Личная информация",
"systemInfo": "Аппаратное обеспечение, тип ОС и версия приложения",
"collect": {
"errorReports": "Сообщение об ошибке и трассировка стека",
"systemInfo": "Аппаратное обеспечение, тип ОС и версия приложения",
"userJourneyEvents": "События пользовательского пути"
},
"doNotCollect": {
"customNodeConfigurations": "Пользовательские конфигурации узлов",
"fileSystemInformation": "Информация о файловой системе",
"personalInformation": "Личная информация",
"workflowContents": "Содержание рабочего процесса"
},
"title": "О сборе данных",
"viewFullPolicy": "Просмотреть политику полностью",
"whatWeCollect": "Что мы собираем:",
"whatWeDoNotCollect": "Что мы не собираем:",
"workflowContent": "Содержимое рабочего процесса",
"workflowContents": "Содержимое рабочего процесса"
"whatWeDoNotCollect": "Что мы не собираем:"
},
"errorUpdatingConsent": "Ошибка обновления согласия",
"errorUpdatingConsentDetail": "Не удалось обновить настройки согласия на метрики",
"learnMoreAboutData": "Узнать больше о сборе данных"
},
"systemLocations": "Системные места",
"unhandledError": "Неизвестная ошибка"
"unhandledError": "Неизвестная ошибка",
"updateConsent": "Вы ранее согласились на отчетность об ошибках. Теперь мы отслеживаем событийные метрики, чтобы помочь выявить ошибки и улучшить приложение. Личная идентифицируемая информация не собирается."
},
"issueReport": {
"contactFollowUp": "Свяжитесь со мной для уточнения",

View File

@@ -113,7 +113,7 @@
}
},
"CLIPLoader": {
"description": "[Рецепты]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5",
"description": "[Рецепты]\n\nstable_diffusion: clip-l\nstable_cascade: clip-g\nsd3: t5 / clip-g / clip-l\nstable_audio: t5\nmochi: t5\ncosmos: old t5 xxl",
"display_name": "Загрузить CLIP",
"inputs": {
"clip_name": {
@@ -862,6 +862,32 @@
}
}
},
"CosmosImageToVideoLatent": {
"display_name": "CosmosImageToVideoLatent",
"inputs": {
"batch_size": {
"name": "размер_пакета"
},
"end_image": {
"name": онечное_изображение"
},
"height": {
"name": "высота"
},
"length": {
"name": "длина"
},
"start_image": {
"name": ачальное_изображение"
},
"vae": {
"name": "vae"
},
"width": {
"name": "ширина"
}
}
},
"CreateHookKeyframe": {
"display_name": "Создать ключевой кадр хука",
"inputs": {
@@ -1230,6 +1256,23 @@
}
}
},
"EmptyCosmosLatentVideo": {
"display_name": "EmptyCosmosLatentVideo",
"inputs": {
"batch_size": {
"name": "размер_пакета"
},
"height": {
"name": "высота"
},
"length": {
"name": "длина"
},
"width": {
"name": "ширина"
}
}
},
"EmptyHunyuanLatentVideo": {
"display_name": устойHunyuanLatentVideo",
"inputs": {
@@ -4825,6 +4868,17 @@
}
}
},
"SetFirstSigma": {
"display_name": "SetFirstSigma",
"inputs": {
"sigma": {
"name": "сигма"
},
"sigmas": {
"name": "сигмы"
}
}
},
"SetHookKeyframes": {
"display_name": "Установить ключевые кадры хука",
"inputs": {

View File

@@ -3,7 +3,7 @@
"name": "Автоматически проверять обновления"
},
"Comfy-Desktop_SendStatistics": {
"name": "Отправлять анонимные отчеты о сбоях"
"name": "Отправлять анонимную статистику использования"
},
"Comfy-Desktop_WindowStyle": {
"name": "Стиль окна",

View File

@@ -107,6 +107,7 @@
"noTasksFound": "未找到任务",
"noTasksFoundMessage": "队列中没有任务。",
"noWorkflowsFound": "未找到工作流。",
"ok": "确定",
"openNewIssue": "打开新问题",
"overwrite": "覆盖",
"reconnected": "已重新连接",
@@ -129,6 +130,7 @@
"searchWorkflows": "搜索工作流",
"settings": "设置",
"showReport": "显示报告",
"success": "成功",
"systemInfo": "系统信息",
"terminal": "终端",
"upload": "上传",
@@ -186,6 +188,7 @@
"selectGpu": "选择 GPU",
"selectGpuDescription": "选择你拥有的 GPU 类型"
},
"helpImprove": "请帮助我们改进ComfyUI",
"installLocation": "安装位置",
"installLocationDescription": "选择 ComfyUI 用户数据的存放目录。将安装一个 Python 环境到所选位置。请确保所选磁盘有足够的空间(约 15GB。",
"installLocationTooltip": "ComfyUI 的用户数据目录。存储:\n- Python 环境\n- 模型\n- 自定义节点\n",
@@ -197,35 +200,47 @@
"title": "手动配置",
"virtualEnvironmentPath": "虚拟环境路径"
},
"metricsDisabled": "禁用度量",
"metricsEnabled": "启用度量",
"migrateFromExistingInstallation": "从现有安装迁移",
"migration": "迁移",
"migrationOptional": "迁移是可选的。如果您之前没有安装过 ComfyUI可以跳过此步骤。",
"migrationSourcePathDescription": "如果您有可用的 ComfyUI我们可以将您的现有用户文件和模型复制/链接到新安装。",
"migrationSourcePathDescription": "如果您已有现有的ComfyUI安装,我们可以复制/链接您现有用户文件和模型到新安装。您现有的ComfyUI安装将不会受到影响。",
"moreInfo": "有关更多信息,请阅读我们的",
"parentMissing": "路径不存在 - 请先创建包含该路径的目录",
"pathExists": "目录已存在 - 请确保您已备份全部数据",
"pathValidationFailed": "路径验证失败",
"privacyPolicy": "隐私政策",
"selectItemsToMigrate": "选择要迁移的项目",
"settings": {
"allowMetrics": "崩溃报告",
"allowMetricsDescription": "发送匿名崩溃报告帮助改ComfyUI。报告不会收集任何个人信息或工作流内容。您可以随时在设置菜单中禁用此功能。",
"allowMetrics": "使用情况指标",
"allowMetricsDescription": "通过发送匿名使用情况指标来帮助改ComfyUI。不会收集任何个人信息或工作流内容。",
"autoUpdate": "自动更新",
"autoUpdateDescription": "更新可用时自动更新。您将在安装更新之前收到通知。",
"dataCollectionDialog": {
"customNodeConfigurations": "自定义节点配置",
"errorReports": "错误信息和堆栈跟踪",
"fileSystemInformation": "文件系统信息",
"personalInformation": "个人信息",
"systemInfo": "硬件、操作系统类型和应用版本",
"collect": {
"errorReports": "错误报告和堆栈跟踪",
"systemInfo": "硬件,操作系统类型和应用版本",
"userJourneyEvents": "用户旅程事件"
},
"doNotCollect": {
"customNodeConfigurations": "自定义节点配置",
"fileSystemInformation": "文件系统信息",
"personalInformation": "个人信息",
"workflowContents": "工作流内容"
},
"title": "关于数据收集",
"viewFullPolicy": "查看完整政策",
"whatWeCollect": "我们收集的内容:",
"whatWeDoNotCollect": "我们不收集的内容:",
"workflowContent": "工作流内容",
"workflowContents": "工作流内容"
"whatWeDoNotCollect": "我们不收集的内容:"
},
"errorUpdatingConsent": "更新同意错误",
"errorUpdatingConsentDetail": "无法更新度量同意设置",
"learnMoreAboutData": "了解更多关于数据收集的信息"
},
"systemLocations": "系统位置",
"unhandledError": "未知错误"
"unhandledError": "未知错误",
"updateConsent": "您之前选择了报告崩溃。我们现在正在跟踪基于事件的度量,以帮助识别错误并改进应用程序。我们不收集任何个人可识别信息。"
},
"issueReport": {
"contactFollowUp": "跟进联系我",

View File

@@ -113,7 +113,7 @@
}
},
"CLIPLoader": {
"description": "[配方]\n\nStable Diffusionclip-l\nStable Cascadeclip-g\nSD3t5 / clip-g / clip-l\nStable Audiot5\nMochit5",
"description": "[配方]\n\nStable Diffusionclip-l\nStable Cascadeclip-g\nSD3t5 / clip-g / clip-l\nStable Audiot5\nMochit5\ncosmosold t5 xxl",
"display_name": "加载CLIP",
"inputs": {
"clip_name": {
@@ -862,6 +862,32 @@
}
}
},
"CosmosImageToVideoLatent": {
"display_name": "Cosmos图像到视频潜在",
"inputs": {
"batch_size": {
"name": "批量大小"
},
"end_image": {
"name": "结束图像"
},
"height": {
"name": "高度"
},
"length": {
"name": "长度"
},
"start_image": {
"name": "开始图像"
},
"vae": {
"name": "vae"
},
"width": {
"name": "宽度"
}
}
},
"CreateHookKeyframe": {
"display_name": "创建约束关键帧",
"inputs": {
@@ -1230,6 +1256,23 @@
}
}
},
"EmptyCosmosLatentVideo": {
"display_name": "空的Cosmos潜在视频",
"inputs": {
"batch_size": {
"name": "批量大小"
},
"height": {
"name": "高度"
},
"length": {
"name": "长度"
},
"width": {
"name": "宽度"
}
}
},
"EmptyHunyuanLatentVideo": {
"display_name": "空Latent视频混元",
"inputs": {
@@ -4825,6 +4868,17 @@
}
}
},
"SetFirstSigma": {
"display_name": "设置第一个Sigma",
"inputs": {
"sigma": {
"name": "sigma"
},
"sigmas": {
"name": "sigmas"
}
}
},
"SetHookKeyframes": {
"display_name": "设置约束关键帧",
"inputs": {

View File

@@ -3,7 +3,7 @@
"name": "自动检查更新"
},
"Comfy-Desktop_SendStatistics": {
"name": "发送匿名崩溃报告"
"name": "发送匿名使用情况统计"
},
"Comfy-Desktop_WindowStyle": {
"name": "窗口样式",

View File

@@ -92,6 +92,24 @@ const router = createRouter({
name: 'ManualConfigurationView',
component: () => import('@/views/ManualConfigurationView.vue'),
beforeEnter: guardElectronAccess
},
{
path: '/metrics-consent',
name: 'MetricsConsentView',
component: () => import('@/views/MetricsConsentView.vue'),
beforeEnter: guardElectronAccess
},
{
path: 'desktop-start',
name: 'DesktopStartView',
component: () => import('@/views/DesktopStartView.vue'),
beforeEnter: guardElectronAccess
},
{
path: 'issue-report',
name: 'IssueReportView',
component: () => import('@/views/IssueReportView.vue'),
beforeEnter: guardElectronAccess
}
]
}

View File

@@ -7,6 +7,7 @@ import type {
ExecutingWsMessage,
ExecutionCachedWsMessage,
ExecutionErrorWsMessage,
ExecutionInterruptedWsMessage,
ExecutionStartWsMessage,
ExecutionSuccessWsMessage,
ExtensionsResponse,
@@ -59,6 +60,7 @@ interface BackendApiCalls {
execution_start: ExecutionStartWsMessage
execution_success: ExecutionSuccessWsMessage
execution_error: ExecutionErrorWsMessage
execution_interrupted: ExecutionInterruptedWsMessage
execution_cached: ExecutionCachedWsMessage
logs: LogsWsMessage
/** Mr Blob Preview, I presume? */
@@ -355,6 +357,7 @@ export class ComfyApi extends EventTarget {
break
case 'execution_start':
case 'execution_error':
case 'execution_interrupted':
case 'execution_cached':
case 'execution_success':
case 'progress':

View File

@@ -417,6 +417,12 @@ LGraphNode.prototype.addDOMWidget = function <
element.dataset.collapsed = this.flags?.collapsed ? 'true' : 'false'
}
const { onConfigure } = this
this.onConfigure = function () {
onConfigure?.apply(this, arguments)
element.dataset.collapsed = this.flags?.collapsed ? 'true' : 'false'
}
const onRemoved = this.onRemoved
this.onRemoved = function () {
element.remove()

View File

@@ -176,10 +176,6 @@ export const useWorkflowService = () => {
workflow: ComfyWorkflow,
options: { warnIfUnsaved: boolean } = { warnIfUnsaved: true }
): Promise<boolean> => {
if (!workflow.isLoaded) {
return true
}
if (workflow.isModified && options.warnIfUnsaved) {
const confirmed = await dialogService.confirm({
title: t('sideToolbar.workflowTab.dirtyCloseTitle'),

View File

@@ -1,3 +1,4 @@
import _ from 'lodash'
import { defineStore } from 'pinia'
import { computed, markRaw, ref } from 'vue'
@@ -130,6 +131,10 @@ export interface WorkflowStore {
openWorkflows: LoadedComfyWorkflow[]
openedWorkflowIndexShift: (shift: number) => LoadedComfyWorkflow | null
openWorkflow: (workflow: ComfyWorkflow) => Promise<LoadedComfyWorkflow>
openWorkflowsInBackground: (paths: {
left?: string[]
right?: string[]
}) => void
isOpen: (workflow: ComfyWorkflow) => boolean
isBusy: boolean
closeWorkflow: (workflow: ComfyWorkflow) => Promise<void>
@@ -213,6 +218,36 @@ export const useWorkflowStore = defineStore('workflow', () => {
const isOpen = (workflow: ComfyWorkflow) =>
openWorkflowPathSet.value.has(workflow.path)
/**
* Add paths to the list of open workflow paths without loading the files
* or changing the active workflow.
*
* @param paths - The workflows to open, specified as:
* - `left`: Workflows to be added to the left.
* - `right`: Workflows to be added to the right.
*
* Invalid paths (non-strings or paths not found in `workflowLookup.value`)
* will be ignored. Duplicate paths are automatically removed.
*/
const openWorkflowsInBackground = (paths: {
left?: string[]
right?: string[]
}) => {
const { left = [], right = [] } = paths
if (!left.length && !right.length) return
const isValidPath = (
path: unknown
): path is keyof typeof workflowLookup.value =>
typeof path === 'string' && path in workflowLookup.value
openWorkflowPaths.value = _.union(
left,
openWorkflowPaths.value,
right
).filter(isValidPath)
}
/**
* Set the workflow as the active workflow.
* @param workflow The workflow to open.
@@ -389,6 +424,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
openWorkflows,
openedWorkflowIndexShift,
openWorkflow,
openWorkflowsInBackground,
isOpen,
isBusy,
closeWorkflow,

View File

@@ -1,17 +1,16 @@
import { ElectronAPI } from '@comfyorg/comfyui-electron-types'
export type InstallOptions = Parameters<ElectronAPI['installComfyUI']>[0]
export type TorchDeviceType = InstallOptions['device']
import {
ElectronAPI,
ElectronContextMenuOptions
} from '@comfyorg/comfyui-electron-types'
export function isElectron() {
return 'electronAPI' in window && window['electronAPI'] !== undefined
return 'electronAPI' in window && window.electronAPI !== undefined
}
export function electronAPI() {
return (window as any)['electronAPI'] as ElectronAPI
return (window as any).electronAPI as ElectronAPI
}
type NativeContextOptions = Parameters<ElectronAPI['showContextMenu']>[0]
export function showNativeMenu(options?: NativeContextOptions) {
export function showNativeMenu(options?: ElectronContextMenuOptions) {
electronAPI()?.showContextMenu(options)
}

View File

@@ -0,0 +1,13 @@
<template>
<BaseViewTemplate dark>
<div class="max-w-screen-sm w-screen p-8">
<ProgressBar mode="indeterminate" />
</div>
</BaseViewTemplate>
</template>
<script setup lang="ts">
import ProgressBar from 'primevue/progressbar'
import BaseViewTemplate from './templates/BaseViewTemplate.vue'
</script>

View File

@@ -55,6 +55,7 @@ const toast = useToast()
const settingStore = useSettingStore()
const executionStore = useExecutionStore()
const colorPaletteStore = useColorPaletteStore()
const queueStore = useQueueStore()
watch(
() => colorPaletteStore.completedActivePalette,
@@ -76,6 +77,29 @@ watch(
{ immediate: true }
)
if (isElectron()) {
watch(
() => queueStore.tasks,
(newTasks, oldTasks) => {
// Report tasks that previously running but are now completed (i.e. in history)
const oldRunningTaskIds = new Set(
oldTasks.filter((task) => task.isRunning).map((task) => task.promptId)
)
newTasks
.filter(
(task) => oldRunningTaskIds.has(task.promptId) && task.isHistory
)
.forEach((task) => {
electronAPI().Events.incrementUserProperty(
`execution:${task.displayStatus.toLowerCase()}`,
1
)
})
},
{ deep: true }
)
}
watchEffect(() => {
const fontSize = settingStore.get('Comfy.TextareaWidget.FontSize')
document.documentElement.style.setProperty(
@@ -110,9 +134,7 @@ watchEffect(() => {
})
watchEffect(() => {
useQueueStore().maxHistoryItems = settingStore.get(
'Comfy.Queue.MaxHistoryItems'
)
queueStore.maxHistoryItems = settingStore.get('Comfy.Queue.MaxHistoryItems')
})
const init = () => {
@@ -126,8 +148,9 @@ const init = () => {
}
const queuePendingTaskCountStore = useQueuePendingTaskCountStore()
const onStatus = (e: CustomEvent<StatusWsMessageStatus>) => {
const onStatus = async (e: CustomEvent<StatusWsMessageStatus>) => {
queuePendingTaskCountStore.update(e)
await queueStore.update()
}
const reconnectingMessage: ToastMessageOptions = {

View File

@@ -6,7 +6,7 @@
<Stepper
class="h-full p-8 2xl:p-16"
value="0"
@update:value="setHighestStep"
@update:value="handleStepChange"
>
<StepList class="select-none">
<Step value="0">
@@ -103,6 +103,10 @@
</template>
<script setup lang="ts">
import type {
InstallOptions,
TorchDeviceType
} from '@comfyorg/comfyui-electron-types'
import Button from 'primevue/button'
import Step from 'primevue/step'
import StepList from 'primevue/steplist'
@@ -116,11 +120,7 @@ import DesktopSettingsConfiguration from '@/components/install/DesktopSettingsCo
import GpuPicker from '@/components/install/GpuPicker.vue'
import InstallLocationPicker from '@/components/install/InstallLocationPicker.vue'
import MigrationPicker from '@/components/install/MigrationPicker.vue'
import {
type InstallOptions,
type TorchDeviceType,
electronAPI
} from '@/utils/envUtil'
import { electronAPI } from '@/utils/envUtil'
import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'
const device = ref<TorchDeviceType>(null)
@@ -137,6 +137,14 @@ const allowMetrics = ref(true)
/** Forces each install step to be visited at least once. */
const highestStep = ref(0)
const handleStepChange = (value: string | number) => {
setHighestStep(value)
electronAPI().Events.trackEvent('install_stepper_change', {
step: value
})
}
const setHighestStep = (value: string | number) => {
const int = typeof value === 'number' ? value : parseInt(value, 10)
if (!isNaN(int) && int > highestStep.value) highestStep.value = int
@@ -167,12 +175,18 @@ onMounted(async () => {
if (!electron) return
const detectedGpu = await electron.Config.getDetectedGpu()
if (detectedGpu === 'mps' || detectedGpu === 'nvidia')
if (detectedGpu === 'mps' || detectedGpu === 'nvidia') {
device.value = detectedGpu
}
electronAPI().Events.trackEvent('install_stepper_change', {
step: '0',
gpu: detectedGpu
})
})
</script>
<style lang="postcss" scoped>
<style scoped>
:deep(.p-steppanel) {
@apply bg-transparent;
}

View File

@@ -0,0 +1,38 @@
<template>
<BaseViewTemplate dark> </BaseViewTemplate>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import ReportIssuePanel from '@/components/dialog/content/error/ReportIssuePanel.vue'
import { useDialogStore } from '@/stores/dialogStore'
import BaseViewTemplate from '@/views/templates/BaseViewTemplate.vue'
const router = useRouter()
const dialogStore = useDialogStore()
onMounted(() => {
dialogStore.showDialog({
key: 'issue-report',
component: ReportIssuePanel,
dialogComponentProps: {
onClose: () => router.push('/')
},
props: {
errorType: 'desktop',
defaultFields: ['SystemStats', 'Logs', 'Settings'],
extraFields: [
{
label: 'Desktop Version',
value: 'desktopVersion',
optIn: false,
data: { desktopVersion: '1.4.2' } // electronAPI().getVersion()
}
],
pt: { root: { class: 'border-none' } }
}
})
})
</script>

View File

@@ -75,8 +75,8 @@ onMounted(async () => {
})
</script>
<style>
:root {
<style scoped>
.p-tag {
--p-tag-gap: 0.5rem;
}

View File

@@ -0,0 +1,83 @@
<template>
<BaseViewTemplate dark>
<div class="h-full p-8 2xl:p-16 flex flex-col items-center justify-center">
<div
class="bg-neutral-800 rounded-lg shadow-lg p-6 w-full max-w-[600px] flex flex-col gap-6"
>
<h2 class="text-3xl font-semibold text-neutral-100">
{{ $t('install.helpImprove') }}
</h2>
<p class="text-neutral-400">
{{ $t('install.updateConsent') }}
</p>
<p class="text-neutral-400">
{{ $t('install.moreInfo') }}
<a
href="https://comfy.org/privacy"
target="_blank"
class="text-blue-400 hover:text-blue-300 underline"
>
{{ $t('install.privacyPolicy') }} </a
>.
</p>
<div class="flex items-center gap-4">
<ToggleSwitch
v-model="allowMetrics"
aria-describedby="metricsDescription"
/>
<span id="metricsDescription" class="text-neutral-100">
{{
allowMetrics
? $t('install.metricsEnabled')
: $t('install.metricsDisabled')
}}
</span>
</div>
<div class="flex pt-6 justify-end">
<Button
:label="$t('g.ok')"
icon="pi pi-check"
:loading="isUpdating"
iconPos="right"
@click="updateConsent"
/>
</div>
</div>
</div>
</BaseViewTemplate>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import ToggleSwitch from 'primevue/toggleswitch'
import { useToast } from 'primevue/usetoast'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { electronAPI } from '@/utils/envUtil'
const toast = useToast()
const { t } = useI18n()
const allowMetrics = ref(true)
const router = useRouter()
const isUpdating = ref(false)
const updateConsent = async () => {
isUpdating.value = true
try {
await electronAPI().setMetricsConsent(allowMetrics.value)
} catch (error) {
toast.add({
severity: 'error',
summary: t('install.errorUpdatingConsent'),
detail: t('install.errorUpdatingConsentDetail'),
life: 3000
})
} finally {
isUpdating.value = false
}
router.push('/')
}
</script>

View File

@@ -78,7 +78,7 @@ const continueToInstall = () => {
}
</script>
<style>
<style scoped>
.sad-container {
@apply grid items-center justify-evenly;
grid-template-columns: 25rem 1fr;

View File

@@ -78,7 +78,7 @@ const terminalCreated = (
) => {
xterm = terminal
useAutoSize(root, true, true)
useAutoSize({ root, autoRows: true, autoCols: true })
electron.onLogMessage((message: string) => {
terminal.write(message)
})

View File

@@ -28,7 +28,7 @@ import { electronAPI, isElectron } from '@/utils/envUtil'
const props = withDefaults(
defineProps<{
dark: boolean
dark?: boolean
}>(),
{
dark: false

View File

@@ -3,6 +3,7 @@ import { createPinia, setActivePinia } from 'pinia'
import { api } from '@/scripts/api'
import { defaultGraph, defaultGraphJSON } from '@/scripts/defaultGraph'
import {
ComfyWorkflow,
LoadedComfyWorkflow,
useWorkflowBookmarkStore,
useWorkflowStore
@@ -161,6 +162,97 @@ describe('useWorkflowStore', () => {
})
})
describe('openWorkflowsInBackground', () => {
let workflowA: ComfyWorkflow
let workflowB: ComfyWorkflow
let workflowC: ComfyWorkflow
const openWorkflowPaths = () =>
store.openWorkflows.filter((w) => store.isOpen(w)).map((w) => w.path)
beforeEach(async () => {
await syncRemoteWorkflows(['a.json', 'b.json', 'c.json'])
workflowA = store.getWorkflowByPath('workflows/a.json')!
workflowB = store.getWorkflowByPath('workflows/b.json')!
workflowC = store.getWorkflowByPath('workflows/c.json')!
;(api.getUserData as jest.Mock).mockResolvedValue({
status: 200,
text: () => Promise.resolve(defaultGraphJSON)
})
})
it('should open workflows adjacent to the active workflow', async () => {
await store.openWorkflow(workflowA)
store.openWorkflowsInBackground({
left: [workflowB.path],
right: [workflowC.path]
})
expect(openWorkflowPaths()).toEqual([
workflowB.path,
workflowA.path,
workflowC.path
])
})
it('should not change the active workflow', async () => {
await store.openWorkflow(workflowA)
store.openWorkflowsInBackground({
left: [workflowC.path],
right: [workflowB.path]
})
expect(store.activeWorkflow).not.toBeUndefined()
expect(store.activeWorkflow!.path).toBe(workflowA.path)
})
it('should open workflows when none are active', async () => {
expect(store.openWorkflows.length).toBe(0)
store.openWorkflowsInBackground({
left: [workflowA.path],
right: [workflowB.path]
})
expect(openWorkflowPaths()).toEqual([workflowA.path, workflowB.path])
})
it('should not open duplicate workflows', async () => {
store.openWorkflowsInBackground({
left: [workflowA.path, workflowB.path, workflowA.path],
right: [workflowB.path, workflowA.path, workflowB.path]
})
expect(openWorkflowPaths()).toEqual([workflowA.path, workflowB.path])
})
it('should not open workflow that is already open', async () => {
await store.openWorkflow(workflowA)
store.openWorkflowsInBackground({
left: [workflowA.path]
})
expect(openWorkflowPaths()).toEqual([workflowA.path])
expect(store.activeWorkflow?.path).toBe(workflowA.path)
})
it('should ignore invalid or deleted workflow paths', async () => {
await store.openWorkflow(workflowA)
store.openWorkflowsInBackground({
left: ['workflows/invalid::$-path.json'],
right: ['workflows/deleted-since-last-session.json']
})
expect(openWorkflowPaths()).toEqual([workflowA.path])
expect(store.activeWorkflow?.path).toBe(workflowA.path)
})
it('should do nothing when given an empty argument', async () => {
await store.openWorkflow(workflowA)
store.openWorkflowsInBackground({})
expect(openWorkflowPaths()).toEqual([workflowA.path])
store.openWorkflowsInBackground({ left: [], right: [] })
expect(openWorkflowPaths()).toEqual([workflowA.path])
expect(store.activeWorkflow?.path).toBe(workflowA.path)
})
})
describe('renameWorkflow', () => {
it('should rename workflow and update bookmarks', async () => {
const workflow = store.createTemporary('dir/test.json')

View File

@@ -180,11 +180,7 @@ export default defineConfig({
process.env.npm_package_version
),
__SENTRY_ENABLED__: JSON.stringify(
!(
process.env.CI === 'true' ||
process.env.NODE_ENV === 'development' ||
!process.env.SENTRY_DSN
)
!(process.env.NODE_ENV === 'development' || !process.env.SENTRY_DSN)
),
__SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN || '')
},

View File

@@ -57,8 +57,18 @@ const mockElectronAPI: Plugin = {
changeTheme: () => {},
Config: {
setWindowStyle: () => {},
getWindowStyle: () => Promise.resolve('default')
}
getWindowStyle: () => Promise.resolve('default'),
getDetectedGpu: () => Promise.resolve('nvidia')
},
Events: {
trackEvent: (event_name, event_data) => {
console.log('trackEvent', event_name, event_data)
},
incrementUserProperty: (property, value) => {
console.log('incrementUserProperty', property, value)
}
},
setMetricsConsent: (consent) => {}
};`
}
]