mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-04 04:30:04 +00:00
[API Node] Sign in required dialog (#3457)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -8,6 +8,15 @@ const vue3CompositionApiBestPractices = [
|
||||
"Use watch and watchEffect for side effects",
|
||||
"Implement lifecycle hooks with onMounted, onUpdated, etc.",
|
||||
"Utilize provide/inject for dependency injection",
|
||||
"Use vue 3.5 style of default prop declaration. Example:
|
||||
|
||||
const { nodes, showTotal = true } = defineProps<{
|
||||
nodes: ApiNodeCost[]
|
||||
showTotal?: boolean
|
||||
}>()
|
||||
|
||||
",
|
||||
"Organize vue component in <template> <script> <style> order",
|
||||
]
|
||||
|
||||
// Folder structure
|
||||
|
||||
BIN
public/assets/images/Comfy_Logo_x32.png
Normal file
BIN
public/assets/images/Comfy_Logo_x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
75
src/components/common/ApiNodesCostBreakdown.vue
Normal file
75
src/components/common/ApiNodesCostBreakdown.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 h-full">
|
||||
<div class="flex justify-between text-xs">
|
||||
<div>{{ t('apiNodesCostBreakdown.title') }}</div>
|
||||
<div>{{ t('apiNodesCostBreakdown.costPerRun') }}</div>
|
||||
</div>
|
||||
<ScrollPanel class="flex-grow h-0">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
v-for="node in nodes"
|
||||
:key="node.name"
|
||||
class="flex items-center justify-between px-3 py-2 rounded-md bg-[var(--p-content-border-color)]"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-base font-medium leading-tight">{{
|
||||
node.name
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<Tag
|
||||
severity="secondary"
|
||||
icon="pi pi-dollar"
|
||||
rounded
|
||||
class="text-amber-400 p-1"
|
||||
/>
|
||||
<span class="text-base font-medium leading-tight">
|
||||
{{ node.cost.toFixed(costPrecision) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollPanel>
|
||||
<template v-if="showTotal && nodes.length > 1">
|
||||
<Divider class="my-2" />
|
||||
<div class="flex justify-between items-center border-t px-3">
|
||||
<span class="text-sm">{{ t('apiNodesCostBreakdown.totalCost') }}</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<Tag
|
||||
severity="secondary"
|
||||
icon="pi pi-dollar"
|
||||
rounded
|
||||
class="text-yellow-500 p-1"
|
||||
/>
|
||||
<span>{{ totalCost.toFixed(costPrecision) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Divider from 'primevue/divider'
|
||||
import ScrollPanel from 'primevue/scrollpanel'
|
||||
import Tag from 'primevue/tag'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { ApiNodeCost } from '@/types/apiNodeTypes'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const {
|
||||
nodes,
|
||||
showTotal = true,
|
||||
costPrecision = 3
|
||||
} = defineProps<{
|
||||
nodes: ApiNodeCost[]
|
||||
showTotal?: boolean
|
||||
costPrecision?: number
|
||||
}>()
|
||||
|
||||
const totalCost = computed(() =>
|
||||
nodes.reduce((sum, node) => sum + node.cost, 0)
|
||||
)
|
||||
</script>
|
||||
43
src/components/dialog/content/ApiNodesSignInContent.vue
Normal file
43
src/components/dialog/content/ApiNodesSignInContent.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<!-- Prompt user that the workflow contains API nodes that needs login to run -->
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 max-w-96 h-110 p-2">
|
||||
<div class="text-2xl font-medium mb-2">
|
||||
{{ t('apiNodesSignInDialog.title') }}
|
||||
</div>
|
||||
|
||||
<div class="text-base mb-4">
|
||||
{{ t('apiNodesSignInDialog.message') }}
|
||||
</div>
|
||||
|
||||
<ApiNodesCostBreakdown :nodes="apiNodes" :show-total="true" />
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<Button :label="t('g.learnMore')" link />
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
:label="t('g.cancel')"
|
||||
outlined
|
||||
severity="secondary"
|
||||
@click="onCancel?.()"
|
||||
/>
|
||||
<Button :label="t('g.login')" @click="onLogin?.()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ApiNodesCostBreakdown from '@/components/common/ApiNodesCostBreakdown.vue'
|
||||
import type { ApiNodeCost } from '@/types/apiNodeTypes'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { apiNodes, onLogin, onCancel } = defineProps<{
|
||||
apiNodes: ApiNodeCost[]
|
||||
onLogin?: () => void
|
||||
onCancel?: () => void
|
||||
}>()
|
||||
</script>
|
||||
6
src/components/dialog/header/ComfyOrgHeader.vue
Normal file
6
src/components/dialog/header/ComfyOrgHeader.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- A dialog header with ComfyOrg logo -->
|
||||
<template>
|
||||
<div class="px-2 py-4">
|
||||
<img src="/assets/images/Comfy_Logo_x32.png" alt="ComfyOrg Logo" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -108,7 +108,9 @@
|
||||
"disabling": "Disabling",
|
||||
"updating": "Updating",
|
||||
"migrate": "Migrate",
|
||||
"updateAvailable": "Update Available"
|
||||
"updateAvailable": "Update Available",
|
||||
"login": "Login",
|
||||
"learnMore": "Learn more"
|
||||
},
|
||||
"manager": {
|
||||
"title": "Custom Nodes Manager",
|
||||
@@ -976,6 +978,15 @@
|
||||
"extensionFileHint": "This may be due to the following script",
|
||||
"promptExecutionError": "Prompt execution failed"
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"title": "Sign In Required to Use API Nodes",
|
||||
"message": "This workflow contains API Nodes, which require you to be signed in to your account in order to run."
|
||||
},
|
||||
"apiNodesCostBreakdown": {
|
||||
"title": "API Node(s)",
|
||||
"costPerRun": "Cost per run",
|
||||
"totalCost": "Total Cost"
|
||||
},
|
||||
"desktopUpdate": {
|
||||
"title": "Updating ComfyUI Desktop",
|
||||
"description": "ComfyUI Desktop is installing new dependencies. This may take a few minutes.",
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "Costo por ejecución",
|
||||
"title": "Nodo(s) de API",
|
||||
"totalCost": "Costo total"
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"message": "Este flujo de trabajo contiene nodos de API, que requieren que inicies sesión en tu cuenta para poder ejecutar.",
|
||||
"title": "Se requiere iniciar sesión para usar los nodos de API"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "Error al copiar al portapapeles",
|
||||
"errorNotSupported": "API del portapapeles no soportada en su navegador",
|
||||
@@ -172,9 +181,11 @@
|
||||
"installing": "Instalando",
|
||||
"interrupted": "Interrumpido",
|
||||
"keybinding": "Combinación de teclas",
|
||||
"learnMore": "Aprende más",
|
||||
"loadAllFolders": "Cargar todas las carpetas",
|
||||
"loadWorkflow": "Cargar flujo de trabajo",
|
||||
"loading": "Cargando",
|
||||
"login": "Iniciar sesión",
|
||||
"logs": "Registros",
|
||||
"migrate": "Migrar",
|
||||
"missing": "Faltante",
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "Coût par exécution",
|
||||
"title": "Nœud(s) API",
|
||||
"totalCost": "Coût total"
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"message": "Ce flux de travail contient des nœuds API, qui nécessitent que vous soyez connecté à votre compte pour pouvoir fonctionner.",
|
||||
"title": "Connexion requise pour utiliser les nœuds API"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "Échec de la copie dans le presse-papiers",
|
||||
"errorNotSupported": "L'API du presse-papiers n'est pas prise en charge par votre navigateur",
|
||||
@@ -172,9 +181,11 @@
|
||||
"installing": "Installation",
|
||||
"interrupted": "Interrompu",
|
||||
"keybinding": "Raccourci clavier",
|
||||
"learnMore": "En savoir plus",
|
||||
"loadAllFolders": "Charger tous les dossiers",
|
||||
"loadWorkflow": "Charger le flux de travail",
|
||||
"loading": "Chargement",
|
||||
"login": "Connexion",
|
||||
"logs": "Journaux",
|
||||
"migrate": "Migrer",
|
||||
"missing": "Manquant",
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "実行あたりのコスト",
|
||||
"title": "APIノード",
|
||||
"totalCost": "合計コスト"
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"message": "このワークフローにはAPIノードが含まれており、実行するためにはアカウントにサインインする必要があります。",
|
||||
"title": "APIノードを使用するためにはサインインが必要です"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "クリップボードへのコピーに失敗しました",
|
||||
"errorNotSupported": "お使いのブラウザではクリップボードAPIがサポートされていません",
|
||||
@@ -172,9 +181,11 @@
|
||||
"installing": "インストール中",
|
||||
"interrupted": "中断されました",
|
||||
"keybinding": "キーバインディング",
|
||||
"learnMore": "詳細を学ぶ",
|
||||
"loadAllFolders": "すべてのフォルダーを読み込む",
|
||||
"loadWorkflow": "ワークフローを読み込む",
|
||||
"loading": "読み込み中",
|
||||
"login": "ログイン",
|
||||
"logs": "ログ",
|
||||
"migrate": "移行する",
|
||||
"missing": "不足している",
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "실행 당 비용",
|
||||
"title": "API 노드(들)",
|
||||
"totalCost": "총 비용"
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"message": "이 워크플로우에는 API 노드가 포함되어 있으며, 실행하려면 계정에 로그인해야 합니다.",
|
||||
"title": "API 노드 사용에 필요한 로그인"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "클립보드에 복사하지 못했습니다",
|
||||
"errorNotSupported": "브라우저가 클립보드 API를 지원하지 않습니다.",
|
||||
@@ -172,9 +181,11 @@
|
||||
"installing": "설치 중",
|
||||
"interrupted": "중단됨",
|
||||
"keybinding": "키 바인딩",
|
||||
"learnMore": "더 알아보기",
|
||||
"loadAllFolders": "모든 폴더 로드",
|
||||
"loadWorkflow": "워크플로 로드",
|
||||
"loading": "로딩 중",
|
||||
"login": "로그인",
|
||||
"logs": "로그",
|
||||
"migrate": "이전(migrate)",
|
||||
"missing": "누락됨",
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "Стоимость за запуск",
|
||||
"title": "API Node(s)",
|
||||
"totalCost": "Общая стоимость"
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"message": "Этот рабочий процесс содержит API Nodes, которые требуют входа в вашу учетную запись для выполнения.",
|
||||
"title": "Требуется вход для использования API Nodes"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "Не удалось скопировать в буфер обмена",
|
||||
"errorNotSupported": "API буфера обмена не поддерживается в вашем браузере",
|
||||
@@ -172,9 +181,11 @@
|
||||
"installing": "Установка",
|
||||
"interrupted": "Прервано",
|
||||
"keybinding": "Привязка клавиш",
|
||||
"learnMore": "Узнать больше",
|
||||
"loadAllFolders": "Загрузить все папки",
|
||||
"loadWorkflow": "Загрузить рабочий процесс",
|
||||
"loading": "Загрузка",
|
||||
"login": "Вход",
|
||||
"logs": "Логи",
|
||||
"migrate": "Мигрировать",
|
||||
"missing": "Отсутствует",
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
{
|
||||
"apiNodesCostBreakdown": {
|
||||
"costPerRun": "每次运行的成本",
|
||||
"title": "API节点",
|
||||
"totalCost": "总成本"
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"message": "此工作流包含API节点,需要您登录账户才能运行。",
|
||||
"title": "使用API节点需要登录"
|
||||
},
|
||||
"clipboard": {
|
||||
"errorMessage": "复制到剪贴板失败",
|
||||
"errorNotSupported": "您的浏览器不支持剪贴板API",
|
||||
@@ -172,9 +181,11 @@
|
||||
"installing": "正在安装",
|
||||
"interrupted": "已中断",
|
||||
"keybinding": "按键绑定",
|
||||
"learnMore": "了解更多",
|
||||
"loadAllFolders": "加载所有文件夹",
|
||||
"loadWorkflow": "加载工作流",
|
||||
"loading": "加载中",
|
||||
"login": "登录",
|
||||
"logs": "日志",
|
||||
"migrate": "迁移",
|
||||
"missing": "缺失",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
|
||||
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
|
||||
import ErrorDialogContent from '@/components/dialog/content/ErrorDialogContent.vue'
|
||||
import IssueReportDialogContent from '@/components/dialog/content/IssueReportDialogContent.vue'
|
||||
@@ -9,6 +10,7 @@ import SettingDialogContent from '@/components/dialog/content/SettingDialogConte
|
||||
import ManagerDialogContent from '@/components/dialog/content/manager/ManagerDialogContent.vue'
|
||||
import ManagerHeader from '@/components/dialog/content/manager/ManagerHeader.vue'
|
||||
import ManagerProgressFooter from '@/components/dialog/footer/ManagerProgressFooter.vue'
|
||||
import ComfyOrgHeader from '@/components/dialog/header/ComfyOrgHeader.vue'
|
||||
import ManagerProgressHeader from '@/components/dialog/header/ManagerProgressHeader.vue'
|
||||
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
|
||||
import TemplateWorkflowsContent from '@/components/templates/TemplateWorkflowsContent.vue'
|
||||
@@ -16,6 +18,7 @@ import TemplateWorkflowsDialogHeader from '@/components/templates/TemplateWorkfl
|
||||
import { t } from '@/i18n'
|
||||
import type { ExecutionErrorWsMessage } from '@/schemas/apiSchema'
|
||||
import { type ShowDialogOptions, useDialogStore } from '@/stores/dialogStore'
|
||||
import { ApiNodeCost } from '@/types/apiNodeTypes'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
|
||||
export type ConfirmationDialogType =
|
||||
@@ -216,6 +219,34 @@ export const useDialogService = () => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog requiring sign in for API nodes
|
||||
* @returns Promise that resolves to true if user clicks login, false if cancelled
|
||||
*/
|
||||
async function showApiNodesSignInDialog(
|
||||
apiNodes: ApiNodeCost[]
|
||||
): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
dialogStore.showDialog({
|
||||
key: 'api-nodes-signin',
|
||||
component: ApiNodesSignInContent,
|
||||
props: {
|
||||
apiNodes,
|
||||
onLogin: () => resolve(true),
|
||||
onCancel: () => resolve(false)
|
||||
},
|
||||
headerComponent: ComfyOrgHeader,
|
||||
dialogComponentProps: {
|
||||
closable: false,
|
||||
onClose: () => resolve(false)
|
||||
}
|
||||
})
|
||||
}).then((result) => {
|
||||
dialogStore.closeDialog({ key: 'api-nodes-signin' })
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async function prompt({
|
||||
title,
|
||||
message,
|
||||
@@ -300,6 +331,7 @@ export const useDialogService = () => {
|
||||
showManagerDialog,
|
||||
showManagerProgressDialog,
|
||||
showErrorDialog,
|
||||
showApiNodesSignInDialog,
|
||||
prompt,
|
||||
confirm
|
||||
}
|
||||
|
||||
4
src/types/apiNodeTypes.ts
Normal file
4
src/types/apiNodeTypes.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface ApiNodeCost {
|
||||
name: string
|
||||
cost: number
|
||||
}
|
||||
Reference in New Issue
Block a user