[API Node] Sign in required dialog (#3457)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2025-04-14 17:49:17 -04:00
committed by GitHub
parent 1631665efb
commit 851739a768
14 changed files with 247 additions and 1 deletions

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View 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>

View 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>

View 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>

View File

@@ -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.",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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": "不足している",

View File

@@ -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": "누락됨",

View File

@@ -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": "Отсутствует",

View File

@@ -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": "缺失",

View File

@@ -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
}

View File

@@ -0,0 +1,4 @@
export interface ApiNodeCost {
name: string
cost: number
}