mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-25 06:47:35 +00:00
Compare commits
4 Commits
refactor/e
...
v1.41.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d23c8026d0 | ||
|
|
a309281ac5 | ||
|
|
e9bf113686 | ||
|
|
1ab48b42a7 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.41.5",
|
||||
"version": "1.41.6",
|
||||
"private": true,
|
||||
"description": "Official front-end implementation of ComfyUI",
|
||||
"homepage": "https://comfy.org",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<!-- First panel: sidebar when left, properties when right -->
|
||||
<SplitterPanel
|
||||
v-if="
|
||||
!focusMode && (sidebarLocation === 'left' || rightSidePanelVisible)
|
||||
!focusMode && (sidebarLocation === 'left' || showOffsideSplitter)
|
||||
"
|
||||
:class="
|
||||
sidebarLocation === 'left'
|
||||
@@ -85,7 +85,7 @@
|
||||
<!-- Last panel: properties when left, sidebar when right -->
|
||||
<SplitterPanel
|
||||
v-if="
|
||||
!focusMode && (sidebarLocation === 'right' || rightSidePanelVisible)
|
||||
!focusMode && (sidebarLocation === 'right' || showOffsideSplitter)
|
||||
"
|
||||
:class="
|
||||
sidebarLocation === 'right'
|
||||
@@ -124,6 +124,7 @@ import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
@@ -144,9 +145,13 @@ const unifiedWidth = computed(() =>
|
||||
|
||||
const { focusMode } = storeToRefs(workspaceStore)
|
||||
|
||||
const appModeStore = useAppModeStore()
|
||||
const { activeSidebarTabId, activeSidebarTab } = storeToRefs(sidebarTabStore)
|
||||
const { bottomPanelVisible } = storeToRefs(useBottomPanelStore())
|
||||
const { isOpen: rightSidePanelVisible } = storeToRefs(rightSidePanelStore)
|
||||
const showOffsideSplitter = computed(
|
||||
() => rightSidePanelVisible.value || appModeStore.mode === 'builder:select'
|
||||
)
|
||||
|
||||
const sidebarPanelVisible = computed(() => activeSidebarTab.value !== null)
|
||||
|
||||
|
||||
324
src/components/builder/AppBuilder.vue
Normal file
324
src/components/builder/AppBuilder.vue
Normal file
@@ -0,0 +1,324 @@
|
||||
<script setup lang="ts">
|
||||
import { remove } from 'es-toolkit'
|
||||
import { computed, ref, toValue } from 'vue'
|
||||
import type { MaybeRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DraggableList from '@/components/common/DraggableList.vue'
|
||||
import IoItem from '@/components/builder/IoItem.vue'
|
||||
import PropertiesAccordionItem from '@/components/rightSidePanel/layout/PropertiesAccordionItem.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
||||
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
||||
import { TitleMode } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { BaseWidget } from '@/lib/litegraph/src/widgets/BaseWidget'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
|
||||
import { app } from '@/scripts/app'
|
||||
import { DOMWidgetImpl } from '@/scripts/domWidget'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
type BoundStyle = { top: string; left: string; width: string; height: string }
|
||||
|
||||
const appModeStore = useAppModeStore()
|
||||
const canvasInteractions = useCanvasInteractions()
|
||||
const canvasStore = useCanvasStore()
|
||||
const settingStore = useSettingStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { t } = useI18n()
|
||||
const canvas: LGraphCanvas = canvasStore.getCanvas()
|
||||
|
||||
const hoveringSelectable = ref(false)
|
||||
|
||||
workflowStore.activeWorkflow?.changeTracker?.reset()
|
||||
|
||||
const inputsWithState = computed(() =>
|
||||
appModeStore.selectedInputs.map(([nodeId, widgetName]) => {
|
||||
const node = app.rootGraph.getNodeById(nodeId)
|
||||
const widget = node?.widgets?.find((w) => w.name === widgetName)
|
||||
if (!node || !widget) return { nodeId, widgetName }
|
||||
|
||||
const input = node.inputs.find((i) => i.widget?.name === widget.name)
|
||||
const rename = input && (() => renameWidget(widget, input))
|
||||
|
||||
return {
|
||||
nodeId,
|
||||
widgetName,
|
||||
label: widget.label,
|
||||
subLabel: node.title,
|
||||
rename
|
||||
}
|
||||
})
|
||||
)
|
||||
const outputsWithState = computed<[NodeId, string][]>(() =>
|
||||
appModeStore.selectedOutputs.map((nodeId) => [
|
||||
nodeId,
|
||||
app.rootGraph.getNodeById(nodeId)?.title ?? String(nodeId)
|
||||
])
|
||||
)
|
||||
|
||||
async function renameWidget(widget: IBaseWidget, input: INodeInputSlot) {
|
||||
const newLabel = await useDialogService().prompt({
|
||||
title: t('g.rename'),
|
||||
message: t('g.enterNewNamePrompt'),
|
||||
defaultValue: widget.label,
|
||||
placeholder: widget.name
|
||||
})
|
||||
if (newLabel === null) return
|
||||
widget.label = newLabel || undefined
|
||||
input.label = newLabel || undefined
|
||||
widget.callback?.(widget.value)
|
||||
useCanvasStore().canvas?.setDirty(true)
|
||||
}
|
||||
|
||||
function getHovered(
|
||||
e: MouseEvent
|
||||
): undefined | [LGraphNode, undefined] | [LGraphNode, IBaseWidget] {
|
||||
const { graph } = canvas
|
||||
if (!canvas || !graph) return
|
||||
|
||||
if (settingStore.get('Comfy.VueNodes.Enabled')) return undefined
|
||||
if (!e) return
|
||||
|
||||
canvas.adjustMouseEvent(e)
|
||||
const node = graph.getNodeOnPos(e.canvasX, e.canvasY)
|
||||
if (!node) return
|
||||
|
||||
const widget = node.getWidgetOnPos(e.canvasX, e.canvasY, false)
|
||||
|
||||
if (widget || node.constructor.nodeData?.output_node) return [node, widget]
|
||||
}
|
||||
|
||||
function getBounding(nodeId: NodeId, widgetName?: string) {
|
||||
if (settingStore.get('Comfy.VueNodes.Enabled')) return undefined
|
||||
const node = app.rootGraph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
|
||||
const titleOffset =
|
||||
node.title_mode === TitleMode.NORMAL_TITLE ? LiteGraph.NODE_TITLE_HEIGHT : 0
|
||||
|
||||
if (!widgetName)
|
||||
return {
|
||||
width: `${node.size[0]}px`,
|
||||
height: `${node.size[1] + titleOffset}px`,
|
||||
left: `${node.pos[0]}px`,
|
||||
top: `${node.pos[1] - titleOffset}px`
|
||||
}
|
||||
const widget = node.widgets?.find((w) => w.name === widgetName)
|
||||
if (!widget) return
|
||||
|
||||
const margin = widget instanceof DOMWidgetImpl ? widget.margin : undefined
|
||||
const marginX = margin ?? BaseWidget.margin
|
||||
const height =
|
||||
(widget.computedHeight !== undefined
|
||||
? widget.computedHeight - 4
|
||||
: LiteGraph.NODE_WIDGET_HEIGHT) - (margin ? 2 * margin - 4 : 0)
|
||||
return {
|
||||
width: `${node.size[0] - marginX * 2}px`,
|
||||
height: `${height}px`,
|
||||
left: `${node.pos[0] + marginX}px`,
|
||||
top: `${node.pos[1] + widget.y + (margin ?? 0)}px`
|
||||
}
|
||||
}
|
||||
|
||||
function handleDown(e: MouseEvent) {
|
||||
const [node] = getHovered(e) ?? []
|
||||
if (!node || e.button > 0) canvasInteractions.forwardEventToCanvas(e)
|
||||
}
|
||||
function handleClick(e: MouseEvent) {
|
||||
const [node, widget] = getHovered(e) ?? []
|
||||
if (!node) return canvasInteractions.forwardEventToCanvas(e)
|
||||
|
||||
if (!widget) {
|
||||
if (!node.constructor.nodeData?.output_node)
|
||||
return canvasInteractions.forwardEventToCanvas(e)
|
||||
const index = appModeStore.selectedOutputs.findIndex((id) => id === node.id)
|
||||
if (index === -1) appModeStore.selectedOutputs.push(node.id)
|
||||
else appModeStore.selectedOutputs.splice(index, 1)
|
||||
return
|
||||
}
|
||||
|
||||
const index = appModeStore.selectedInputs.findIndex(
|
||||
([nodeId, widgetName]) => node.id === nodeId && widget.name === widgetName
|
||||
)
|
||||
if (index === -1) appModeStore.selectedInputs.push([node.id, widget.name])
|
||||
else appModeStore.selectedInputs.splice(index, 1)
|
||||
}
|
||||
|
||||
function nodeToDisplayTuple(
|
||||
n: LGraphNode
|
||||
): [NodeId, MaybeRef<BoundStyle> | undefined, boolean] {
|
||||
return [
|
||||
n.id,
|
||||
getBounding(n.id),
|
||||
appModeStore.selectedOutputs.some((id) => n.id === id)
|
||||
]
|
||||
}
|
||||
|
||||
const renderedOutputs = computed(() => {
|
||||
void appModeStore.selectedOutputs.length
|
||||
return canvas
|
||||
.graph!.nodes.filter((n) => n.constructor.nodeData?.output_node)
|
||||
.map(nodeToDisplayTuple)
|
||||
})
|
||||
const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
() =>
|
||||
appModeStore.selectedInputs.map(([nodeId, widgetName]) => [
|
||||
`${nodeId}: ${widgetName}`,
|
||||
getBounding(nodeId, widgetName)
|
||||
])
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex font-bold p-2 border-border-subtle border-b items-center">
|
||||
{{ t('linearMode.builder.title') }}
|
||||
<Button class="ml-auto" @click="appModeStore.exitBuilder">
|
||||
{{ t('linearMode.builder.exit') }}
|
||||
</Button>
|
||||
</div>
|
||||
<PropertiesAccordionItem
|
||||
:label="t('nodeHelpPage.inputs')"
|
||||
enable-empty-state
|
||||
:disabled="!appModeStore.selectedInputs.length"
|
||||
class="border-border-subtle border-b"
|
||||
:tooltip="`${t('linearMode.builder.inputsDesc')}\n${t('linearMode.builder.inputsExample')}`"
|
||||
>
|
||||
<template #label>
|
||||
<div class="flex gap-3">
|
||||
{{ t('nodeHelpPage.inputs') }}
|
||||
<i class="bg-muted-foreground icon-[lucide--circle-alert]" />
|
||||
</div>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddInputs')"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddInputs')"
|
||||
/>
|
||||
<DraggableList v-slot="{ dragClass }" v-model="appModeStore.selectedInputs">
|
||||
<IoItem
|
||||
v-for="{
|
||||
nodeId,
|
||||
widgetName,
|
||||
label,
|
||||
subLabel,
|
||||
rename
|
||||
} in inputsWithState"
|
||||
:key="`${nodeId}: ${widgetName}`"
|
||||
:class="cn(dragClass, 'bg-primary-background/30 p-2 my-2 rounded-lg')"
|
||||
:title="label ?? widgetName"
|
||||
:sub-title="subLabel"
|
||||
:rename
|
||||
:remove="
|
||||
() =>
|
||||
remove(
|
||||
appModeStore.selectedInputs,
|
||||
([id, name]) => nodeId === id && widgetName === name
|
||||
)
|
||||
"
|
||||
/>
|
||||
</DraggableList>
|
||||
</PropertiesAccordionItem>
|
||||
<PropertiesAccordionItem
|
||||
:label="t('nodeHelpPage.outputs')"
|
||||
enable-empty-state
|
||||
:disabled="!appModeStore.selectedOutputs.length"
|
||||
:tooltip="`${t('linearMode.builder.outputsDesc')}\n${t('linearMode.builder.outputsExample')}`"
|
||||
>
|
||||
<template #label>
|
||||
<div class="flex gap-3">
|
||||
{{ t('nodeHelpPage.outputs') }}
|
||||
<i class="bg-muted-foreground icon-[lucide--circle-alert]" />
|
||||
</div>
|
||||
</template>
|
||||
<template #empty>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddOutputs')"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddOutputs')"
|
||||
/>
|
||||
<DraggableList
|
||||
v-slot="{ dragClass }"
|
||||
v-model="appModeStore.selectedOutputs"
|
||||
>
|
||||
<IoItem
|
||||
v-for="([key, title], index) in outputsWithState"
|
||||
:key
|
||||
:class="
|
||||
cn(
|
||||
dragClass,
|
||||
'bg-warning-background/40 p-2 my-2 rounded-lg',
|
||||
index === 0 && 'ring-warning-background ring-2'
|
||||
)
|
||||
"
|
||||
:title
|
||||
:sub-title="String(key)"
|
||||
:remove="() => remove(appModeStore.selectedOutputs, (k) => k === key)"
|
||||
/>
|
||||
</DraggableList>
|
||||
</PropertiesAccordionItem>
|
||||
|
||||
<Teleport to="body">
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'absolute w-full h-full pointer-events-auto',
|
||||
hoveringSelectable ? 'cursor-pointer' : 'cursor-grab'
|
||||
)
|
||||
"
|
||||
@pointerdown="handleDown"
|
||||
@pointermove="hoveringSelectable = !!getHovered($event)"
|
||||
@click="handleClick"
|
||||
@wheel="canvasInteractions.forwardEventToCanvas"
|
||||
>
|
||||
<TransformPane :canvas="canvasStore.getCanvas()">
|
||||
<div
|
||||
v-for="[key, style] in renderedInputs"
|
||||
:key
|
||||
:style="toValue(style)"
|
||||
class="fixed bg-primary-background/30 rounded-lg"
|
||||
/>
|
||||
<div
|
||||
v-for="[key, style, isSelected] in renderedOutputs"
|
||||
:key
|
||||
:style="toValue(style)"
|
||||
:class="
|
||||
cn(
|
||||
'fixed ring-warning-background ring-5 rounded-2xl',
|
||||
!isSelected && 'ring-warning-background/50'
|
||||
)
|
||||
"
|
||||
>
|
||||
<div class="absolute top-0 right-0 size-8">
|
||||
<div
|
||||
v-if="isSelected"
|
||||
class="absolute -top-1/2 -right-1/2 size-full p-2 bg-warning-background rounded-lg"
|
||||
>
|
||||
<i class="icon-[lucide--check] bg-text-foreground size-full" />
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="absolute -top-1/2 -right-1/2 size-full ring-warning-background/50 ring-4 ring-inset bg-component-node-background rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TransformPane>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
@@ -62,6 +62,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import type { AppMode } from '@/stores/appModeStore'
|
||||
@@ -75,6 +76,14 @@ import type { BuilderToolbarStep } from './types'
|
||||
const { t } = useI18n()
|
||||
const appModeStore = useAppModeStore()
|
||||
|
||||
useEventListener(document, 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.key !== 'Escape') return
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
void appModeStore.exitBuilder()
|
||||
})
|
||||
|
||||
const activeStep = computed(() =>
|
||||
appModeStore.isBuilderSaving ? 'save' : appModeStore.mode
|
||||
)
|
||||
|
||||
46
src/components/builder/IoItem.vue
Normal file
46
src/components/builder/IoItem.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Popover from '@/components/ui/Popover.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { rename, remove } = defineProps<{
|
||||
title: string
|
||||
subTitle?: string
|
||||
rename?: () => void
|
||||
remove?: () => void
|
||||
}>()
|
||||
|
||||
const entries = computed(() => {
|
||||
const items = []
|
||||
if (rename)
|
||||
items.push({
|
||||
label: t('g.rename'),
|
||||
command: rename,
|
||||
icon: 'icon-[lucide--pencil]'
|
||||
})
|
||||
if (remove)
|
||||
items.push({
|
||||
label: t('g.delete'),
|
||||
command: remove,
|
||||
icon: 'icon-[lucide--trash-2]'
|
||||
})
|
||||
return items
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="p-2 my-2 rounded-lg flex items-center-safe">
|
||||
<span class="mr-auto" v-text="title" />
|
||||
<span class="text-muted-foreground mr-2 text-end" v-text="subTitle" />
|
||||
<Popover :entries>
|
||||
<template #button>
|
||||
<Button variant="muted-textonly">
|
||||
<i class="icon-[lucide--ellipsis]" />
|
||||
</Button>
|
||||
</template>
|
||||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,12 +33,10 @@ export function useBuilderSave() {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Update this to show the save dialog if it is temp OR if the user has not saved app mode before.
|
||||
// If they have saved app mode before, just save the workflow, but use the initial app mode state not current.
|
||||
|
||||
if (!workflow.isTemporary) {
|
||||
if (!workflow.isTemporary && workflow.activeState.extra?.linearMode) {
|
||||
try {
|
||||
workflow.changeTracker?.checkState()
|
||||
appModeStore.saveSelectedToWorkflow()
|
||||
await workflowService.saveWorkflow(workflow)
|
||||
showSuccessDialog(workflow.filename, appModeStore.isAppMode)
|
||||
} catch {
|
||||
@@ -75,6 +73,7 @@ export function useBuilderSave() {
|
||||
const workflow = workflowStore.activeWorkflow
|
||||
if (!workflow) return
|
||||
|
||||
appModeStore.saveSelectedToWorkflow()
|
||||
const saved = await workflowService.saveWorkflowAs(workflow, {
|
||||
filename,
|
||||
openAsApp
|
||||
|
||||
59
src/components/common/DraggableList.vue
Normal file
59
src/components/common/DraggableList.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts" generic="T">
|
||||
import { onBeforeUnmount, ref, useTemplateRef, watchPostEffect } from 'vue'
|
||||
|
||||
import { DraggableList } from '@/scripts/ui/draggableList'
|
||||
|
||||
const modelValue = defineModel<T[]>({ required: true })
|
||||
const draggableList = ref<DraggableList>()
|
||||
const draggableItems = useTemplateRef('draggableItems')
|
||||
|
||||
watchPostEffect(() => {
|
||||
void modelValue.value.length
|
||||
draggableList.value?.dispose()
|
||||
if (!draggableItems.value?.children?.length) return
|
||||
draggableList.value = new DraggableList(
|
||||
draggableItems.value,
|
||||
'.draggable-item'
|
||||
)
|
||||
draggableList.value.applyNewItemsOrder = function () {
|
||||
const reorderedItems = []
|
||||
|
||||
let oldPosition = -1
|
||||
this.getAllItems().forEach((item, index) => {
|
||||
if (item === this.draggableItem) {
|
||||
oldPosition = index
|
||||
return
|
||||
}
|
||||
if (!this.isItemToggled(item)) {
|
||||
reorderedItems[index] = item
|
||||
return
|
||||
}
|
||||
const newIndex = this.isItemAbove(item) ? index + 1 : index - 1
|
||||
reorderedItems[newIndex] = item
|
||||
})
|
||||
|
||||
for (let index = 0; index < this.getAllItems().length; index++) {
|
||||
const item = reorderedItems[index]
|
||||
if (typeof item === 'undefined') {
|
||||
reorderedItems[index] = this.draggableItem
|
||||
}
|
||||
}
|
||||
const newPosition = reorderedItems.indexOf(this.draggableItem)
|
||||
const itemList = modelValue.value
|
||||
const [item] = itemList.splice(oldPosition, 1)
|
||||
itemList.splice(newPosition, 0, item)
|
||||
modelValue.value = [...itemList]
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
draggableList.value?.dispose()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div ref="draggableItems" class="pb-2 px-2 space-y-0.5 mt-0.5">
|
||||
<slot
|
||||
drag-class="draggable-item drag-handle cursor-grab [&.is-draggable]:cursor-grabbing"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,7 +38,8 @@
|
||||
<BottomPanel />
|
||||
</template>
|
||||
<template v-if="showUI" #right-side-panel>
|
||||
<NodePropertiesPanel v-if="!appModeStore.isBuilderMode" />
|
||||
<AppBuilder v-if="appModeStore.mode === 'builder:select'" />
|
||||
<NodePropertiesPanel v-else-if="!appModeStore.isBuilderMode" />
|
||||
</template>
|
||||
<template #graph-canvas-panel>
|
||||
<GraphCanvasMenu
|
||||
@@ -126,6 +127,7 @@ import {
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { isMiddlePointerInput } from '@/base/pointerUtils'
|
||||
import AppBuilder from '@/components/builder/AppBuilder.vue'
|
||||
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
|
||||
import TopMenuSection from '@/components/TopMenuSection.vue'
|
||||
import BottomPanel from '@/components/bottomPanel/BottomPanel.vue'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import DraggableList from '@/components/common/DraggableList.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import {
|
||||
demoteWidget,
|
||||
@@ -17,10 +18,10 @@ import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import FormSearchInput from '@/renderer/extensions/vueNodes/widgets/components/form/FormSearchInput.vue'
|
||||
import { DraggableList } from '@/scripts/ui/draggableList'
|
||||
import { useLitegraphService } from '@/services/litegraphService'
|
||||
import { usePromotionStore } from '@/stores/promotionStore'
|
||||
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import SubgraphNodeWidget from './SubgraphNodeWidget.vue'
|
||||
|
||||
@@ -30,9 +31,6 @@ const promotionStore = usePromotionStore()
|
||||
const rightSidePanelStore = useRightSidePanelStore()
|
||||
const { searchQuery } = storeToRefs(rightSidePanelStore)
|
||||
|
||||
const draggableList = ref<DraggableList | undefined>(undefined)
|
||||
const draggableItems = ref()
|
||||
|
||||
const promotionEntries = computed(() => {
|
||||
const node = activeNode.value
|
||||
if (!node) return []
|
||||
@@ -195,54 +193,9 @@ function showRecommended() {
|
||||
}
|
||||
}
|
||||
|
||||
function setDraggableState() {
|
||||
draggableList.value?.dispose()
|
||||
if (searchQuery.value || !draggableItems.value?.children?.length) return
|
||||
draggableList.value = new DraggableList(
|
||||
draggableItems.value,
|
||||
'.draggable-item'
|
||||
)
|
||||
draggableList.value.applyNewItemsOrder = function () {
|
||||
const reorderedItems = []
|
||||
|
||||
let oldPosition = -1
|
||||
this.getAllItems().forEach((item, index) => {
|
||||
if (item === this.draggableItem) {
|
||||
oldPosition = index
|
||||
return
|
||||
}
|
||||
if (!this.isItemToggled(item)) {
|
||||
reorderedItems[index] = item
|
||||
return
|
||||
}
|
||||
const newIndex = this.isItemAbove(item) ? index + 1 : index - 1
|
||||
reorderedItems[newIndex] = item
|
||||
})
|
||||
|
||||
for (let index = 0; index < this.getAllItems().length; index++) {
|
||||
const item = reorderedItems[index]
|
||||
if (typeof item === 'undefined') {
|
||||
reorderedItems[index] = this.draggableItem
|
||||
}
|
||||
}
|
||||
const newPosition = reorderedItems.indexOf(this.draggableItem)
|
||||
const aw = activeWidgets.value
|
||||
const [w] = aw.splice(oldPosition, 1)
|
||||
aw.splice(newPosition, 0, w)
|
||||
activeWidgets.value = aw
|
||||
}
|
||||
}
|
||||
watch(filteredActive, () => {
|
||||
setDraggableState()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
setDraggableState()
|
||||
if (activeNode.value) pruneDisconnected(activeNode.value)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
draggableList.value?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -280,19 +233,18 @@ onBeforeUnmount(() => {
|
||||
{{ $t('subgraphStore.hideAll') }}</a
|
||||
>
|
||||
</div>
|
||||
<div ref="draggableItems" class="pb-2 px-2 space-y-0.5 mt-0.5">
|
||||
<DraggableList v-slot="{ dragClass }" v-model="activeWidgets">
|
||||
<SubgraphNodeWidget
|
||||
v-for="[node, widget] in filteredActive"
|
||||
:key="toKey([node, widget])"
|
||||
class="bg-comfy-menu-bg"
|
||||
:class="cn(!searchQuery && dragClass, 'bg-comfy-menu-bg')"
|
||||
:node-title="node.title"
|
||||
:widget-name="widget.name"
|
||||
:is-shown="true"
|
||||
:is-draggable="!searchQuery"
|
||||
:is-physical="node.id === -1"
|
||||
:is-draggable="!searchQuery"
|
||||
@toggle-visibility="demote([node, widget])"
|
||||
/>
|
||||
</div>
|
||||
</DraggableList>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -29,8 +29,7 @@ function getIcon() {
|
||||
cn(
|
||||
'flex py-1 px-2 break-all rounded items-center gap-1',
|
||||
'bg-node-component-surface',
|
||||
props.isDraggable &&
|
||||
'draggable-item drag-handle cursor-grab [&.is-draggable]:cursor-grabbing hover:ring-1 ring-accent-background',
|
||||
props.isDraggable && 'hover:ring-1 ring-accent-background',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
|
||||
@@ -367,7 +367,6 @@ export interface IBaseWidget<
|
||||
/** Widget type (see {@link TWidgetType}) */
|
||||
type: TType
|
||||
value?: TValue
|
||||
vueTrack?: () => void
|
||||
|
||||
/**
|
||||
* Whether the widget value is persisted in the workflow JSON
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "و",
|
||||
"backToLogin": "العودة إلى تسجيل الدخول",
|
||||
"backToSocialLogin": "سجّل باستخدام Google أو Github بدلاً من ذلك",
|
||||
"confirmPasswordLabel": "تأكيد كلمة المرور",
|
||||
"confirmPasswordPlaceholder": "أدخل نفس كلمة المرور مرة أخرى",
|
||||
"didntReceiveEmail": "لم تستلم البريد الإلكتروني؟ اتصل بنا على",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "فشل تسجيل الدخول",
|
||||
"forgotPassword": "هل نسيت كلمة المرور؟",
|
||||
"forgotPasswordError": "فشل في إرسال بريد إعادة تعيين كلمة المرور",
|
||||
"freeTierBadge": "مؤهل للخطة المجانية",
|
||||
"freeTierDescription": "سجّل باستخدام Google للحصول على {credits} رصيد مجاني كل شهر. لا حاجة لبطاقة.",
|
||||
"freeTierDescriptionGeneric": "سجّل باستخدام Google للحصول على رصيد مجاني كل شهر. لا حاجة لبطاقة.",
|
||||
"insecureContextWarning": "هذا الاتصال غير آمن (HTTP) - قد يتم اعتراض بيانات اعتمادك من قبل المهاجمين إذا تابعت تسجيل الدخول.",
|
||||
"loginButton": "تسجيل الدخول",
|
||||
"loginWithGithub": "تسجيل الدخول باستخدام Github",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "إرسال رابط إعادة التعيين",
|
||||
"signInOrSignUp": "تسجيل الدخول / إنشاء حساب",
|
||||
"signUp": "إنشاء حساب",
|
||||
"signUpFreeTierPromo": "جديد هنا؟ {signUp} باستخدام Google للحصول على {credits} رصيد مجاني كل شهر.",
|
||||
"success": "تم تسجيل الدخول بنجاح",
|
||||
"termsLink": "شروط الاستخدام",
|
||||
"termsText": "بالنقر على \"التالي\" أو \"إنشاء حساب\"، فإنك توافق على",
|
||||
"title": "تسجيل الدخول إلى حسابك",
|
||||
"useApiKey": "مفتاح API الخاص بـ Comfy",
|
||||
"useEmailInstead": "استخدم البريد الإلكتروني بدلاً من ذلك",
|
||||
"userAvatar": "صورة المستخدم"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "هل لديك حساب بالفعل؟",
|
||||
"emailLabel": "البريد الإلكتروني",
|
||||
"emailNotEligibleForFreeTier": "التسجيل بالبريد الإلكتروني غير مؤهل للخطة المجانية.",
|
||||
"emailPlaceholder": "أدخل بريدك الإلكتروني",
|
||||
"passwordLabel": "كلمة المرور",
|
||||
"passwordPlaceholder": "أدخل كلمة مرور جديدة",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "الانتقال إلى التحديد"
|
||||
},
|
||||
"beta": "وضع التطبيق تجريبي - أرسل ملاحظاتك",
|
||||
"builder": {
|
||||
"exit": "خروج من البناء",
|
||||
"exitConfirmMessage": "لديك تغييرات غير محفوظة ستفقد\nهل تريد الخروج بدون حفظ؟",
|
||||
"exitConfirmTitle": "الخروج من بناء التطبيق؟",
|
||||
"inputsDesc": "سيتفاعل المستخدمون مع هذه المدخلات ويعدلونها لإنشاء النتائج.",
|
||||
"inputsExample": "أمثلة: \"تحميل صورة\"، \"موجه نصي\"، \"خطوات\"",
|
||||
"noInputs": "لم تتم إضافة أي مدخلات بعد",
|
||||
"noOutputs": "لم تتم إضافة أي عقد إخراج بعد",
|
||||
"outputsDesc": "وصل عقدة إخراج واحدة على الأقل حتى يتمكن المستخدمون من رؤية النتائج بعد التشغيل.",
|
||||
"outputsExample": "أمثلة: \"حفظ صورة\" أو \"حفظ فيديو\"",
|
||||
"promptAddInputs": "انقر على معلمات العقدة لإضافتها هنا كمدخلات",
|
||||
"promptAddOutputs": "انقر على عقد الإخراج لإضافتها هنا. هذه ستكون النتائج المُولدة.",
|
||||
"title": "وضع بناء التطبيق"
|
||||
},
|
||||
"downloadAll": "تنزيل الكل",
|
||||
"dragAndDropImage": "اسحب وأسقط صورة",
|
||||
"graphMode": "وضع الرسم البياني",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "إظهار الروابط"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "ستحتاج إلى العثور عليها وتنزيلها يدويًا. ابحث عنها عبر الإنترنت (جرّب Civitai أو Hugging Face) أو تواصل مع مزود سير العمل الأصلي.",
|
||||
"customModelsWarning": "بعض هذه النماذج مخصصة ولا نتعرف عليها.",
|
||||
"description": "يتطلب سير العمل هذا نماذج لم تقم بتنزيلها بعد.",
|
||||
"doNotAskAgain": "عدم العرض مرة أخرى",
|
||||
"missingModels": "نماذج مفقودة",
|
||||
"missingModelsMessage": "عند تحميل الرسم البياني، لم يتم العثور على النماذج التالية",
|
||||
"downloadAll": "تنزيل الكل",
|
||||
"downloadAvailable": "التنزيل متاح",
|
||||
"footerDescription": "قم بتنزيل هذه النماذج وضعها في المجلد الصحيح.\nالعُقد التي تفتقد إلى النماذج مميزة باللون الأحمر على اللوحة.",
|
||||
"gotIt": "حسنًا، فهمت",
|
||||
"reEnableInSettings": "إعادة التفعيل في {link}",
|
||||
"reEnableInSettingsLink": "الإعدادات"
|
||||
"reEnableInSettingsLink": "الإعدادات",
|
||||
"title": "هذا سير العمل يفتقد إلى النماذج",
|
||||
"totalSize": "إجمالي حجم التنزيل:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "أضف المزيد من الرصيد في أي وقت",
|
||||
"benefits": {
|
||||
"benefit1": "رصيد شهري للعقد الشريكة - تجديد عند الحاجة",
|
||||
"benefit2": "حتى 30 دقيقة وقت تشغيل لكل مهمة"
|
||||
"benefit1FreeTier": "رصيد شهري أكثر، مع إمكانية الشحن في أي وقت",
|
||||
"benefit2": "حتى 30 دقيقة وقت تشغيل لكل مهمة",
|
||||
"benefit3": "استخدم نماذجك الخاصة (Creator & Pro)"
|
||||
},
|
||||
"beta": "نسخة تجريبية",
|
||||
"billedMonthly": "يتم الفوترة شهريًا",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "اختر الخطة الأنسب لك",
|
||||
"descriptionWorkspace": "اختر أفضل خطة لمساحة العمل الخاصة بك",
|
||||
"expiresDate": "ينتهي في {date}",
|
||||
"freeTier": {
|
||||
"description": "تشمل خطتك المجانية {credits} رصيد شهري لتجربة Comfy Cloud.",
|
||||
"descriptionGeneric": "تشمل خطتك المجانية رصيدًا شهريًا لتجربة Comfy Cloud.",
|
||||
"nextRefresh": "سيتم تجديد رصيدك في {date}.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "اشترك لفتح الشحن والمزيد",
|
||||
"title": "لقد نفد رصيدك المجاني"
|
||||
},
|
||||
"subscribeCta": "اشترك للمزيد",
|
||||
"title": "أنت على الخطة المجانية",
|
||||
"topUpBlocked": {
|
||||
"title": "افتح الشحن والمزيد"
|
||||
},
|
||||
"upgradeCta": "عرض الخطط"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (ذاكرة 96GB VRAM)",
|
||||
"haveQuestions": "هل لديك أسئلة أو ترغب في معرفة المزيد عن المؤسسات؟",
|
||||
"invoiceHistory": "سجل الفواتير",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 دقيقة",
|
||||
"founder": "30 دقيقة",
|
||||
"free": "٣٠ دقيقة",
|
||||
"pro": "ساعة واحدة",
|
||||
"standard": "30 دقيقة"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "إصدار المؤسس"
|
||||
},
|
||||
"free": {
|
||||
"name": "مجاني"
|
||||
},
|
||||
"pro": {
|
||||
"name": "احترافي"
|
||||
},
|
||||
|
||||
@@ -3007,6 +3007,20 @@
|
||||
"switchToSelectButton": "Switch to Select",
|
||||
"outputs": "Outputs",
|
||||
"resultsLabel": "Results generated from the selected output node(s) will be shown here after running this app"
|
||||
},
|
||||
"builder": {
|
||||
"title": "App builder mode",
|
||||
"exit": "Exit builder",
|
||||
"exitConfirmTitle": "Exit app builder?",
|
||||
"exitConfirmMessage": "You have unsaved changes that will be lost\nExit without saving?",
|
||||
"promptAddInputs": "Click on node parameters to add them here as inputs",
|
||||
"noInputs": "No inputs added yet",
|
||||
"inputsDesc": "Users will interact and adjust these to generate their outputs.",
|
||||
"inputsExample": "Examples: “Load image”, “Text prompt”, “Steps”",
|
||||
"promptAddOutputs": "Click on output nodes to add them here. These will be the generated results.",
|
||||
"noOutputs": "No output nodes added yet",
|
||||
"outputsDesc": "Connect at least one output node so users can see results after running.",
|
||||
"outputsExample": "Examples: “Save Image” or “Save Video”"
|
||||
}
|
||||
},
|
||||
"missingNodes": {
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "y",
|
||||
"backToLogin": "Volver al inicio de sesión",
|
||||
"backToSocialLogin": "Regístrate con Google o Github en su lugar",
|
||||
"confirmPasswordLabel": "Confirmar contraseña",
|
||||
"confirmPasswordPlaceholder": "Ingresa la misma contraseña nuevamente",
|
||||
"didntReceiveEmail": "¿No recibiste el correo? Contáctanos en",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "Inicio de sesión fallido",
|
||||
"forgotPassword": "¿Olvidaste tu contraseña?",
|
||||
"forgotPasswordError": "No se pudo enviar el correo electrónico para restablecer la contraseña",
|
||||
"freeTierBadge": "Elegible para el plan gratuito",
|
||||
"freeTierDescription": "Regístrate con Google para obtener {credits} créditos gratis cada mes. No se necesita tarjeta.",
|
||||
"freeTierDescriptionGeneric": "Regístrate con Google para obtener créditos gratis cada mes. No se necesita tarjeta.",
|
||||
"insecureContextWarning": "Esta conexión no es segura (HTTP): tus credenciales pueden ser interceptadas por atacantes si continúas con el inicio de sesión.",
|
||||
"loginButton": "Iniciar sesión",
|
||||
"loginWithGithub": "Iniciar sesión con Github",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "Enviar enlace de restablecimiento",
|
||||
"signInOrSignUp": "Iniciar sesión / Registrarse",
|
||||
"signUp": "Regístrate",
|
||||
"signUpFreeTierPromo": "¿Nuevo aquí? {signUp} con Google para obtener {credits} créditos gratis cada mes.",
|
||||
"success": "Inicio de sesión exitoso",
|
||||
"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",
|
||||
"useEmailInstead": "Usar correo electrónico en su lugar",
|
||||
"userAvatar": "Avatar de usuario"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "¿Ya tienes una cuenta?",
|
||||
"emailLabel": "Correo electrónico",
|
||||
"emailNotEligibleForFreeTier": "El registro por correo electrónico no es elegible para el plan gratuito.",
|
||||
"emailPlaceholder": "Ingresa tu correo electrónico",
|
||||
"passwordLabel": "Contraseña",
|
||||
"passwordPlaceholder": "Ingresa una nueva contraseña",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "Cambiar a Seleccionar"
|
||||
},
|
||||
"beta": "Modo App Beta - Enviar comentarios",
|
||||
"builder": {
|
||||
"exit": "Salir del constructor",
|
||||
"exitConfirmMessage": "Tienes cambios sin guardar que se perderán\n¿Salir sin guardar?",
|
||||
"exitConfirmTitle": "¿Salir del constructor de aplicaciones?",
|
||||
"inputsDesc": "Los usuarios interactuarán y ajustarán estos para generar sus resultados.",
|
||||
"inputsExample": "Ejemplos: “Cargar imagen”, “Prompt de texto”, “Pasos”",
|
||||
"noInputs": "Aún no se han agregado entradas",
|
||||
"noOutputs": "Aún no se han agregado nodos de salida",
|
||||
"outputsDesc": "Conecta al menos un nodo de salida para que los usuarios vean los resultados después de ejecutar.",
|
||||
"outputsExample": "Ejemplos: “Guardar imagen” o “Guardar video”",
|
||||
"promptAddInputs": "Haz clic en los parámetros del nodo para agregarlos aquí como entradas",
|
||||
"promptAddOutputs": "Haz clic en los nodos de salida para agregarlos aquí. Estos serán los resultados generados.",
|
||||
"title": "Modo constructor de aplicaciones"
|
||||
},
|
||||
"downloadAll": "Descargar todo",
|
||||
"dragAndDropImage": "Arrastra y suelta una imagen",
|
||||
"graphMode": "Modo gráfico",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "Mostrar enlaces"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "Tendrás que encontrarlos y descargarlos manualmente. Búscalos en línea (prueba Civitai o Hugging Face) o contacta al proveedor original del flujo de trabajo.",
|
||||
"customModelsWarning": "Algunos de estos son modelos personalizados que no reconocemos.",
|
||||
"description": "Este flujo de trabajo requiere modelos que aún no has descargado.",
|
||||
"doNotAskAgain": "No mostrar esto de nuevo",
|
||||
"missingModels": "Modelos faltantes",
|
||||
"missingModelsMessage": "Al cargar el gráfico, no se encontraron los siguientes modelos",
|
||||
"downloadAll": "Descargar todo",
|
||||
"downloadAvailable": "Descargar disponibles",
|
||||
"footerDescription": "Descarga y coloca estos modelos en la carpeta correcta.\nLos nodos con modelos faltantes están resaltados en rojo en el lienzo.",
|
||||
"gotIt": "Entendido",
|
||||
"reEnableInSettings": "Vuelve a habilitar en {link}",
|
||||
"reEnableInSettingsLink": "Configuración"
|
||||
"reEnableInSettingsLink": "Configuración",
|
||||
"title": "Faltan modelos en este flujo de trabajo",
|
||||
"totalSize": "Tamaño total de descarga:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "Agrega más créditos cuando quieras",
|
||||
"benefits": {
|
||||
"benefit1": "Créditos mensuales para Nodos de Socio — recarga cuando sea necesario",
|
||||
"benefit2": "Hasta 30 min de tiempo de ejecución por trabajo"
|
||||
"benefit1FreeTier": "Más créditos mensuales, recarga en cualquier momento",
|
||||
"benefit2": "Hasta 30 min de tiempo de ejecución por trabajo",
|
||||
"benefit3": "Usa tus propios modelos (Creator & Pro)"
|
||||
},
|
||||
"beta": "BETA",
|
||||
"billedMonthly": "Facturado mensualmente",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "Elige el mejor plan para ti",
|
||||
"descriptionWorkspace": "Elige el mejor plan para tu espacio de trabajo",
|
||||
"expiresDate": "Caduca el {date}",
|
||||
"freeTier": {
|
||||
"description": "Tu plan gratuito incluye {credits} créditos cada mes para probar Comfy Cloud.",
|
||||
"descriptionGeneric": "Tu plan gratuito incluye una asignación mensual de créditos para probar Comfy Cloud.",
|
||||
"nextRefresh": "Tus créditos se renovarán el {date}.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "Suscríbete para desbloquear recargas y más",
|
||||
"title": "Te has quedado sin créditos gratuitos"
|
||||
},
|
||||
"subscribeCta": "Suscríbete para más",
|
||||
"title": "Estás en el plan gratuito",
|
||||
"topUpBlocked": {
|
||||
"title": "Desbloquea recargas y más"
|
||||
},
|
||||
"upgradeCta": "Ver planes"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||
"haveQuestions": "¿Tienes preguntas o buscas soluciones empresariales?",
|
||||
"invoiceHistory": "Historial de facturas",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 min",
|
||||
"founder": "30 min",
|
||||
"free": "30 min",
|
||||
"pro": "1 h",
|
||||
"standard": "30 min"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "Edición Fundador"
|
||||
},
|
||||
"free": {
|
||||
"name": "Gratis"
|
||||
},
|
||||
"pro": {
|
||||
"name": "Pro"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "و",
|
||||
"backToLogin": "بازگشت به ورود",
|
||||
"backToSocialLogin": "ثبتنام با Google یا Github",
|
||||
"confirmPasswordLabel": "تأیید رمز عبور",
|
||||
"confirmPasswordPlaceholder": "رمز عبور را مجدداً وارد کنید",
|
||||
"didntReceiveEmail": "ایمیلی دریافت نکردید؟ با ما تماس بگیرید:",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "ورود ناموفق بود",
|
||||
"forgotPassword": "رمز عبور را فراموش کردهاید؟",
|
||||
"forgotPasswordError": "ارسال ایمیل بازیابی رمز عبور ناموفق بود",
|
||||
"freeTierBadge": "واجد شرایط طرح رایگان",
|
||||
"freeTierDescription": "با ثبتنام از طریق Google هر ماه {credits} اعتبار رایگان دریافت کنید. نیاز به کارت نیست.",
|
||||
"freeTierDescriptionGeneric": "با ثبتنام از طریق Google هر ماه اعتبار رایگان دریافت کنید. نیاز به کارت نیست.",
|
||||
"insecureContextWarning": "این اتصال ناامن است (HTTP) - در صورت ادامه ورود، اطلاعات شما ممکن است توسط مهاجمان رهگیری شود.",
|
||||
"loginButton": "ورود",
|
||||
"loginWithGithub": "ورود با Github",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "ارسال لینک بازیابی",
|
||||
"signInOrSignUp": "ورود / ثبتنام",
|
||||
"signUp": "ثبتنام",
|
||||
"signUpFreeTierPromo": "جدید هستید؟ با {signUp} از طریق Google هر ماه {credits} اعتبار رایگان دریافت کنید.",
|
||||
"success": "ورود موفقیتآمیز بود",
|
||||
"termsLink": "شرایط استفاده",
|
||||
"termsText": "با کلیک بر روی «بعدی» یا «ثبتنام»، شما با",
|
||||
"title": "ورود به حساب کاربری",
|
||||
"useApiKey": "کلید Comfy API",
|
||||
"useEmailInstead": "استفاده از ایمیل به جای آن",
|
||||
"userAvatar": "آواتار کاربر"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "قبلاً حساب کاربری دارید؟",
|
||||
"emailLabel": "ایمیل",
|
||||
"emailNotEligibleForFreeTier": "ثبتنام با ایمیل شامل طرح رایگان نمیشود.",
|
||||
"emailPlaceholder": "ایمیل خود را وارد کنید",
|
||||
"passwordLabel": "رمز عبور",
|
||||
"passwordPlaceholder": "رمز عبور جدید را وارد کنید",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "رفتن به انتخاب"
|
||||
},
|
||||
"beta": "حالت برنامه بتا - ارسال بازخورد",
|
||||
"builder": {
|
||||
"exit": "خروج از حالت ساخت",
|
||||
"exitConfirmMessage": "تغییرات ذخیرهنشده شما از بین خواهد رفت\nخروج بدون ذخیره؟",
|
||||
"exitConfirmTitle": "خروج از حالت ساخت اپلیکیشن؟",
|
||||
"inputsDesc": "کاربران میتوانند این موارد را تنظیم کنند تا خروجی مورد نظر خود را تولید نمایند.",
|
||||
"inputsExample": "مثالها: «بارگذاری تصویر»، «متن راهنما»، «تعداد مراحل»",
|
||||
"noInputs": "هنوز ورودیای اضافه نشده است",
|
||||
"noOutputs": "هنوز گره خروجی اضافه نشده است",
|
||||
"outputsDesc": "حداقل یک گره خروجی متصل کنید تا کاربران پس از اجرا نتایج را مشاهده کنند.",
|
||||
"outputsExample": "مثالها: «ذخیره تصویر» یا «ذخیره ویدیو»",
|
||||
"promptAddInputs": "برای افزودن پارامترها به عنوان ورودی، روی پارامترهای گره کلیک کنید",
|
||||
"promptAddOutputs": "برای افزودن خروجی، روی گرههای خروجی کلیک کنید. اینها نتایج تولیدشده خواهند بود.",
|
||||
"title": "حالت ساخت اپلیکیشن"
|
||||
},
|
||||
"downloadAll": "دانلود همه",
|
||||
"dragAndDropImage": "تصویر را بکشید و رها کنید",
|
||||
"graphMode": "حالت گراف",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "نمایش پیوندها"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "باید این مدلها را به صورت دستی پیدا و دانلود کنید. آنها را به صورت آنلاین جستجو کنید (Civitai یا Hugging Face را امتحان کنید) یا با ارائهدهنده اصلی گردشکار تماس بگیرید.",
|
||||
"customModelsWarning": "برخی از این مدلها سفارشی هستند و ما آنها را نمیشناسیم.",
|
||||
"description": "این گردشکار به مدلهایی نیاز دارد که هنوز آنها را دانلود نکردهاید.",
|
||||
"doNotAskAgain": "دیگر نمایش داده نشود",
|
||||
"missingModels": "مدلهای مفقود",
|
||||
"missingModelsMessage": "هنگام بارگذاری گراف، مدلهای زیر یافت نشدند",
|
||||
"downloadAll": "دانلود همه",
|
||||
"downloadAvailable": "دانلود موجود",
|
||||
"footerDescription": "این مدلها را دانلود کرده و در پوشه صحیح قرار دهید.\nگرههایی که مدل آنها موجود نیست، روی بوم به رنگ قرمز نمایش داده میشوند.",
|
||||
"gotIt": "متوجه شدم",
|
||||
"reEnableInSettings": "فعالسازی مجدد در {link}",
|
||||
"reEnableInSettingsLink": "تنظیمات"
|
||||
"reEnableInSettingsLink": "تنظیمات",
|
||||
"title": "این گردشکار فاقد مدلها است",
|
||||
"totalSize": "حجم کل دانلود:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2696,7 +2724,9 @@
|
||||
"addCreditsLabel": "هر زمان اعتبار بیشتری اضافه کنید",
|
||||
"benefits": {
|
||||
"benefit1": "۱۰ دلار اعتبار ماهانه برای Partner Nodes — در صورت نیاز شارژ کنید",
|
||||
"benefit2": "تا ۳۰ دقیقه زمان اجرا برای هر کار"
|
||||
"benefit1FreeTier": "اعتبار ماهانه بیشتر، شارژ مجدد در هر زمان",
|
||||
"benefit2": "تا ۳۰ دقیقه زمان اجرا برای هر کار",
|
||||
"benefit3": "امکان استفاده از مدلهای شخصی (Creator و Pro)"
|
||||
},
|
||||
"beta": "بتا",
|
||||
"billedMonthly": "صورتحساب ماهانه",
|
||||
@@ -2734,6 +2764,21 @@
|
||||
"description": "بهترین طرح را برای خود انتخاب کنید",
|
||||
"descriptionWorkspace": "بهترین طرح را برای فضای کاری خود انتخاب کنید",
|
||||
"expiresDate": "انقضا در {date}",
|
||||
"freeTier": {
|
||||
"description": "طرح رایگان شما شامل {credits} اعتبار در هر ماه برای استفاده از Comfy Cloud است.",
|
||||
"descriptionGeneric": "طرح رایگان شما شامل اعتبار ماهانه برای استفاده از Comfy Cloud است.",
|
||||
"nextRefresh": "اعتبار شما در تاریخ {date} بهروزرسانی میشود.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "با اشتراک، امکان شارژ مجدد و امکانات بیشتر را فعال کنید",
|
||||
"title": "اعتبار رایگان شما تمام شده است"
|
||||
},
|
||||
"subscribeCta": "اشتراک برای اعتبار بیشتر",
|
||||
"title": "شما در طرح رایگان هستید",
|
||||
"topUpBlocked": {
|
||||
"title": "امکان شارژ مجدد و امکانات بیشتر را فعال کنید"
|
||||
},
|
||||
"upgradeCta": "مشاهده طرحها"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (۹۶ گیگابایت VRAM)",
|
||||
"haveQuestions": "سوالی دارید یا به دنبال راهکار سازمانی هستید؟",
|
||||
"invoiceHistory": "تاریخچه فاکتورها",
|
||||
@@ -2744,6 +2789,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "۳۰ دقیقه",
|
||||
"founder": "۳۰ دقیقه",
|
||||
"free": "۳۰ دقیقه",
|
||||
"pro": "۱ ساعت",
|
||||
"standard": "۳۰ دقیقه"
|
||||
},
|
||||
@@ -2816,6 +2862,9 @@
|
||||
"founder": {
|
||||
"name": "نسخه بنیانگذاران"
|
||||
},
|
||||
"free": {
|
||||
"name": "رایگان"
|
||||
},
|
||||
"pro": {
|
||||
"name": "حرفهای"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "et",
|
||||
"backToLogin": "Retour à la connexion",
|
||||
"backToSocialLogin": "Inscrivez-vous avec Google ou Github à la place",
|
||||
"confirmPasswordLabel": "Confirmer le mot de passe",
|
||||
"confirmPasswordPlaceholder": "Entrez à nouveau le même mot de passe",
|
||||
"didntReceiveEmail": "Vous n'avez pas reçu d'e-mail ? Contactez-nous à",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "Échec de la connexion",
|
||||
"forgotPassword": "Mot de passe oublié?",
|
||||
"forgotPasswordError": "Échec de l'envoi de l'e-mail de réinitialisation du mot de passe",
|
||||
"freeTierBadge": "Éligible à l’offre gratuite",
|
||||
"freeTierDescription": "Inscrivez-vous avec Google pour obtenir {credits} crédits gratuits chaque mois. Aucune carte requise.",
|
||||
"freeTierDescriptionGeneric": "Inscrivez-vous avec Google pour obtenir des crédits gratuits chaque mois. Aucune carte requise.",
|
||||
"insecureContextWarning": "Cette connexion n'est pas sécurisée (HTTP) - vos identifiants pourraient être interceptés par des attaquants si vous continuez à vous connecter.",
|
||||
"loginButton": "Se connecter",
|
||||
"loginWithGithub": "Se connecter avec Github",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "Envoyer le lien de réinitialisation",
|
||||
"signInOrSignUp": "Se connecter / S’inscrire",
|
||||
"signUp": "S'inscrire",
|
||||
"signUpFreeTierPromo": "Nouveau ici ? {signUp} avec Google pour obtenir {credits} crédits gratuits chaque mois.",
|
||||
"success": "Connexion réussie",
|
||||
"termsLink": "Conditions d'utilisation",
|
||||
"termsText": "En cliquant sur \"Suivant\" ou \"S'inscrire\", vous acceptez nos",
|
||||
"title": "Connectez-vous à votre compte",
|
||||
"useApiKey": "Clé API Comfy",
|
||||
"useEmailInstead": "Utiliser l’e-mail à la place",
|
||||
"userAvatar": "Avatar utilisateur"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "Vous avez déjà un compte?",
|
||||
"emailLabel": "Email",
|
||||
"emailNotEligibleForFreeTier": "L’inscription par e-mail n’est pas éligible à l’offre gratuite.",
|
||||
"emailPlaceholder": "Entrez votre email",
|
||||
"passwordLabel": "Mot de passe",
|
||||
"passwordPlaceholder": "Entrez un nouveau mot de passe",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "Passer à Sélectionner"
|
||||
},
|
||||
"beta": "Mode App Bêta - Donnez votre avis",
|
||||
"builder": {
|
||||
"exit": "Quitter le mode créateur",
|
||||
"exitConfirmMessage": "Vous avez des modifications non enregistrées qui seront perdues\nQuitter sans enregistrer ?",
|
||||
"exitConfirmTitle": "Quitter le créateur d’application ?",
|
||||
"inputsDesc": "Les utilisateurs interagiront avec ces paramètres pour générer leurs résultats.",
|
||||
"inputsExample": "Exemples : « Charger une image », « Prompt texte », « Étapes »",
|
||||
"noInputs": "Aucune entrée ajoutée pour le moment",
|
||||
"noOutputs": "Aucun nœud de sortie ajouté pour le moment",
|
||||
"outputsDesc": "Connectez au moins un nœud de sortie pour que les utilisateurs voient les résultats après l’exécution.",
|
||||
"outputsExample": "Exemples : « Enregistrer l’image » ou « Enregistrer la vidéo »",
|
||||
"promptAddInputs": "Cliquez sur les paramètres du nœud pour les ajouter ici comme entrées",
|
||||
"promptAddOutputs": "Cliquez sur les nœuds de sortie pour les ajouter ici. Ce seront les résultats générés.",
|
||||
"title": "Mode créateur d’application"
|
||||
},
|
||||
"downloadAll": "Tout télécharger",
|
||||
"dragAndDropImage": "Glissez-déposez une image",
|
||||
"graphMode": "Mode graphique",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "Afficher les liens"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "Vous devrez les trouver et les télécharger manuellement. Cherchez-les en ligne (essayez Civitai ou Hugging Face) ou contactez le créateur du workflow d'origine.",
|
||||
"customModelsWarning": "Certains de ces modèles sont personnalisés et nous ne les reconnaissons pas.",
|
||||
"description": "Ce workflow nécessite des modèles que vous n'avez pas encore téléchargés.",
|
||||
"doNotAskAgain": "Ne plus afficher ce message",
|
||||
"missingModels": "Modèles manquants",
|
||||
"missingModelsMessage": "Lors du chargement du graphique, les modèles suivants n'ont pas été trouvés",
|
||||
"downloadAll": "Tout télécharger",
|
||||
"downloadAvailable": "Téléchargement disponible",
|
||||
"footerDescription": "Téléchargez et placez ces modèles dans le dossier approprié.\nLes nœuds avec des modèles manquants sont surlignés en rouge sur le canevas.",
|
||||
"gotIt": "Ok, compris",
|
||||
"reEnableInSettings": "Réactiver dans {link}",
|
||||
"reEnableInSettingsLink": "Paramètres"
|
||||
"reEnableInSettingsLink": "Paramètres",
|
||||
"title": "Ce workflow est incomplet : modèles manquants",
|
||||
"totalSize": "Taille totale du téléchargement :"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "Ajoutez des crédits à tout moment",
|
||||
"benefits": {
|
||||
"benefit1": "Crédits mensuels pour les Nœuds Partenaires — rechargez si nécessaire",
|
||||
"benefit2": "Jusqu'à 30 min d'exécution par tâche"
|
||||
"benefit1FreeTier": "Plus de crédits mensuels, recharge à tout moment",
|
||||
"benefit2": "Jusqu'à 30 min d'exécution par tâche",
|
||||
"benefit3": "Utilisez vos propres modèles (Creator & Pro)"
|
||||
},
|
||||
"beta": "BÊTA",
|
||||
"billedMonthly": "Facturé mensuellement",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "Choisissez le forfait qui vous convient",
|
||||
"descriptionWorkspace": "Choisissez la meilleure offre pour votre espace de travail",
|
||||
"expiresDate": "Expire le {date}",
|
||||
"freeTier": {
|
||||
"description": "Votre plan gratuit inclut {credits} crédits chaque mois pour essayer Comfy Cloud.",
|
||||
"descriptionGeneric": "Votre plan gratuit inclut une allocation mensuelle de crédits pour essayer Comfy Cloud.",
|
||||
"nextRefresh": "Vos crédits seront renouvelés le {date}.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "Abonnez-vous pour débloquer les recharges et plus encore",
|
||||
"title": "Vous n’avez plus de crédits gratuits"
|
||||
},
|
||||
"subscribeCta": "Abonnez-vous pour plus",
|
||||
"title": "Vous êtes sur le plan Gratuit",
|
||||
"topUpBlocked": {
|
||||
"title": "Débloquez les recharges et plus encore"
|
||||
},
|
||||
"upgradeCta": "Voir les offres"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||
"haveQuestions": "Des questions ou besoin d'une offre entreprise ?",
|
||||
"invoiceHistory": "Historique des factures",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 min",
|
||||
"founder": "30 min",
|
||||
"free": "30 min",
|
||||
"pro": "1 h",
|
||||
"standard": "30 min"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "Édition Fondateur"
|
||||
},
|
||||
"free": {
|
||||
"name": "Gratuit"
|
||||
},
|
||||
"pro": {
|
||||
"name": "Pro"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "および",
|
||||
"backToLogin": "ログインに戻る",
|
||||
"backToSocialLogin": "GoogleまたはGithubでサインアップする",
|
||||
"confirmPasswordLabel": "パスワードの確認",
|
||||
"confirmPasswordPlaceholder": "もう一度同じパスワードを入力してください",
|
||||
"didntReceiveEmail": "メールが届きませんか?こちらまでご連絡ください:",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "ログイン失敗",
|
||||
"forgotPassword": "パスワードを忘れましたか?",
|
||||
"forgotPasswordError": "パスワードリセット用メールの送信に失敗しました",
|
||||
"freeTierBadge": "無料プラン対象",
|
||||
"freeTierDescription": "Googleでサインアップすると、毎月{credits}の無料クレジットがもらえます。クレジットカード不要。",
|
||||
"freeTierDescriptionGeneric": "Googleでサインアップすると、毎月無料クレジットがもらえます。クレジットカード不要。",
|
||||
"insecureContextWarning": "この接続は安全ではありません(HTTP)- このままログインを続けると、認証情報が攻撃者に傍受される可能性があります。",
|
||||
"loginButton": "ログイン",
|
||||
"loginWithGithub": "Githubでログイン",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "リセットリンクを送信",
|
||||
"signInOrSignUp": "サインイン / サインアップ",
|
||||
"signUp": "サインアップ",
|
||||
"signUpFreeTierPromo": "初めての方はこちら。Googleで{signUp}して、毎月{credits}の無料クレジットを獲得しましょう。",
|
||||
"success": "ログイン成功",
|
||||
"termsLink": "利用規約",
|
||||
"termsText": "「次へ」または「サインアップ」をクリックすると、私たちの",
|
||||
"title": "アカウントにログインする",
|
||||
"useApiKey": "Comfy APIキー",
|
||||
"useEmailInstead": "メールアドレスを使用する",
|
||||
"userAvatar": "ユーザーアバター"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "すでにアカウントをお持ちですか?",
|
||||
"emailLabel": "メール",
|
||||
"emailNotEligibleForFreeTier": "メールでのサインアップは無料プランの対象外です。",
|
||||
"emailPlaceholder": "メールアドレスを入力してください",
|
||||
"passwordLabel": "パスワード",
|
||||
"passwordPlaceholder": "新しいパスワードを入力してください",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "選択に切り替え"
|
||||
},
|
||||
"beta": "アプリモード ベータ版 - フィードバックを送る",
|
||||
"builder": {
|
||||
"exit": "ビルダーを終了",
|
||||
"exitConfirmMessage": "保存されていない変更は失われます。\n保存せずに終了しますか?",
|
||||
"exitConfirmTitle": "アプリビルダーを終了しますか?",
|
||||
"inputsDesc": "ユーザーはこれらを操作して出力を生成します。",
|
||||
"inputsExample": "例:「画像を読み込む」「テキストプロンプト」「ステップ数」",
|
||||
"noInputs": "まだ入力が追加されていません",
|
||||
"noOutputs": "まだ出力ノードが追加されていません",
|
||||
"outputsDesc": "少なくとも1つの出力ノードを接続すると、ユーザーが実行後に結果を確認できます。",
|
||||
"outputsExample": "例:「画像を保存」「動画を保存」",
|
||||
"promptAddInputs": "ノードのパラメータをクリックして、ここに入力として追加してください",
|
||||
"promptAddOutputs": "出力ノードをクリックしてここに追加してください。これが生成される結果となります。",
|
||||
"title": "アプリビルダーモード"
|
||||
},
|
||||
"downloadAll": "すべてダウンロード",
|
||||
"dragAndDropImage": "画像をドラッグ&ドロップ",
|
||||
"graphMode": "グラフモード",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "リンクを表示"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "手動で探してダウンロードする必要があります。オンラインで検索するか(CivitaiやHugging Faceを試してください)、元のワークフロープロバイダーに連絡してください。",
|
||||
"customModelsWarning": "これらの中には、認識できないカスタムモデルが含まれています。",
|
||||
"description": "このワークフローには、まだダウンロードしていないモデルが必要です。",
|
||||
"doNotAskAgain": "再度表示しない",
|
||||
"missingModels": "モデルが見つかりません",
|
||||
"missingModelsMessage": "グラフを読み込む際に、次のモデルが見つかりませんでした",
|
||||
"downloadAll": "すべてダウンロード",
|
||||
"downloadAvailable": "ダウンロード可能",
|
||||
"footerDescription": "これらのモデルをダウンロードし、正しいフォルダに配置してください。\n不足しているモデルがあるノードはキャンバス上で赤く表示されます。",
|
||||
"gotIt": "了解しました",
|
||||
"reEnableInSettings": "{link}で再有効化",
|
||||
"reEnableInSettingsLink": "設定"
|
||||
"reEnableInSettingsLink": "設定",
|
||||
"title": "このワークフローにはモデルが不足しています",
|
||||
"totalSize": "合計ダウンロードサイズ:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "いつでもクレジット追加可能",
|
||||
"benefits": {
|
||||
"benefit1": "パートナーノード用月間クレジット — 必要に応じて追加購入可能",
|
||||
"benefit2": "ジョブあたり最大30分の実行時間"
|
||||
"benefit1FreeTier": "毎月のクレジット増加、いつでもチャージ可能",
|
||||
"benefit2": "ジョブあたり最大30分の実行時間",
|
||||
"benefit3": "独自のモデルを利用可能(Creator & Pro)"
|
||||
},
|
||||
"beta": "ベータ版",
|
||||
"billedMonthly": "毎月請求",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "あなたに最適なプランを選択してください",
|
||||
"descriptionWorkspace": "ワークスペースに最適なプランを選択してください",
|
||||
"expiresDate": "{date} に期限切れ",
|
||||
"freeTier": {
|
||||
"description": "無料プランには、Comfy Cloudをお試しいただける毎月{credits}クレジットが含まれています。",
|
||||
"descriptionGeneric": "無料プランには、Comfy Cloudをお試しいただける毎月のクレジット枠が含まれています。",
|
||||
"nextRefresh": "クレジットは{date}にリフレッシュされます。",
|
||||
"outOfCredits": {
|
||||
"subtitle": "サブスクリプションでチャージや追加機能を利用しましょう",
|
||||
"title": "無料クレジットがなくなりました"
|
||||
},
|
||||
"subscribeCta": "さらに詳しく",
|
||||
"title": "無料プランをご利用中です",
|
||||
"topUpBlocked": {
|
||||
"title": "チャージや追加機能をアンロック"
|
||||
},
|
||||
"upgradeCta": "プランを見る"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro(96GB VRAM)",
|
||||
"haveQuestions": "ご質問やエンタープライズについてのお問い合わせはこちら",
|
||||
"invoiceHistory": "請求履歴",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30分",
|
||||
"founder": "30分",
|
||||
"free": "30分",
|
||||
"pro": "1時間",
|
||||
"standard": "30分"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "ファウンダーエディション"
|
||||
},
|
||||
"free": {
|
||||
"name": "無料"
|
||||
},
|
||||
"pro": {
|
||||
"name": "プロ"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "및",
|
||||
"backToLogin": "로그인으로 돌아가기",
|
||||
"backToSocialLogin": "Google 또는 Github로 가입하기",
|
||||
"confirmPasswordLabel": "비밀번호 확인",
|
||||
"confirmPasswordPlaceholder": "동일한 비밀번호를 다시 입력하세요",
|
||||
"didntReceiveEmail": "이메일을 받지 못하셨나요? 다음으로 문의하세요:",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "로그인 실패",
|
||||
"forgotPassword": "비밀번호를 잊으셨나요?",
|
||||
"forgotPasswordError": "비밀번호 재설정 이메일 전송에 실패했습니다",
|
||||
"freeTierBadge": "무료 등급 가능",
|
||||
"freeTierDescription": "Google로 가입하면 매월 {credits} 무료 크레딧을 받을 수 있습니다. 카드 필요 없음.",
|
||||
"freeTierDescriptionGeneric": "Google로 가입하면 매월 무료 크레딧을 받을 수 있습니다. 카드 필요 없음.",
|
||||
"insecureContextWarning": "이 연결은 안전하지 않습니다(HTTP) - 로그인을 계속하면 자격 증명이 공격자에게 가로채질 수 있습니다.",
|
||||
"loginButton": "로그인",
|
||||
"loginWithGithub": "Github로 로그인",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "재설정 링크 보내기",
|
||||
"signInOrSignUp": "로그인 / 회원가입",
|
||||
"signUp": "가입하기",
|
||||
"signUpFreeTierPromo": "처음이신가요? Google로 {signUp} 하여 매월 {credits} 무료 크레딧을 받으세요.",
|
||||
"success": "로그인 성공",
|
||||
"termsLink": "이용 약관",
|
||||
"termsText": "\"다음\" 또는 \"가입하기\"를 클릭하면 우리의",
|
||||
"title": "계정에 로그인",
|
||||
"useApiKey": "Comfy API 키",
|
||||
"useEmailInstead": "이메일로 계속하기",
|
||||
"userAvatar": "사용자 아바타"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "이미 계정이 있으신가요?",
|
||||
"emailLabel": "이메일",
|
||||
"emailNotEligibleForFreeTier": "이메일 가입은 무료 등급에 해당되지 않습니다.",
|
||||
"emailPlaceholder": "이메일을 입력하세요",
|
||||
"passwordLabel": "비밀번호",
|
||||
"passwordPlaceholder": "새 비밀번호를 입력하세요",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "선택으로 전환"
|
||||
},
|
||||
"beta": "앱 모드 베타 - 피드백 보내기",
|
||||
"builder": {
|
||||
"exit": "빌더 종료",
|
||||
"exitConfirmMessage": "저장되지 않은 변경사항이 사라집니다\n저장하지 않고 종료하시겠습니까?",
|
||||
"exitConfirmTitle": "앱 빌더를 종료할까요?",
|
||||
"inputsDesc": "사용자가 이 항목을 조정하여 결과를 생성할 수 있습니다.",
|
||||
"inputsExample": "예시: “이미지 불러오기”, “텍스트 프롬프트”, “스텝 수”",
|
||||
"noInputs": "아직 입력값이 추가되지 않았습니다",
|
||||
"noOutputs": "아직 출력 노드가 추가되지 않았습니다",
|
||||
"outputsDesc": "최소 한 개 이상의 출력 노드를 연결해야 실행 후 결과를 볼 수 있습니다.",
|
||||
"outputsExample": "예시: “이미지 저장” 또는 “비디오 저장”",
|
||||
"promptAddInputs": "노드 파라미터를 클릭하여 입력값으로 추가하세요",
|
||||
"promptAddOutputs": "출력 노드를 클릭하여 여기에 추가하세요. 이들이 생성된 결과가 됩니다.",
|
||||
"title": "앱 빌더 모드"
|
||||
},
|
||||
"downloadAll": "모두 다운로드",
|
||||
"dragAndDropImage": "이미지를 드래그 앤 드롭하세요",
|
||||
"graphMode": "그래프 모드",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "링크 표시"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "직접 찾아서 수동으로 다운로드해야 합니다. 온라인에서 검색해보세요(예: Civitai 또는 Hugging Face) 또는 원래 워크플로우 제공자에게 문의하세요.",
|
||||
"customModelsWarning": "이 중 일부는 인식되지 않는 커스텀 모델입니다.",
|
||||
"description": "이 워크플로우에는 아직 다운로드하지 않은 모델이 필요합니다.",
|
||||
"doNotAskAgain": "다시 보지 않기",
|
||||
"missingModels": "모델이 없습니다",
|
||||
"missingModelsMessage": "그래프를 로드할 때 다음 모델을 찾을 수 없었습니다",
|
||||
"downloadAll": "모두 다운로드",
|
||||
"downloadAvailable": "다운로드 가능",
|
||||
"footerDescription": "이 모델들을 다운로드하여 올바른 폴더에 넣으세요.\n모델이 누락된 노드는 캔버스에서 빨간색으로 표시됩니다.",
|
||||
"gotIt": "확인",
|
||||
"reEnableInSettings": "{link}에서 다시 활성화",
|
||||
"reEnableInSettingsLink": "설정"
|
||||
"reEnableInSettingsLink": "설정",
|
||||
"title": "이 워크플로우에 모델이 누락되었습니다",
|
||||
"totalSize": "총 다운로드 크기:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "언제든지 크레딧 추가 가능",
|
||||
"benefits": {
|
||||
"benefit1": "파트너 노드 월간 크레딧 — 필요 시 충전",
|
||||
"benefit2": "작업당 최대 30분 실행 시간"
|
||||
"benefit1FreeTier": "더 많은 월간 크레딧, 언제든지 충전 가능",
|
||||
"benefit2": "작업당 최대 30분 실행 시간",
|
||||
"benefit3": "직접 모델 가져오기(Creator & Pro)"
|
||||
},
|
||||
"beta": "베타",
|
||||
"billedMonthly": "매월 결제",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "가장 적합한 플랜을 선택하세요",
|
||||
"descriptionWorkspace": "워크스페이스에 가장 적합한 플랜을 선택하세요",
|
||||
"expiresDate": "만료일 {date}",
|
||||
"freeTier": {
|
||||
"description": "무료 플랜에는 Comfy Cloud를 체험할 수 있도록 매월 {credits} 크레딧이 포함되어 있습니다.",
|
||||
"descriptionGeneric": "무료 플랜에는 Comfy Cloud를 체험할 수 있는 월간 크레딧이 포함되어 있습니다.",
|
||||
"nextRefresh": "크레딧은 {date}에 새로 고침됩니다.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "충전 및 추가 혜택을 위해 구독하세요",
|
||||
"title": "무료 크레딧이 모두 소진되었습니다"
|
||||
},
|
||||
"subscribeCta": "더 많은 혜택 구독하기",
|
||||
"title": "무료 플랜을 사용 중입니다",
|
||||
"topUpBlocked": {
|
||||
"title": "충전 및 추가 혜택 잠금 해제"
|
||||
},
|
||||
"upgradeCta": "플랜 보기"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||
"haveQuestions": "질문이 있거나 엔터프라이즈가 궁금하신가요?",
|
||||
"invoiceHistory": "청구서 기록",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30분",
|
||||
"founder": "30분",
|
||||
"free": "30분",
|
||||
"pro": "1시간",
|
||||
"standard": "30분"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "Founder's Edition"
|
||||
},
|
||||
"free": {
|
||||
"name": "무료"
|
||||
},
|
||||
"pro": {
|
||||
"name": "Pro"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "e",
|
||||
"backToLogin": "Voltar para login",
|
||||
"backToSocialLogin": "Cadastre-se com Google ou Github em vez disso",
|
||||
"confirmPasswordLabel": "Confirmar senha",
|
||||
"confirmPasswordPlaceholder": "Digite a mesma senha novamente",
|
||||
"didntReceiveEmail": "Não recebeu o e-mail? Entre em contato conosco em",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "Falha no login",
|
||||
"forgotPassword": "Esqueceu a senha?",
|
||||
"forgotPasswordError": "Falha ao enviar e-mail de redefinição de senha",
|
||||
"freeTierBadge": "Elegível para o Plano Gratuito",
|
||||
"freeTierDescription": "Cadastre-se com Google para ganhar {credits} créditos gratuitos todo mês. Não precisa de cartão.",
|
||||
"freeTierDescriptionGeneric": "Cadastre-se com Google para ganhar créditos gratuitos todo mês. Não precisa de cartão.",
|
||||
"insecureContextWarning": "Esta conexão é insegura (HTTP) - suas credenciais podem ser interceptadas por invasores se você continuar.",
|
||||
"loginButton": "Entrar",
|
||||
"loginWithGithub": "Entrar com Github",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "Enviar link de redefinição",
|
||||
"signInOrSignUp": "Entrar / Cadastrar-se",
|
||||
"signUp": "Cadastrar-se",
|
||||
"signUpFreeTierPromo": "Novo por aqui? {signUp} com Google para ganhar {credits} créditos gratuitos todo mês.",
|
||||
"success": "Login realizado com sucesso",
|
||||
"termsLink": "Termos de Uso",
|
||||
"termsText": "Ao clicar em \"Próximo\" ou \"Cadastrar-se\", você concorda com nossos",
|
||||
"title": "Faça login na sua conta",
|
||||
"useApiKey": "Chave de API Comfy",
|
||||
"useEmailInstead": "Usar e-mail em vez disso",
|
||||
"userAvatar": "Avatar do usuário"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "Já tem uma conta?",
|
||||
"emailLabel": "E-mail",
|
||||
"emailNotEligibleForFreeTier": "Cadastro por e-mail não é elegível para o Plano Gratuito.",
|
||||
"emailPlaceholder": "Digite seu e-mail",
|
||||
"passwordLabel": "Senha",
|
||||
"passwordPlaceholder": "Digite uma nova senha",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "Ir para Selecionar"
|
||||
},
|
||||
"beta": "Modo App Beta - Envie seu feedback",
|
||||
"builder": {
|
||||
"exit": "Sair do construtor",
|
||||
"exitConfirmMessage": "Você tem alterações não salvas que serão perdidas\nSair sem salvar?",
|
||||
"exitConfirmTitle": "Sair do construtor de app?",
|
||||
"inputsDesc": "Os usuários irão interagir e ajustar estes para gerar seus resultados.",
|
||||
"inputsExample": "Exemplos: “Carregar imagem”, “Prompt de texto”, “Passos”",
|
||||
"noInputs": "Nenhuma entrada adicionada ainda",
|
||||
"noOutputs": "Nenhum nó de saída adicionado ainda",
|
||||
"outputsDesc": "Conecte pelo menos um nó de saída para que os usuários vejam os resultados após executar.",
|
||||
"outputsExample": "Exemplos: “Salvar imagem” ou “Salvar vídeo”",
|
||||
"promptAddInputs": "Clique nos parâmetros do nó para adicioná-los aqui como entradas",
|
||||
"promptAddOutputs": "Clique nos nós de saída para adicioná-los aqui. Estes serão os resultados gerados.",
|
||||
"title": "Modo construtor de app"
|
||||
},
|
||||
"downloadAll": "Baixar tudo",
|
||||
"dragAndDropImage": "Arraste e solte uma imagem",
|
||||
"graphMode": "Modo Gráfico",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "Mostrar Conexões"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "Você precisará encontrá-los e baixá-los manualmente. Procure por eles online (tente Civitai ou Hugging Face) ou entre em contato com o provedor original do fluxo de trabalho.",
|
||||
"customModelsWarning": "Alguns desses são modelos personalizados que não reconhecemos.",
|
||||
"description": "Este fluxo de trabalho requer modelos que você ainda não baixou.",
|
||||
"doNotAskAgain": "Não mostrar novamente",
|
||||
"missingModels": "Modelos ausentes",
|
||||
"missingModelsMessage": "Ao carregar o grafo, os seguintes modelos não foram encontrados",
|
||||
"downloadAll": "Baixar todos",
|
||||
"downloadAvailable": "Baixar disponíveis",
|
||||
"footerDescription": "Baixe e coloque esses modelos na pasta correta.\nNós com modelos ausentes estão destacados em vermelho no canvas.",
|
||||
"gotIt": "Ok, entendi",
|
||||
"reEnableInSettings": "Reativar em {link}",
|
||||
"reEnableInSettingsLink": "Configurações"
|
||||
"reEnableInSettingsLink": "Configurações",
|
||||
"title": "Este fluxo de trabalho está sem modelos",
|
||||
"totalSize": "Tamanho total do download:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2696,7 +2724,9 @@
|
||||
"addCreditsLabel": "Adicione mais créditos quando quiser",
|
||||
"benefits": {
|
||||
"benefit1": "$10 em créditos mensais para Partner Nodes — recarregue quando necessário",
|
||||
"benefit2": "Até 30 min de execução por tarefa"
|
||||
"benefit1FreeTier": "Mais créditos mensais, recarregue a qualquer momento",
|
||||
"benefit2": "Até 30 min de execução por tarefa",
|
||||
"benefit3": "Use seus próprios modelos (Creator & Pro)"
|
||||
},
|
||||
"beta": "BETA",
|
||||
"billedMonthly": "Cobrado mensalmente",
|
||||
@@ -2734,6 +2764,21 @@
|
||||
"description": "Escolha o melhor plano para você",
|
||||
"descriptionWorkspace": "Escolha o melhor plano para seu workspace",
|
||||
"expiresDate": "Expira em {date}",
|
||||
"freeTier": {
|
||||
"description": "Seu plano gratuito inclui {credits} créditos por mês para testar o Comfy Cloud.",
|
||||
"descriptionGeneric": "Seu plano gratuito inclui uma cota mensal de créditos para testar o Comfy Cloud.",
|
||||
"nextRefresh": "Seus créditos serão renovados em {date}.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "Assine para liberar recargas e mais benefícios",
|
||||
"title": "Você ficou sem créditos gratuitos"
|
||||
},
|
||||
"subscribeCta": "Assine para mais",
|
||||
"title": "Você está no plano Gratuito",
|
||||
"topUpBlocked": {
|
||||
"title": "Desbloqueie recargas e mais benefícios"
|
||||
},
|
||||
"upgradeCta": "Ver planos"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||
"haveQuestions": "Tem dúvidas ou interesse em soluções empresariais?",
|
||||
"invoiceHistory": "Histórico de faturas",
|
||||
@@ -2744,6 +2789,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 min",
|
||||
"founder": "30 min",
|
||||
"free": "30 min",
|
||||
"pro": "1 h",
|
||||
"standard": "30 min"
|
||||
},
|
||||
@@ -2816,6 +2862,9 @@
|
||||
"founder": {
|
||||
"name": "Edição do Fundador"
|
||||
},
|
||||
"free": {
|
||||
"name": "Gratuito"
|
||||
},
|
||||
"pro": {
|
||||
"name": "Pro"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "и",
|
||||
"backToLogin": "Вернуться к входу",
|
||||
"backToSocialLogin": "Зарегистрируйтесь через Google или Github",
|
||||
"confirmPasswordLabel": "Подтвердите пароль",
|
||||
"confirmPasswordPlaceholder": "Введите тот же пароль еще раз",
|
||||
"didntReceiveEmail": "Не получили письмо? Свяжитесь с нами по адресу",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "Вход не удался",
|
||||
"forgotPassword": "Забыли пароль?",
|
||||
"forgotPasswordError": "Не удалось отправить письмо для сброса пароля",
|
||||
"freeTierBadge": "Доступен бесплатный тариф",
|
||||
"freeTierDescription": "Зарегистрируйтесь через Google и получите {credits} бесплатных кредитов каждый месяц. Карта не требуется.",
|
||||
"freeTierDescriptionGeneric": "Зарегистрируйтесь через Google и получайте бесплатные кредиты каждый месяц. Карта не требуется.",
|
||||
"insecureContextWarning": "Это соединение небезопасно (HTTP) — ваши учетные данные могут быть перехвачены злоумышленниками, если вы продолжите вход.",
|
||||
"loginButton": "Войти",
|
||||
"loginWithGithub": "Войти через Github",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "Отправить ссылку для сброса",
|
||||
"signInOrSignUp": "Войти / Зарегистрироваться",
|
||||
"signUp": "Зарегистрироваться",
|
||||
"signUpFreeTierPromo": "Впервые здесь? {signUp} через Google и получите {credits} бесплатных кредитов каждый месяц.",
|
||||
"success": "Вход выполнен успешно",
|
||||
"termsLink": "Условиями использования",
|
||||
"termsText": "Нажимая \"Далее\" или \"Зарегистрироваться\", вы соглашаетесь с нашими",
|
||||
"title": "Войдите в свой аккаунт",
|
||||
"useApiKey": "Comfy API-ключ",
|
||||
"useEmailInstead": "Использовать электронную почту",
|
||||
"userAvatar": "Аватар пользователя"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "Уже есть аккаунт?",
|
||||
"emailLabel": "Электронная почта",
|
||||
"emailNotEligibleForFreeTier": "Регистрация по электронной почте не даёт права на бесплатный тариф.",
|
||||
"emailPlaceholder": "Введите вашу электронную почту",
|
||||
"passwordLabel": "Пароль",
|
||||
"passwordPlaceholder": "Введите новый пароль",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "Переключиться на выбор"
|
||||
},
|
||||
"beta": "Режим приложения Бета - Оставить отзыв",
|
||||
"builder": {
|
||||
"exit": "Выйти из конструктора",
|
||||
"exitConfirmMessage": "У вас есть несохранённые изменения, которые будут потеряны\nВыйти без сохранения?",
|
||||
"exitConfirmTitle": "Выйти из конструктора приложений?",
|
||||
"inputsDesc": "Пользователи будут взаимодействовать с этими параметрами и настраивать их для генерации результата.",
|
||||
"inputsExample": "Примеры: «Загрузить изображение», «Текстовый промпт», «Шаги»",
|
||||
"noInputs": "Входные данные ещё не добавлены",
|
||||
"noOutputs": "Выходные узлы ещё не добавлены",
|
||||
"outputsDesc": "Подключите хотя бы один выходной узел, чтобы пользователи видели результаты после запуска.",
|
||||
"outputsExample": "Примеры: «Сохранить изображение» или «Сохранить видео»",
|
||||
"promptAddInputs": "Нажмите на параметры узла, чтобы добавить их сюда как входные данные",
|
||||
"promptAddOutputs": "Нажмите на выходные узлы, чтобы добавить их сюда. Это будут сгенерированные результаты.",
|
||||
"title": "Режим конструктора приложений"
|
||||
},
|
||||
"downloadAll": "Скачать всё",
|
||||
"dragAndDropImage": "Перетащите изображение",
|
||||
"graphMode": "Графовый режим",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "Показать связи"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "Вам нужно найти и скачать их вручную. Поискать их можно в интернете (например, на Civitai или Hugging Face) или связаться с автором рабочего процесса.",
|
||||
"customModelsWarning": "Некоторые из них — это пользовательские модели, которые нам не известны.",
|
||||
"description": "Для этого рабочего процесса требуются модели, которые вы ещё не скачали.",
|
||||
"doNotAskAgain": "Больше не показывать это",
|
||||
"missingModels": "Отсутствующие модели",
|
||||
"missingModelsMessage": "При загрузке графа следующие модели не были найдены",
|
||||
"downloadAll": "Скачать всё",
|
||||
"downloadAvailable": "Доступно для загрузки",
|
||||
"footerDescription": "Скачайте и поместите эти модели в нужную папку.\nУзлы с отсутствующими моделями выделены красным на холсте.",
|
||||
"gotIt": "Понятно",
|
||||
"reEnableInSettings": "Включить снова в {link}",
|
||||
"reEnableInSettingsLink": "Настройки"
|
||||
"reEnableInSettingsLink": "Настройки",
|
||||
"title": "В этом рабочем процессе отсутствуют модели",
|
||||
"totalSize": "Общий размер загрузки:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "Добавляйте кредиты в любое время",
|
||||
"benefits": {
|
||||
"benefit1": "Ежемесячные кредиты для Партнёрских узлов — пополняйте по необходимости",
|
||||
"benefit2": "До 30 минут выполнения на задание"
|
||||
"benefit1FreeTier": "Больше кредитов в месяц, пополнение в любое время",
|
||||
"benefit2": "До 30 минут выполнения на задание",
|
||||
"benefit3": "Используйте свои модели (Creator & Pro)"
|
||||
},
|
||||
"beta": "БЕТА",
|
||||
"billedMonthly": "Оплата ежемесячно",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "Выберите лучший план для себя",
|
||||
"descriptionWorkspace": "Выберите лучший тариф для вашего рабочего пространства",
|
||||
"expiresDate": "Истекает {date}",
|
||||
"freeTier": {
|
||||
"description": "Ваш бесплатный тариф включает {credits} кредитов каждый месяц для использования Comfy Cloud.",
|
||||
"descriptionGeneric": "Ваш бесплатный тариф включает ежемесячный лимит кредитов для использования Comfy Cloud.",
|
||||
"nextRefresh": "Кредиты обновятся {date}.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "Оформите подписку для пополнения и других возможностей",
|
||||
"title": "У вас закончились бесплатные кредиты"
|
||||
},
|
||||
"subscribeCta": "Подписаться для большего",
|
||||
"title": "Вы на бесплатном тарифе",
|
||||
"topUpBlocked": {
|
||||
"title": "Откройте пополнение и другие возможности"
|
||||
},
|
||||
"upgradeCta": "Посмотреть тарифы"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (96ГБ VRAM)",
|
||||
"haveQuestions": "Есть вопросы или интересует корпоративное решение?",
|
||||
"invoiceHistory": "История счетов",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 мин",
|
||||
"founder": "30 мин",
|
||||
"free": "30 мин",
|
||||
"pro": "1 ч",
|
||||
"standard": "30 мин"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "Founder's Edition"
|
||||
},
|
||||
"free": {
|
||||
"name": "Бесплатно"
|
||||
},
|
||||
"pro": {
|
||||
"name": "Pro"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "ve",
|
||||
"backToLogin": "Girişe dön",
|
||||
"backToSocialLogin": "Bunun yerine Google veya Github ile kaydolun",
|
||||
"confirmPasswordLabel": "Şifreyi Onayla",
|
||||
"confirmPasswordPlaceholder": "Aynı şifreyi tekrar girin",
|
||||
"didntReceiveEmail": "E-posta almadınız mı? Bize şu adresten ulaşın:",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "Giriş başarısız",
|
||||
"forgotPassword": "Şifrenizi mi unuttunuz?",
|
||||
"forgotPasswordError": "Şifre sıfırlama e-postası gönderilemedi",
|
||||
"freeTierBadge": "Ücretsiz Katman Uygun",
|
||||
"freeTierDescription": "Google ile kaydolun, her ay {credits} ücretsiz kredi kazanın. Kart gerekmez.",
|
||||
"freeTierDescriptionGeneric": "Google ile kaydolun, her ay ücretsiz kredi kazanın. Kart gerekmez.",
|
||||
"insecureContextWarning": "Bu bağlantı güvensiz (HTTP) - giriş yapmaya devam ederseniz kimlik bilgileriniz saldırganlar tarafından ele geçirilebilir.",
|
||||
"loginButton": "Giriş Yap",
|
||||
"loginWithGithub": "Github ile giriş yap",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "Sıfırlama bağlantısını gönder",
|
||||
"signInOrSignUp": "Giriş Yap / Kaydol",
|
||||
"signUp": "Kaydol",
|
||||
"signUpFreeTierPromo": "Yeni misiniz? Her ay {credits} ücretsiz kredi almak için Google ile {signUp} olun.",
|
||||
"success": "Giriş başarılı",
|
||||
"termsLink": "Kullanım Koşullarımızı",
|
||||
"termsText": "\"İleri\" veya \"Kaydol\" düğmesine tıklayarak,",
|
||||
"title": "Hesabınıza giriş yapın",
|
||||
"useApiKey": "Comfy API Anahtarı",
|
||||
"useEmailInstead": "Bunun yerine e-posta kullan",
|
||||
"userAvatar": "Kullanıcı Avatarı"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "Zaten bir hesabınız var mı?",
|
||||
"emailLabel": "E-posta",
|
||||
"emailNotEligibleForFreeTier": "E-posta ile kayıt Ücretsiz Katman için uygun değildir.",
|
||||
"emailPlaceholder": "E-postanızı girin",
|
||||
"passwordLabel": "Şifre",
|
||||
"passwordPlaceholder": "Yeni şifre girin",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "Seç'e Geç"
|
||||
},
|
||||
"beta": "Uygulama Modu Beta - Geri Bildirim Verin",
|
||||
"builder": {
|
||||
"exit": "Oluşturucudan çık",
|
||||
"exitConfirmMessage": "Kaydedilmemiş değişiklikleriniz kaybolacak\nKaydetmeden çıkılsın mı?",
|
||||
"exitConfirmTitle": "Uygulama oluşturucudan çıkılsın mı?",
|
||||
"inputsDesc": "Kullanıcılar bunlarla etkileşime geçip ayarlayarak çıktılarını oluşturacak.",
|
||||
"inputsExample": "Örnekler: “Resim yükle”, “Metin istemi”, “Adımlar”",
|
||||
"noInputs": "Henüz giriş eklenmedi",
|
||||
"noOutputs": "Henüz çıktı düğümü eklenmedi",
|
||||
"outputsDesc": "Kullanıcıların çalıştırdıktan sonra sonuçları görebilmesi için en az bir çıktı düğümü bağlayın.",
|
||||
"outputsExample": "Örnekler: “Resmi Kaydet” veya “Videoyu Kaydet”",
|
||||
"promptAddInputs": "Girdi olarak eklemek için düğüm parametrelerine tıklayın",
|
||||
"promptAddOutputs": "Çıktı olarak eklemek için çıktı düğümlerine tıklayın. Bunlar oluşturulan sonuçlar olacak.",
|
||||
"title": "Uygulama oluşturucu modu"
|
||||
},
|
||||
"downloadAll": "Tümünü İndir",
|
||||
"dragAndDropImage": "Bir görseli sürükleyip bırakın",
|
||||
"graphMode": "Grafik Modu",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "Bağlantıları Göster"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "Bunları manuel olarak bulup indirmeniz gerekecek. İnternette arayın (Civitai veya Hugging Face deneyin) ya da orijinal iş akışı sağlayıcısıyla iletişime geçin.",
|
||||
"customModelsWarning": "Bunlardan bazıları tanımadığımız özel modellerdir.",
|
||||
"description": "Bu iş akışı, henüz indirmediğiniz modellere ihtiyaç duyuyor.",
|
||||
"doNotAskAgain": "Bunu bir daha gösterme",
|
||||
"missingModels": "Eksik Modeller",
|
||||
"missingModelsMessage": "Grafik yüklenirken aşağıdaki modeller bulunamadı",
|
||||
"downloadAll": "Hepsini indir",
|
||||
"downloadAvailable": "İndirilebilir",
|
||||
"footerDescription": "Bu modelleri indirip doğru klasöre yerleştirin.\nEksik modeli olan düğümler tuvalde kırmızı ile vurgulanır.",
|
||||
"gotIt": "Tamam, anladım",
|
||||
"reEnableInSettings": "{link} içinde tekrar etkinleştir",
|
||||
"reEnableInSettingsLink": "Ayarlar"
|
||||
"reEnableInSettingsLink": "Ayarlar",
|
||||
"title": "Bu iş akışında eksik modeller var",
|
||||
"totalSize": "Toplam indirme boyutu:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "İstediğiniz zaman kredi ekleyin",
|
||||
"benefits": {
|
||||
"benefit1": "Partner Düğümleri için aylık krediler — ihtiyaç duyulduğunda yükleyin",
|
||||
"benefit2": "İş başına en fazla 30 dakika çalışma süresi"
|
||||
"benefit1FreeTier": "Daha fazla aylık kredi, istediğiniz zaman yükleyin",
|
||||
"benefit2": "İş başına en fazla 30 dakika çalışma süresi",
|
||||
"benefit3": "Kendi modellerinizi getirin (Creator & Pro)"
|
||||
},
|
||||
"beta": "BETA",
|
||||
"billedMonthly": "Aylık faturalandırılır",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "Sizin için en iyi planı seçin",
|
||||
"descriptionWorkspace": "Çalışma alanınız için en iyi planı seçin",
|
||||
"expiresDate": "{date} tarihinde sona erer",
|
||||
"freeTier": {
|
||||
"description": "Ücretsiz planınız, Comfy Cloud'u denemek için her ay {credits} kredi içerir.",
|
||||
"descriptionGeneric": "Ücretsiz planınız, Comfy Cloud'u denemek için aylık kredi hakkı içerir.",
|
||||
"nextRefresh": "Kredileriniz {date} tarihinde yenilenecek.",
|
||||
"outOfCredits": {
|
||||
"subtitle": "Yeniden yükleme ve daha fazlasının kilidini açmak için abone olun",
|
||||
"title": "Ücretsiz kredileriniz bitti"
|
||||
},
|
||||
"subscribeCta": "Daha fazlası için abone olun",
|
||||
"title": "Ücretsiz plandasınız",
|
||||
"topUpBlocked": {
|
||||
"title": "Yeniden yükleme ve daha fazlasının kilidini açın"
|
||||
},
|
||||
"upgradeCta": "Planları görüntüle"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||
"haveQuestions": "Sorularınız mı var veya kurumsal çözüm mü arıyorsunuz?",
|
||||
"invoiceHistory": "Fatura geçmişi",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 dk",
|
||||
"founder": "30 dk",
|
||||
"free": "30 dk",
|
||||
"pro": "1 sa",
|
||||
"standard": "30 dk"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "Kurucu Sürümü"
|
||||
},
|
||||
"free": {
|
||||
"name": "Ücretsiz"
|
||||
},
|
||||
"pro": {
|
||||
"name": "Pro"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "以及",
|
||||
"backToLogin": "返回登入",
|
||||
"backToSocialLogin": "改用 Google 或 Github 註冊",
|
||||
"confirmPasswordLabel": "確認密碼",
|
||||
"confirmPasswordPlaceholder": "請再次輸入相同密碼",
|
||||
"didntReceiveEmail": "沒有收到電子郵件?請聯絡我們:",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "登入失敗",
|
||||
"forgotPassword": "忘記密碼?",
|
||||
"forgotPasswordError": "密碼重設郵件發送失敗",
|
||||
"freeTierBadge": "符合免費方案資格",
|
||||
"freeTierDescription": "使用 Google 註冊,每月可獲得 {credits} 免費點數。無需信用卡。",
|
||||
"freeTierDescriptionGeneric": "使用 Google 註冊,每月可獲得免費點數。無需信用卡。",
|
||||
"insecureContextWarning": "此連線不安全(HTTP)。如果您繼續登入,您的憑證可能會被攻擊者攔截。",
|
||||
"loginButton": "登入",
|
||||
"loginWithGithub": "使用 Github 登入",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "發送重設連結",
|
||||
"signInOrSignUp": "登入 / 註冊",
|
||||
"signUp": "註冊",
|
||||
"signUpFreeTierPromo": "新用戶?{signUp} 使用 Google 註冊,每月獲得 {credits} 免費點數。",
|
||||
"success": "登入成功",
|
||||
"termsLink": "使用條款",
|
||||
"termsText": "點擊「下一步」或「註冊」即表示您同意我們的",
|
||||
"title": "登入您的帳戶",
|
||||
"useApiKey": "Comfy API 金鑰",
|
||||
"useEmailInstead": "改用電子郵件",
|
||||
"userAvatar": "用戶頭像"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "已經有帳戶?",
|
||||
"emailLabel": "電子郵件",
|
||||
"emailNotEligibleForFreeTier": "電子郵件註冊不符合免費方案資格。",
|
||||
"emailPlaceholder": "請輸入您的電子郵件",
|
||||
"passwordLabel": "密碼",
|
||||
"passwordPlaceholder": "請輸入新密碼",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "切換到選擇"
|
||||
},
|
||||
"beta": "App 模式 Beta - 提供回饋",
|
||||
"builder": {
|
||||
"exit": "離開建構器",
|
||||
"exitConfirmMessage": "您有尚未儲存的變更將會遺失\n確定要不儲存直接離開嗎?",
|
||||
"exitConfirmTitle": "要離開應用程式建構器嗎?",
|
||||
"inputsDesc": "使用者可調整這些參數以產生輸出。",
|
||||
"inputsExample": "例如:「載入圖像」、「文字提示」、「步數」",
|
||||
"noInputs": "尚未新增任何輸入",
|
||||
"noOutputs": "尚未新增任何輸出節點",
|
||||
"outputsDesc": "請至少連接一個輸出節點,讓使用者在執行後能看到結果。",
|
||||
"outputsExample": "例如:「儲存圖像」或「儲存影片」",
|
||||
"promptAddInputs": "點擊節點參數,將其新增為輸入",
|
||||
"promptAddOutputs": "點擊輸出節點,將其新增於此。這些將是產生的結果。",
|
||||
"title": "應用程式建構模式"
|
||||
},
|
||||
"downloadAll": "全部下載",
|
||||
"dragAndDropImage": "拖曳圖片到此",
|
||||
"graphMode": "圖形模式",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "顯示連結"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "您需要自行尋找並下載這些模型。請在網路上搜尋(可嘗試 Civitai 或 Hugging Face),或聯絡原始工作流程提供者。",
|
||||
"customModelsWarning": "其中有些是我們無法識別的自訂模型。",
|
||||
"description": "此工作流程需要您尚未下載的模型。",
|
||||
"doNotAskAgain": "不要再顯示此訊息",
|
||||
"missingModels": "缺少模型",
|
||||
"missingModelsMessage": "載入圖形時,找不到以下模型",
|
||||
"downloadAll": "全部下載",
|
||||
"downloadAvailable": "下載可用項目",
|
||||
"footerDescription": "請下載並將這些模型放置在正確的資料夾中。\n缺少模型的節點會在畫布上以紅色標示。",
|
||||
"gotIt": "知道了",
|
||||
"reEnableInSettings": "請在{link}中重新啟用",
|
||||
"reEnableInSettingsLink": "設定"
|
||||
"reEnableInSettingsLink": "設定",
|
||||
"title": "此工作流程缺少模型",
|
||||
"totalSize": "總下載大小:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2684,7 +2712,9 @@
|
||||
"addCreditsLabel": "隨時可儲值點數",
|
||||
"benefits": {
|
||||
"benefit1": "合作節點每月點數 — 需要時可隨時加值",
|
||||
"benefit2": "每項任務最多運行 30 分鐘"
|
||||
"benefit1FreeTier": "每月更多點數,隨時加值",
|
||||
"benefit2": "每項任務最多運行 30 分鐘",
|
||||
"benefit3": "可自帶模型(Creator & Pro)"
|
||||
},
|
||||
"beta": "測試版",
|
||||
"billedMonthly": "每月收費",
|
||||
@@ -2722,6 +2752,21 @@
|
||||
"description": "選擇最適合您的方案",
|
||||
"descriptionWorkspace": "為您的工作區選擇最佳方案",
|
||||
"expiresDate": "將於 {date} 到期",
|
||||
"freeTier": {
|
||||
"description": "您的免費方案每月包含 {credits} 點數,可體驗 Comfy Cloud。",
|
||||
"descriptionGeneric": "您的免費方案每月包含點數額度,可體驗 Comfy Cloud。",
|
||||
"nextRefresh": "您的點數將於 {date} 重置。",
|
||||
"outOfCredits": {
|
||||
"subtitle": "訂閱以解鎖加值與更多功能",
|
||||
"title": "您的免費點數已用完"
|
||||
},
|
||||
"subscribeCta": "訂閱以獲得更多",
|
||||
"title": "您目前使用的是免費方案",
|
||||
"topUpBlocked": {
|
||||
"title": "解鎖加值與更多功能"
|
||||
},
|
||||
"upgradeCta": "查看方案"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro(96GB VRAM)",
|
||||
"haveQuestions": "有疑問或想了解企業方案?",
|
||||
"invoiceHistory": "發票記錄",
|
||||
@@ -2732,6 +2777,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 分鐘",
|
||||
"founder": "30 分鐘",
|
||||
"free": "30 分鐘",
|
||||
"pro": "1 小時",
|
||||
"standard": "30 分鐘"
|
||||
},
|
||||
@@ -2804,6 +2850,9 @@
|
||||
"founder": {
|
||||
"name": "創始版"
|
||||
},
|
||||
"free": {
|
||||
"name": "免費"
|
||||
},
|
||||
"pro": {
|
||||
"name": "專業版"
|
||||
},
|
||||
|
||||
@@ -225,6 +225,7 @@
|
||||
"login": {
|
||||
"andText": "和",
|
||||
"backToLogin": "返回登录",
|
||||
"backToSocialLogin": "改用 Google 或 Github 注册",
|
||||
"confirmPasswordLabel": "确认密码",
|
||||
"confirmPasswordPlaceholder": "再次输入相同的密码",
|
||||
"didntReceiveEmail": "没有收到邮件?请联系我们:",
|
||||
@@ -233,6 +234,9 @@
|
||||
"failed": "登录失败",
|
||||
"forgotPassword": "忘记密码?",
|
||||
"forgotPasswordError": "发送重置密码邮件失败",
|
||||
"freeTierBadge": "可享免费套餐",
|
||||
"freeTierDescription": "使用 Google 注册,每月可获得 {credits} 免费积分。无需绑定银行卡。",
|
||||
"freeTierDescriptionGeneric": "使用 Google 注册,每月可获得免费积分。无需绑定银行卡。",
|
||||
"insecureContextWarning": "此连接不安全(HTTP)—如果继续登录,您的凭据可能会被攻击者拦截。",
|
||||
"loginButton": "登录",
|
||||
"loginWithGithub": "使用Github登录",
|
||||
@@ -251,11 +255,13 @@
|
||||
"sendResetLink": "发送重置链接",
|
||||
"signInOrSignUp": "登录 / 注册",
|
||||
"signUp": "注册",
|
||||
"signUpFreeTierPromo": "新用户?使用 Google {signUp},每月可获得 {credits} 免费积分。",
|
||||
"success": "登录成功",
|
||||
"termsLink": "使用条款",
|
||||
"termsText": "点击“下一步”或“注册”即表示您同意我们的",
|
||||
"title": "登录您的账户",
|
||||
"useApiKey": "Comfy API 密钥",
|
||||
"useEmailInstead": "改用邮箱",
|
||||
"userAvatar": "用户头像"
|
||||
},
|
||||
"loginButton": {
|
||||
@@ -282,6 +288,7 @@
|
||||
"signup": {
|
||||
"alreadyHaveAccount": "已经有账户了?",
|
||||
"emailLabel": "电子邮件",
|
||||
"emailNotEligibleForFreeTier": "邮箱注册不支持免费套餐。",
|
||||
"emailPlaceholder": "输入您的电子邮件",
|
||||
"passwordLabel": "密码",
|
||||
"passwordPlaceholder": "输入新密码",
|
||||
@@ -1331,6 +1338,20 @@
|
||||
"switchToSelectButton": "切换到选择"
|
||||
},
|
||||
"beta": "App 模式测试版 - 提供反馈",
|
||||
"builder": {
|
||||
"exit": "退出构建器",
|
||||
"exitConfirmMessage": "您有未保存的更改将会丢失\n确定不保存直接退出吗?",
|
||||
"exitConfirmTitle": "退出应用构建器?",
|
||||
"inputsDesc": "用户可通过这些输入项进行交互和调整,以生成输出结果。",
|
||||
"inputsExample": "示例:“加载图像”、“文本提示”、“步数”",
|
||||
"noInputs": "尚未添加输入项",
|
||||
"noOutputs": "尚未添加输出节点",
|
||||
"outputsDesc": "请至少连接一个输出节点,用户运行后才能看到结果。",
|
||||
"outputsExample": "示例:“保存图像”或“保存视频”",
|
||||
"promptAddInputs": "点击节点参数,将其添加为输入项",
|
||||
"promptAddOutputs": "点击输出节点,将其添加到此处。这些将作为生成结果。",
|
||||
"title": "应用构建模式"
|
||||
},
|
||||
"downloadAll": "全部下载",
|
||||
"dragAndDropImage": "拖拽图片到此处",
|
||||
"graphMode": "图形模式",
|
||||
@@ -1868,11 +1889,18 @@
|
||||
"showLinks": "显示连接"
|
||||
},
|
||||
"missingModelsDialog": {
|
||||
"customModelsInstruction": "您需要手动查找并下载这些模型。请在网上搜索(如 Civitai 或 Hugging Face),或联系原始工作流提供者。",
|
||||
"customModelsWarning": "其中一些是我们无法识别的自定义模型。",
|
||||
"description": "此工作流需要您尚未下载的模型。",
|
||||
"doNotAskAgain": "不再显示此消息",
|
||||
"missingModels": "缺少模型",
|
||||
"missingModelsMessage": "加载工作流时,未找到以下模型",
|
||||
"downloadAll": "全部下载",
|
||||
"downloadAvailable": "下载可用项",
|
||||
"footerDescription": "请下载并将这些模型放入正确的文件夹。\n画布上缺少模型的节点会以红色高亮显示。",
|
||||
"gotIt": "好的,知道了",
|
||||
"reEnableInSettings": "可在{link}中重新启用",
|
||||
"reEnableInSettingsLink": "设置"
|
||||
"reEnableInSettingsLink": "设置",
|
||||
"title": "此工作流缺少模型",
|
||||
"totalSize": "总下载大小:"
|
||||
},
|
||||
"missingNodes": {
|
||||
"cloud": {
|
||||
@@ -2696,7 +2724,9 @@
|
||||
"addCreditsLabel": "随时获取更多积分",
|
||||
"benefits": {
|
||||
"benefit1": "合作伙伴节点的月度积分 — 按需充值",
|
||||
"benefit2": "每个队列最长运行 30 分钟"
|
||||
"benefit1FreeTier": "每月更多积分,随时补充",
|
||||
"benefit2": "每个队列最长运行 30 分钟",
|
||||
"benefit3": "支持自带模型(Creator & Pro)"
|
||||
},
|
||||
"beta": "测试版",
|
||||
"billedMonthly": "每月付款",
|
||||
@@ -2734,6 +2764,21 @@
|
||||
"description": "选择最适合您的订阅计划",
|
||||
"descriptionWorkspace": "为您的工作区选择最佳方案",
|
||||
"expiresDate": "于 {date} 过期",
|
||||
"freeTier": {
|
||||
"description": "您的免费套餐每月包含 {credits} 积分,可体验 Comfy Cloud。",
|
||||
"descriptionGeneric": "您的免费套餐每月包含积分额度,可体验 Comfy Cloud。",
|
||||
"nextRefresh": "您的积分将在 {date} 刷新。",
|
||||
"outOfCredits": {
|
||||
"subtitle": "订阅以解锁补充积分和更多功能",
|
||||
"title": "您的免费积分已用完"
|
||||
},
|
||||
"subscribeCta": "订阅获取更多",
|
||||
"title": "您正在使用免费套餐",
|
||||
"topUpBlocked": {
|
||||
"title": "解锁补充积分和更多功能"
|
||||
},
|
||||
"upgradeCta": "查看套餐"
|
||||
},
|
||||
"gpuLabel": "RTX 6000 Pro (96GB VRAM)",
|
||||
"haveQuestions": "对企业级有疑问?",
|
||||
"invoiceHistory": "发票历史",
|
||||
@@ -2744,6 +2789,7 @@
|
||||
"maxDuration": {
|
||||
"creator": "30 分钟",
|
||||
"founder": "30 分钟",
|
||||
"free": "30 分钟",
|
||||
"pro": "1 小时",
|
||||
"standard": "30 分钟"
|
||||
},
|
||||
@@ -2816,6 +2862,9 @@
|
||||
"founder": {
|
||||
"name": "Founder's Edition"
|
||||
},
|
||||
"free": {
|
||||
"name": "免费"
|
||||
},
|
||||
"pro": {
|
||||
"name": "Pro"
|
||||
},
|
||||
|
||||
@@ -44,17 +44,7 @@
|
||||
<template #header />
|
||||
|
||||
<template #content>
|
||||
<template v-if="inSearch">
|
||||
<SettingsPanel :setting-groups="searchResults" />
|
||||
</template>
|
||||
<template v-else-if="activeSettingCategory">
|
||||
<CurrentUserMessage v-if="activeSettingCategory.label === 'Comfy'" />
|
||||
<ColorPaletteMessage
|
||||
v-if="activeSettingCategory.label === 'Appearance'"
|
||||
/>
|
||||
<SettingsPanel :setting-groups="sortedGroups(activeSettingCategory)" />
|
||||
</template>
|
||||
<template v-else-if="activePanel">
|
||||
<template v-if="activePanel">
|
||||
<Suspense>
|
||||
<component :is="activePanel.component" v-bind="activePanel.props" />
|
||||
<template #fallback>
|
||||
@@ -64,6 +54,16 @@
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
<template v-else-if="inSearch">
|
||||
<SettingsPanel :setting-groups="searchResults" />
|
||||
</template>
|
||||
<template v-else-if="activeSettingCategory">
|
||||
<CurrentUserMessage v-if="activeSettingCategory.label === 'Comfy'" />
|
||||
<ColorPaletteMessage
|
||||
v-if="activeSettingCategory.label === 'Appearance'"
|
||||
/>
|
||||
<SettingsPanel :setting-groups="sortedGroups(activeSettingCategory)" />
|
||||
</template>
|
||||
</template>
|
||||
</BaseModalLayout>
|
||||
</template>
|
||||
@@ -110,6 +110,7 @@ const {
|
||||
searchQuery,
|
||||
inSearch,
|
||||
searchResultsCategories,
|
||||
matchedNavItemKeys,
|
||||
handleSearch: handleSearchBase,
|
||||
getSearchResults
|
||||
} = useSettingSearch()
|
||||
@@ -119,16 +120,29 @@ const authActions = useFirebaseAuthActions()
|
||||
const navRef = ref<HTMLElement | null>(null)
|
||||
const activeCategoryKey = ref<string | null>(defaultCategory.value?.key ?? null)
|
||||
|
||||
watch(searchResultsCategories, (categories) => {
|
||||
if (!inSearch.value || categories.size === 0) return
|
||||
const firstMatch = navGroups.value
|
||||
.flatMap((g) => g.items)
|
||||
.find((item) => {
|
||||
const node = findCategoryByKey(item.id)
|
||||
return node && categories.has(node.label)
|
||||
})
|
||||
activeCategoryKey.value = firstMatch?.id ?? null
|
||||
})
|
||||
const searchableNavItems = computed(() =>
|
||||
navGroups.value.flatMap((g) =>
|
||||
g.items.map((item) => ({
|
||||
key: item.id,
|
||||
label: item.label
|
||||
}))
|
||||
)
|
||||
)
|
||||
|
||||
watch(
|
||||
[searchResultsCategories, matchedNavItemKeys],
|
||||
([categories, navKeys]) => {
|
||||
if (!inSearch.value || (categories.size === 0 && navKeys.size === 0)) return
|
||||
const firstMatch = navGroups.value
|
||||
.flatMap((g) => g.items)
|
||||
.find((item) => {
|
||||
if (navKeys.has(item.id)) return true
|
||||
const node = findCategoryByKey(item.id)
|
||||
return node && categories.has(node.label)
|
||||
})
|
||||
activeCategoryKey.value = firstMatch?.id ?? null
|
||||
}
|
||||
)
|
||||
|
||||
const activeSettingCategory = computed<SettingTreeNode | null>(() => {
|
||||
if (!activeCategoryKey.value) return null
|
||||
@@ -163,7 +177,7 @@ function sortedGroups(category: SettingTreeNode): ISettingGroup[] {
|
||||
}
|
||||
|
||||
function handleSearch(query: string) {
|
||||
handleSearchBase(query.trim())
|
||||
handleSearchBase(query.trim(), searchableNavItems.value)
|
||||
if (query) {
|
||||
activeCategoryKey.value = null
|
||||
} else if (!activeCategoryKey.value) {
|
||||
@@ -175,12 +189,7 @@ function onNavItemClick(id: string) {
|
||||
activeCategoryKey.value = id
|
||||
}
|
||||
|
||||
const searchResults = computed<ISettingGroup[]>(() => {
|
||||
const category = activeCategoryKey.value
|
||||
? findCategoryByKey(activeCategoryKey.value)
|
||||
: null
|
||||
return getSearchResults(category)
|
||||
})
|
||||
const searchResults = computed<ISettingGroup[]>(() => getSearchResults(null))
|
||||
|
||||
// Scroll to and highlight the target setting once the correct category renders.
|
||||
if (scrollToSettingId) {
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
<div class="setting-group">
|
||||
<Divider v-if="divider" />
|
||||
<h3>
|
||||
<span v-if="group.category" class="text-muted">
|
||||
{{
|
||||
$t(
|
||||
`settingsCategories.${normalizeI18nKey(group.category)}`,
|
||||
group.category
|
||||
)
|
||||
}}
|
||||
›
|
||||
</span>
|
||||
{{
|
||||
$t(`settingsCategories.${normalizeI18nKey(group.label)}`, group.label)
|
||||
}}
|
||||
@@ -27,6 +36,7 @@ import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
defineProps<{
|
||||
group: {
|
||||
label: string
|
||||
category?: string
|
||||
settings: SettingParams[]
|
||||
}
|
||||
divider?: boolean
|
||||
|
||||
@@ -299,10 +299,12 @@ describe('useSettingSearch', () => {
|
||||
expect(results).toEqual([
|
||||
{
|
||||
label: 'Basic',
|
||||
category: 'Category',
|
||||
settings: [mockSettings['Category.Setting1']]
|
||||
},
|
||||
{
|
||||
label: 'Advanced',
|
||||
category: 'Category',
|
||||
settings: [mockSettings['Category.Setting2']]
|
||||
}
|
||||
])
|
||||
@@ -332,15 +334,50 @@ describe('useSettingSearch', () => {
|
||||
expect(results).toEqual([
|
||||
{
|
||||
label: 'Basic',
|
||||
category: 'Category',
|
||||
settings: [mockSettings['Category.Setting1']]
|
||||
},
|
||||
{
|
||||
label: 'SubCategory',
|
||||
category: 'Other',
|
||||
settings: [mockSettings['Other.Setting3']]
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('returns results from all categories when searching cross-category term', () => {
|
||||
// Simulates the "badge" scenario: same term matches settings in
|
||||
// multiple categories (e.g. LiteGraph and Comfy)
|
||||
mockSettings['LiteGraph.BadgeSetting'] = {
|
||||
id: 'LiteGraph.BadgeSetting',
|
||||
name: 'Node source badge mode',
|
||||
type: 'combo',
|
||||
defaultValue: 'default',
|
||||
category: ['LiteGraph', 'Node']
|
||||
}
|
||||
mockSettings['Comfy.BadgeSetting'] = {
|
||||
id: 'Comfy.BadgeSetting',
|
||||
name: 'Show API node pricing badge',
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
category: ['Comfy', 'API Nodes']
|
||||
}
|
||||
|
||||
const search = useSettingSearch()
|
||||
search.handleSearch('badge')
|
||||
|
||||
expect(search.filteredSettingIds.value).toContain(
|
||||
'LiteGraph.BadgeSetting'
|
||||
)
|
||||
expect(search.filteredSettingIds.value).toContain('Comfy.BadgeSetting')
|
||||
|
||||
// getSearchResults(null) should return both categories' results
|
||||
const results = search.getSearchResults(null)
|
||||
const labels = results.map((g) => g.label)
|
||||
expect(labels).toContain('Node')
|
||||
expect(labels).toContain('API Nodes')
|
||||
})
|
||||
|
||||
it('returns empty array when no filtered results', () => {
|
||||
const search = useSettingSearch()
|
||||
search.filteredSettingIds.value = []
|
||||
@@ -372,6 +409,7 @@ describe('useSettingSearch', () => {
|
||||
expect(results).toEqual([
|
||||
{
|
||||
label: 'Basic',
|
||||
category: 'Category',
|
||||
settings: [
|
||||
mockSettings['Category.Setting1'],
|
||||
mockSettings['Category.Setting4']
|
||||
@@ -381,6 +419,75 @@ describe('useSettingSearch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('nav item matching', () => {
|
||||
const navItems = [
|
||||
{ key: 'keybinding', label: 'Keybinding' },
|
||||
{ key: 'about', label: 'About' },
|
||||
{ key: 'extension', label: 'Extension' },
|
||||
{ key: 'Comfy', label: 'Comfy' }
|
||||
]
|
||||
|
||||
it('matches nav items by key', () => {
|
||||
const search = useSettingSearch()
|
||||
|
||||
search.handleSearch('keybinding', navItems)
|
||||
|
||||
expect(search.matchedNavItemKeys.value.has('keybinding')).toBe(true)
|
||||
})
|
||||
|
||||
it('matches nav items by translated label (case insensitive)', () => {
|
||||
const search = useSettingSearch()
|
||||
|
||||
search.handleSearch('ABOUT', navItems)
|
||||
|
||||
expect(search.matchedNavItemKeys.value.has('about')).toBe(true)
|
||||
})
|
||||
|
||||
it('matches partial nav item labels', () => {
|
||||
const search = useSettingSearch()
|
||||
|
||||
search.handleSearch('ext', navItems)
|
||||
|
||||
expect(search.matchedNavItemKeys.value.has('extension')).toBe(true)
|
||||
})
|
||||
|
||||
it('clears matched nav item keys on empty query', () => {
|
||||
const search = useSettingSearch()
|
||||
|
||||
search.handleSearch('keybinding', navItems)
|
||||
expect(search.matchedNavItemKeys.value.size).toBeGreaterThan(0)
|
||||
|
||||
search.handleSearch('', navItems)
|
||||
expect(search.matchedNavItemKeys.value.size).toBe(0)
|
||||
})
|
||||
|
||||
it('can match both settings and nav items simultaneously', () => {
|
||||
const search = useSettingSearch()
|
||||
|
||||
search.handleSearch('other', navItems)
|
||||
|
||||
expect(search.filteredSettingIds.value).toContain('Other.Setting3')
|
||||
expect(search.matchedNavItemKeys.value.size).toBe(0)
|
||||
})
|
||||
|
||||
it('matches nav items with translated labels different from key', () => {
|
||||
const translatedNavItems = [{ key: 'keybinding', label: '키 바인딩' }]
|
||||
const search = useSettingSearch()
|
||||
|
||||
search.handleSearch('키 바인딩', translatedNavItems)
|
||||
|
||||
expect(search.matchedNavItemKeys.value.has('keybinding')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not match nav items when no nav items provided', () => {
|
||||
const search = useSettingSearch()
|
||||
|
||||
search.handleSearch('keybinding')
|
||||
|
||||
expect(search.matchedNavItemKeys.value.size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('handles empty settings store', () => {
|
||||
mockSettingStore.settingsById = {}
|
||||
|
||||
@@ -10,12 +10,18 @@ import type { ISettingGroup, SettingParams } from '@/platform/settings/types'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
|
||||
interface SearchableNavItem {
|
||||
key: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export function useSettingSearch() {
|
||||
const settingStore = useSettingStore()
|
||||
const { shouldRenderVueNodes } = useVueFeatureFlags()
|
||||
|
||||
const searchQuery = ref<string>('')
|
||||
const filteredSettingIds = ref<string[]>([])
|
||||
const matchedNavItemKeys = ref<Set<string>>(new Set())
|
||||
const searchInProgress = ref<boolean>(false)
|
||||
|
||||
watch(searchQuery, () => (searchInProgress.value = true))
|
||||
@@ -46,7 +52,9 @@ export function useSettingSearch() {
|
||||
/**
|
||||
* Handle search functionality
|
||||
*/
|
||||
const handleSearch = (query: string) => {
|
||||
const handleSearch = (query: string, navItems?: SearchableNavItem[]) => {
|
||||
matchedNavItemKeys.value = new Set()
|
||||
|
||||
if (!query) {
|
||||
filteredSettingIds.value = []
|
||||
return
|
||||
@@ -89,6 +97,17 @@ export function useSettingSearch() {
|
||||
)
|
||||
})
|
||||
|
||||
if (navItems) {
|
||||
for (const item of navItems) {
|
||||
if (
|
||||
item.key.toLocaleLowerCase().includes(queryLower) ||
|
||||
item.label.toLocaleLowerCase().includes(queryLower)
|
||||
) {
|
||||
matchedNavItemKeys.value.add(item.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filteredSettingIds.value = filteredSettings.map((x) => x.id)
|
||||
searchInProgress.value = false
|
||||
}
|
||||
@@ -99,30 +118,42 @@ export function useSettingSearch() {
|
||||
const getSearchResults = (
|
||||
activeCategory: SettingTreeNode | null
|
||||
): ISettingGroup[] => {
|
||||
const groupedSettings: { [key: string]: SettingParams[] } = {}
|
||||
const groupedSettings: {
|
||||
[key: string]: { category: string; settings: SettingParams[] }
|
||||
} = {}
|
||||
|
||||
filteredSettingIds.value.forEach((id) => {
|
||||
const setting = settingStore.settingsById[id]
|
||||
const info = getSettingInfo(setting)
|
||||
const groupLabel = info.subCategory
|
||||
const groupKey =
|
||||
activeCategory === null
|
||||
? `${info.category}/${info.subCategory}`
|
||||
: info.subCategory
|
||||
|
||||
if (activeCategory === null || activeCategory.label === info.category) {
|
||||
if (!groupedSettings[groupLabel]) {
|
||||
groupedSettings[groupLabel] = []
|
||||
if (!groupedSettings[groupKey]) {
|
||||
groupedSettings[groupKey] = {
|
||||
category: info.category,
|
||||
settings: []
|
||||
}
|
||||
}
|
||||
groupedSettings[groupLabel].push(setting)
|
||||
groupedSettings[groupKey].settings.push(setting)
|
||||
}
|
||||
})
|
||||
|
||||
return Object.entries(groupedSettings).map(([label, settings]) => ({
|
||||
label,
|
||||
settings
|
||||
}))
|
||||
return Object.entries(groupedSettings).map(
|
||||
([key, { category, settings }]) => ({
|
||||
label: activeCategory === null ? key.split('/')[1] : key,
|
||||
...(activeCategory === null ? { category } : {}),
|
||||
settings
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
filteredSettingIds,
|
||||
matchedNavItemKeys,
|
||||
searchInProgress,
|
||||
searchResultsCategories,
|
||||
queryIsEmpty,
|
||||
|
||||
@@ -62,6 +62,7 @@ export interface FormItem {
|
||||
|
||||
export interface ISettingGroup {
|
||||
label: string
|
||||
category?: string
|
||||
settings: SettingParams[]
|
||||
}
|
||||
|
||||
|
||||
@@ -277,7 +277,13 @@ const zExtra = z
|
||||
reroutes: z.array(zReroute).optional(),
|
||||
workflowRendererVersion: zRendererType.optional(),
|
||||
BlueprintDescription: z.string().optional(),
|
||||
BlueprintSearchAliases: z.array(z.string()).optional()
|
||||
BlueprintSearchAliases: z.array(z.string()).optional(),
|
||||
linearData: z
|
||||
.object({
|
||||
inputs: z.array(z.tuple([zNodeId, z.string()])).optional(),
|
||||
outputs: z.array(zNodeId).optional()
|
||||
})
|
||||
.optional()
|
||||
})
|
||||
.passthrough()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useEventListener, useTimeout } from '@vueuse/core'
|
||||
import { partition } from 'es-toolkit'
|
||||
import { partition, remove, takeWhile } from 'es-toolkit'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -55,6 +55,27 @@ useEventListener(
|
||||
() => (graphNodes.value = app.rootGraph.nodes)
|
||||
)
|
||||
|
||||
const mappedSelections = computed(() => {
|
||||
let unprocessedInputs = [...appModeStore.selectedInputs]
|
||||
//FIXME strict typing here
|
||||
const processedInputs: ReturnType<typeof nodeToNodeData>[] = []
|
||||
while (unprocessedInputs.length) {
|
||||
const nodeId = unprocessedInputs[0][0]
|
||||
const inputGroup = takeWhile(
|
||||
unprocessedInputs,
|
||||
([id]) => id === nodeId
|
||||
).map(([, widgetName]) => widgetName)
|
||||
unprocessedInputs = unprocessedInputs.slice(inputGroup.length)
|
||||
const node = app.rootGraph.getNodeById(nodeId)
|
||||
if (!node) continue
|
||||
|
||||
const nodeData = nodeToNodeData(node)
|
||||
remove(nodeData.widgets ?? [], (w) => !inputGroup.includes(w.name))
|
||||
processedInputs.push(nodeData)
|
||||
}
|
||||
return processedInputs
|
||||
})
|
||||
|
||||
function getDropIndicator(node: LGraphNode) {
|
||||
if (node.type !== 'LoadImage') return undefined
|
||||
|
||||
@@ -231,11 +252,13 @@ defineExpose({ runButtonClick })
|
||||
class="grow-1 md:overflow-y-auto md:contain-size"
|
||||
>
|
||||
<template
|
||||
v-for="(nodeData, index) of partitionedNodes[1]"
|
||||
v-for="(nodeData, index) of appModeStore.selectedInputs.length
|
||||
? mappedSelections
|
||||
: partitionedNodes[0]"
|
||||
:key="nodeData.id"
|
||||
>
|
||||
<div
|
||||
v-if="index !== 0"
|
||||
v-if="index !== 0 && !appModeStore.selectedInputs.length"
|
||||
class="w-full border-t-1 border-node-component-border"
|
||||
/>
|
||||
<DropZone
|
||||
|
||||
@@ -55,6 +55,7 @@ export function useTextPreviewWidget(
|
||||
typeof value === 'string' ? value : String(value)
|
||||
},
|
||||
getMinHeight: () => options.minHeight ?? 42 + PADDING,
|
||||
serialize: false,
|
||||
read_only: true
|
||||
},
|
||||
type: inputSpec.type
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { readonly, computed, ref } from 'vue'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { reactive, readonly, computed, ref, watch } from 'vue'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
export type AppMode = 'graph' | 'app' | 'builder:select' | 'builder:arrange'
|
||||
|
||||
export const useAppModeStore = defineStore('appMode', () => {
|
||||
const { getCanvas } = useCanvasStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const selectedInputs = reactive<[NodeId, string][]>([])
|
||||
const selectedOutputs = reactive<NodeId[]>([])
|
||||
const mode = ref<AppMode>('graph')
|
||||
const builderSaving = ref(false)
|
||||
const hasOutputs = ref(true)
|
||||
const enableAppBuilder = ref(false)
|
||||
const hasOutputs = computed(() => !!selectedOutputs.length)
|
||||
const enableAppBuilder = ref(true)
|
||||
|
||||
const isBuilderMode = computed(
|
||||
() => mode.value === 'builder:select' || mode.value === 'builder:arrange'
|
||||
@@ -22,14 +35,64 @@ export const useAppModeStore = defineStore('appMode', () => {
|
||||
() => builderSaving.value && isBuilderMode.value
|
||||
)
|
||||
|
||||
function resetSelectedToWorkflow() {
|
||||
const { activeWorkflow } = workflowStore
|
||||
if (!activeWorkflow) return
|
||||
|
||||
const { activeState } = activeWorkflow.changeTracker
|
||||
selectedInputs.splice(
|
||||
0,
|
||||
selectedInputs.length,
|
||||
...(activeState.extra?.linearData?.inputs ?? [])
|
||||
)
|
||||
selectedOutputs.splice(
|
||||
0,
|
||||
selectedOutputs.length,
|
||||
...(activeState.extra?.linearData?.outputs ?? [])
|
||||
)
|
||||
}
|
||||
function saveSelectedToWorkflow() {
|
||||
app.rootGraph.extra ??= {}
|
||||
app.rootGraph.extra.linearData = {
|
||||
inputs: [...selectedInputs],
|
||||
outputs: [...selectedOutputs]
|
||||
}
|
||||
}
|
||||
whenever(() => workflowStore.activeWorkflow, resetSelectedToWorkflow, {
|
||||
immediate: true
|
||||
})
|
||||
|
||||
watch(
|
||||
() => mode.value === 'builder:select',
|
||||
(inSelect) => (getCanvas().read_only = inSelect)
|
||||
)
|
||||
|
||||
async function exitBuilder() {
|
||||
if (
|
||||
!(await useDialogService().confirm({
|
||||
title: t('linearMode.builder.exitConfirmTitle'),
|
||||
message: t('linearMode.builder.exitConfirmMessage')
|
||||
}))
|
||||
)
|
||||
return
|
||||
|
||||
resetSelectedToWorkflow()
|
||||
mode.value = 'graph'
|
||||
}
|
||||
|
||||
return {
|
||||
mode: readonly(mode),
|
||||
enableAppBuilder: readonly(enableAppBuilder),
|
||||
exitBuilder,
|
||||
isBuilderMode,
|
||||
isAppMode,
|
||||
isGraphMode,
|
||||
isBuilderSaving,
|
||||
hasOutputs,
|
||||
resetSelectedToWorkflow,
|
||||
saveSelectedToWorkflow,
|
||||
selectedInputs,
|
||||
selectedOutputs,
|
||||
setBuilderSaving: (newBuilderSaving: boolean) => {
|
||||
if (!isBuilderMode.value) return
|
||||
builderSaving.value = newBuilderSaving
|
||||
|
||||
Reference in New Issue
Block a user