[API Nodes] Signin/Signup dialog (#3466)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2025-04-15 17:37:53 -04:00
committed by GitHub
parent 60dd242b23
commit 9935b322f0
13 changed files with 790 additions and 1 deletions

View File

@@ -49,4 +49,6 @@ const additionalInstructions = `
7. Implement proper error handling
8. Follow Vue 3 style guide and naming conventions
9. Use Vite for fast development and building
10. Use vue-i18n in composition API for any string literals. Place new translation
entries in src/locales/en/main.json.
`;

View File

@@ -0,0 +1,118 @@
<template>
<div class="w-96 p-2">
<!-- Header -->
<div class="flex flex-col gap-4 mb-8">
<h1 class="text-2xl font-medium leading-normal my-0">
{{ isSignIn ? t('auth.login.title') : t('auth.signup.title') }}
</h1>
<p class="text-base my-0">
<span class="text-muted">{{
isSignIn
? t('auth.login.newUser')
: t('auth.signup.alreadyHaveAccount')
}}</span>
<span class="ml-1 cursor-pointer text-blue-500" @click="toggleState">{{
isSignIn ? t('auth.login.signUp') : t('auth.signup.signIn')
}}</span>
</p>
</div>
<!-- Form -->
<SignInForm v-if="isSignIn" @submit="signInWithEmail" />
<SignUpForm v-else @submit="signInWithEmail" />
<!-- Divider -->
<Divider align="center" layout="horizontal" class="my-8">
<span class="text-muted">{{ t('auth.login.orContinueWith') }}</span>
</Divider>
<!-- Social Login Buttons -->
<div class="flex flex-col gap-6">
<Button
type="button"
class="h-10"
severity="secondary"
outlined
@click="signInWithGoogle"
>
<i class="pi pi-google mr-2"></i>
{{
isSignIn
? t('auth.login.loginWithGoogle')
: t('auth.signup.signUpWithGoogle')
}}
</Button>
<Button
type="button"
class="h-10"
severity="secondary"
outlined
@click="signInWithGithub"
>
<i class="pi pi-github mr-2"></i>
{{
isSignIn
? t('auth.login.loginWithGithub')
: t('auth.signup.signUpWithGithub')
}}
</Button>
</div>
<!-- Terms -->
<p class="text-xs text-muted mt-8">
{{ t('auth.login.termsText') }}
<span class="text-blue-500 cursor-pointer">{{
t('auth.login.termsLink')
}}</span>
{{ t('auth.login.andText') }}
<span class="text-blue-500 cursor-pointer">{{
t('auth.login.privacyLink')
}}</span
>.
</p>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { SignInData, SignUpData } from '@/schemas/signInSchema'
import SignInForm from './signin/SignInForm.vue'
import SignUpForm from './signin/SignUpForm.vue'
const { t } = useI18n()
const { onSuccess } = defineProps<{
onSuccess: () => void
}>()
const isSignIn = ref(true)
const toggleState = () => {
isSignIn.value = !isSignIn.value
}
const signInWithGoogle = () => {
// Implement Google login
console.log(isSignIn.value)
console.log('Google login clicked')
onSuccess()
}
const signInWithGithub = () => {
// Implement Github login
console.log(isSignIn.value)
console.log('Github login clicked')
onSuccess()
}
const signInWithEmail = (values: SignInData | SignUpData) => {
// Implement email login
console.log(isSignIn.value)
console.log(values)
onSuccess()
}
</script>

View File

@@ -0,0 +1,89 @@
<template>
<Form
v-slot="$form"
class="flex flex-col gap-6"
:resolver="zodResolver(signInSchema)"
@submit="onSubmit"
>
<!-- Email Field -->
<div class="flex flex-col gap-2">
<label
class="opacity-80 text-base font-medium mb-2"
for="comfy-org-sign-in-email"
>
{{ t('auth.login.emailLabel') }}
</label>
<InputText
pt:root:id="comfy-org-sign-in-email"
pt:root:autocomplete="email"
class="h-10"
name="email"
type="text"
:placeholder="t('auth.login.emailPlaceholder')"
:invalid="$form.email?.invalid"
/>
<small v-if="$form.email?.invalid" class="text-red-500">{{
$form.email.error.message
}}</small>
</div>
<!-- Password Field -->
<div class="flex flex-col gap-2">
<div class="flex justify-between items-center mb-2">
<label
class="opacity-80 text-base font-medium"
for="comfy-org-sign-in-password"
>
{{ t('auth.login.passwordLabel') }}
</label>
<span class="text-muted text-base font-medium cursor-pointer">
{{ t('auth.login.forgotPassword') }}
</span>
</div>
<Password
input-id="comfy-org-sign-in-password"
pt:pc-input-text:root:autocomplete="current-password"
name="password"
:feedback="false"
toggle-mask
:placeholder="t('auth.login.passwordPlaceholder')"
:class="{ 'p-invalid': $form.password?.invalid }"
fluid
class="h-10"
/>
<small v-if="$form.password?.invalid" class="text-red-500">{{
$form.password.error.message
}}</small>
</div>
<!-- Submit Button -->
<Button
type="submit"
:label="t('auth.login.loginButton')"
class="h-10 font-medium mt-4"
/>
</Form>
</template>
<script setup lang="ts">
import { Form, FormSubmitEvent } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'
import { useI18n } from 'vue-i18n'
import { type SignInData, signInSchema } from '@/schemas/signInSchema'
const { t } = useI18n()
const emit = defineEmits<{
submit: [values: SignInData]
}>()
const onSubmit = (event: FormSubmitEvent) => {
if (event.valid) {
emit('submit', event.values as SignInData)
}
}
</script>

View File

@@ -0,0 +1,165 @@
<template>
<Form
v-slot="$form"
class="flex flex-col gap-6"
:resolver="zodResolver(signUpSchema)"
@submit="onSubmit"
>
<!-- Email Field -->
<div class="flex flex-col gap-2">
<label
class="opacity-80 text-base font-medium mb-2"
for="comfy-org-sign-up-email"
>
{{ t('auth.signup.emailLabel') }}
</label>
<InputText
pt:root:id="comfy-org-sign-up-email"
pt:root:autocomplete="email"
class="h-10"
name="email"
type="text"
:placeholder="t('auth.signup.emailPlaceholder')"
:invalid="$form.email?.invalid"
/>
<small v-if="$form.email?.invalid" class="text-red-500">{{
$form.email.error.message
}}</small>
</div>
<!-- Password Field -->
<div class="flex flex-col gap-2">
<div class="flex justify-between items-center mb-2">
<label
class="opacity-80 text-base font-medium"
for="comfy-org-sign-up-password"
>
{{ t('auth.signup.passwordLabel') }}
</label>
</div>
<Password
v-model="password"
input-id="comfy-org-sign-up-password"
pt:pc-input-text:root:autocomplete="new-password"
name="password"
:feedback="false"
toggle-mask
:placeholder="t('auth.signup.passwordPlaceholder')"
:class="{ 'p-invalid': $form.password?.invalid }"
fluid
class="h-10"
/>
<div class="flex flex-col gap-1">
<small
v-if="$form.password?.dirty || $form.password?.invalid"
class="text-sm"
>
{{ t('validation.password.requirements') }}:
<ul class="mt-1 space-y-1">
<li
:class="{
'text-red-500': !passwordChecks.length
}"
>
{{ t('validation.password.minLength') }}
</li>
<li
:class="{
'text-red-500': !passwordChecks.uppercase
}"
>
{{ t('validation.password.uppercase') }}
</li>
<li
:class="{
'text-red-500': !passwordChecks.lowercase
}"
>
{{ t('validation.password.lowercase') }}
</li>
<li
:class="{
'text-red-500': !passwordChecks.number
}"
>
{{ t('validation.password.number') }}
</li>
<li
:class="{
'text-red-500': !passwordChecks.special
}"
>
{{ t('validation.password.special') }}
</li>
</ul>
</small>
</div>
</div>
<!-- Confirm Password Field -->
<div class="flex flex-col gap-2">
<label
class="opacity-80 text-base font-medium mb-2"
for="comfy-org-sign-up-confirm-password"
>
{{ t('auth.login.confirmPasswordLabel') }}
</label>
<Password
name="confirmPassword"
input-id="comfy-org-sign-up-confirm-password"
pt:pc-input-text:root:autocomplete="new-password"
:feedback="false"
toggle-mask
:placeholder="t('auth.login.confirmPasswordPlaceholder')"
:class="{ 'p-invalid': $form.confirmPassword?.invalid }"
fluid
class="h-10"
/>
<small v-if="$form.confirmPassword?.error" class="text-red-500">{{
$form.confirmPassword.error.message
}}</small>
</div>
<!-- Submit Button -->
<Button
type="submit"
:label="t('auth.signup.signUpButton')"
class="h-10 font-medium mt-4"
/>
</Form>
</template>
<script setup lang="ts">
import { Form, FormSubmitEvent } from '@primevue/forms'
import { zodResolver } from '@primevue/forms/resolvers/zod'
import Button from 'primevue/button'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { type SignUpData, signUpSchema } from '@/schemas/signInSchema'
const { t } = useI18n()
const password = ref('')
// TODO: Use dynamic form to better organize the password checks.
// Ref: https://primevue.org/forms/#dynamic
const passwordChecks = computed(() => ({
length: password.value.length >= 8 && password.value.length <= 32,
uppercase: /[A-Z]/.test(password.value),
lowercase: /[a-z]/.test(password.value),
number: /\d/.test(password.value),
special: /[^A-Za-z0-9]/.test(password.value)
}))
const emit = defineEmits<{
submit: [values: SignUpData]
}>()
const onSubmit = (event: FormSubmitEvent) => {
if (event.valid) {
emit('submit', event.values as SignUpData)
}
}
</script>

View File

@@ -1051,5 +1051,56 @@
"noTemplatesToExport": "No templates to export",
"failedToFetchLogs": "Failed to fetch server logs",
"migrateToLitegraphReroute": "Reroute nodes will be removed in future versions. Click to migrate to litegraph-native reroute."
},
"auth": {
"login": {
"title": "Log in to your account",
"newUser": "New here?",
"signUp": "Sign up",
"emailLabel": "Email",
"emailPlaceholder": "Enter your email",
"passwordLabel": "Password",
"passwordPlaceholder": "Enter your password",
"confirmPasswordLabel": "Confirm Password",
"confirmPasswordPlaceholder": "Enter the same password again",
"forgotPassword": "Forgot password?",
"loginButton": "Log in",
"orContinueWith": "Or continue with",
"loginWithGoogle": "Log in with Google",
"loginWithGithub": "Log in with Github",
"termsText": "By clicking \"Next\" or \"Sign Up\", you agree to our",
"termsLink": "Terms of Use",
"andText": "and",
"privacyLink": "Privacy Policy",
"success": "Login successful",
"failed": "Login failed"
},
"signup": {
"title": "Create an account",
"alreadyHaveAccount": "Already have an account?",
"emailLabel": "Email",
"emailPlaceholder": "Enter your email",
"passwordLabel": "Password",
"passwordPlaceholder": "Enter new password",
"signUpButton": "Sign up",
"signIn": "Sign in",
"signUpWithGoogle": "Sign up with Google",
"signUpWithGithub": "Sign up with Github"
}
},
"validation": {
"invalidEmail": "Invalid email address",
"required": "Required",
"minLength": "Must be at least {length} characters",
"maxLength": "Must be no more than {length} characters",
"password": {
"requirements": "Password requirements",
"minLength": "Must be between 8 and 32 characters",
"uppercase": "Must contain at least one uppercase letter",
"lowercase": "Must contain at least one lowercase letter",
"number": "Must contain at least one number",
"special": "Must contain at least one special character",
"match": "Passwords must match"
}
}
}

View File

@@ -8,6 +8,42 @@
"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"
},
"auth": {
"login": {
"andText": "y",
"confirmPasswordLabel": "Confirmar contraseña",
"confirmPasswordPlaceholder": "Ingresa la misma contraseña nuevamente",
"emailLabel": "Correo electrónico",
"emailPlaceholder": "Ingresa tu correo electrónico",
"failed": "Inicio de sesión fallido",
"forgotPassword": "¿Olvidaste tu contraseña?",
"loginButton": "Iniciar sesión",
"loginWithGithub": "Iniciar sesión con Github",
"loginWithGoogle": "Iniciar sesión con Google",
"newUser": "¿Eres nuevo aquí?",
"orContinueWith": "O continuar con",
"passwordLabel": "Contraseña",
"passwordPlaceholder": "Ingresa tu contraseña",
"privacyLink": "Política de privacidad",
"signUp": "Regístrate",
"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"
},
"signup": {
"alreadyHaveAccount": "¿Ya tienes una cuenta?",
"emailLabel": "Correo electrónico",
"emailPlaceholder": "Ingresa tu correo electrónico",
"passwordLabel": "Contraseña",
"passwordPlaceholder": "Ingresa una nueva contraseña",
"signIn": "Iniciar sesión",
"signUpButton": "Registrarse",
"signUpWithGithub": "Registrarse con Github",
"signUpWithGoogle": "Registrarse con Google",
"title": "Crea una cuenta"
}
},
"clipboard": {
"errorMessage": "Error al copiar al portapapeles",
"errorNotSupported": "API del portapapeles no soportada en su navegador",
@@ -1043,6 +1079,21 @@
"next": "Siguiente",
"selectUser": "Selecciona un usuario"
},
"validation": {
"invalidEmail": "Dirección de correo electrónico inválida",
"maxLength": "No debe tener más de {length} caracteres",
"minLength": "Debe tener al menos {length} caracteres",
"password": {
"lowercase": "Debe contener al menos una letra minúscula",
"match": "Las contraseñas deben coincidir",
"minLength": "Debe tener entre 8 y 32 caracteres",
"number": "Debe contener al menos un número",
"requirements": "Requisitos de la contraseña",
"special": "Debe contener al menos un carácter especial",
"uppercase": "Debe contener al menos una letra mayúscula"
},
"required": "Requerido"
},
"welcome": {
"getStarted": "Empezar",
"title": "Bienvenido a ComfyUI"

View File

@@ -8,6 +8,42 @@
"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"
},
"auth": {
"login": {
"andText": "et",
"confirmPasswordLabel": "Confirmer le mot de passe",
"confirmPasswordPlaceholder": "Entrez à nouveau le même mot de passe",
"emailLabel": "Email",
"emailPlaceholder": "Entrez votre email",
"failed": "Échec de la connexion",
"forgotPassword": "Mot de passe oublié?",
"loginButton": "Se connecter",
"loginWithGithub": "Se connecter avec Github",
"loginWithGoogle": "Se connecter avec Google",
"newUser": "Nouveau ici?",
"orContinueWith": "Ou continuer avec",
"passwordLabel": "Mot de passe",
"passwordPlaceholder": "Entrez votre mot de passe",
"privacyLink": "Politique de confidentialité",
"signUp": "S'inscrire",
"success": "Connexion réussie",
"termsLink": "Conditions d'utilisation",
"termsText": "En cliquant sur \"Suivant\" ou \"S'inscrire\", vous acceptez nos",
"title": "Connectez-vous à votre compte"
},
"signup": {
"alreadyHaveAccount": "Vous avez déjà un compte?",
"emailLabel": "Email",
"emailPlaceholder": "Entrez votre email",
"passwordLabel": "Mot de passe",
"passwordPlaceholder": "Entrez un nouveau mot de passe",
"signIn": "Se connecter",
"signUpButton": "S'inscrire",
"signUpWithGithub": "S'inscrire avec Github",
"signUpWithGoogle": "S'inscrire avec Google",
"title": "Créer un compte"
}
},
"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",
@@ -1043,6 +1079,21 @@
"next": "Suivant",
"selectUser": "Sélectionnez un utilisateur"
},
"validation": {
"invalidEmail": "Adresse e-mail invalide",
"maxLength": "Ne doit pas dépasser {length} caractères",
"minLength": "Doit contenir au moins {length} caractères",
"password": {
"lowercase": "Doit contenir au moins une lettre minuscule",
"match": "Les mots de passe doivent correspondre",
"minLength": "Doit contenir entre 8 et 32 caractères",
"number": "Doit contenir au moins un chiffre",
"requirements": "Exigences du mot de passe",
"special": "Doit contenir au moins un caractère spécial",
"uppercase": "Doit contenir au moins une lettre majuscule"
},
"required": "Requis"
},
"welcome": {
"getStarted": "Commencer",
"title": "Bienvenue sur ComfyUI"

View File

@@ -8,6 +8,42 @@
"message": "このワークフローにはAPIードが含まれており、実行するためにはアカウントにサインインする必要があります。",
"title": "APIードを使用するためにはサインインが必要です"
},
"auth": {
"login": {
"andText": "および",
"confirmPasswordLabel": "パスワードの確認",
"confirmPasswordPlaceholder": "もう一度同じパスワードを入力してください",
"emailLabel": "メール",
"emailPlaceholder": "メールアドレスを入力してください",
"failed": "ログイン失敗",
"forgotPassword": "パスワードを忘れましたか?",
"loginButton": "ログイン",
"loginWithGithub": "Githubでログイン",
"loginWithGoogle": "Googleでログイン",
"newUser": "新規ユーザーですか?",
"orContinueWith": "または以下で続ける",
"passwordLabel": "パスワード",
"passwordPlaceholder": "パスワードを入力してください",
"privacyLink": "プライバシーポリシー",
"signUp": "サインアップ",
"success": "ログイン成功",
"termsLink": "利用規約",
"termsText": "「次へ」または「サインアップ」をクリックすると、私たちの",
"title": "アカウントにログインする"
},
"signup": {
"alreadyHaveAccount": "すでにアカウントをお持ちですか?",
"emailLabel": "メール",
"emailPlaceholder": "メールアドレスを入力してください",
"passwordLabel": "パスワード",
"passwordPlaceholder": "新しいパスワードを入力してください",
"signIn": "サインイン",
"signUpButton": "サインアップ",
"signUpWithGithub": "Githubでサインアップ",
"signUpWithGoogle": "Googleでサインアップ",
"title": "アカウントを作成する"
}
},
"clipboard": {
"errorMessage": "クリップボードへのコピーに失敗しました",
"errorNotSupported": "お使いのブラウザではクリップボードAPIがサポートされていません",
@@ -1043,6 +1079,21 @@
"next": "次へ",
"selectUser": "ユーザーを選択"
},
"validation": {
"invalidEmail": "無効なメールアドレス",
"maxLength": "{length}文字以下でなければなりません",
"minLength": "{length}文字以上でなければなりません",
"password": {
"lowercase": "少なくとも1つの小文字を含む必要があります",
"match": "パスワードが一致する必要があります",
"minLength": "8文字から32文字の間でなければなりません",
"number": "少なくとも1つの数字を含む必要があります",
"requirements": "パスワードの要件",
"special": "少なくとも1つの特殊文字を含む必要があります",
"uppercase": "少なくとも1つの大文字を含む必要があります"
},
"required": "必須"
},
"welcome": {
"getStarted": "はじめる",
"title": "ComfyUIへようこそ"

View File

@@ -8,6 +8,42 @@
"message": "이 워크플로우에는 API 노드가 포함되어 있으며, 실행하려면 계정에 로그인해야 합니다.",
"title": "API 노드 사용에 필요한 로그인"
},
"auth": {
"login": {
"andText": "및",
"confirmPasswordLabel": "비밀번호 확인",
"confirmPasswordPlaceholder": "동일한 비밀번호를 다시 입력하세요",
"emailLabel": "이메일",
"emailPlaceholder": "이메일을 입력하세요",
"failed": "로그인 실패",
"forgotPassword": "비밀번호를 잊으셨나요?",
"loginButton": "로그인",
"loginWithGithub": "Github로 로그인",
"loginWithGoogle": "구글로 로그인",
"newUser": "처음이신가요?",
"orContinueWith": "또는 다음으로 계속",
"passwordLabel": "비밀번호",
"passwordPlaceholder": "비밀번호를 입력하세요",
"privacyLink": "개인정보 보호정책",
"signUp": "가입하기",
"success": "로그인 성공",
"termsLink": "이용 약관",
"termsText": "\"다음\" 또는 \"가입하기\"를 클릭하면 우리의",
"title": "계정에 로그인"
},
"signup": {
"alreadyHaveAccount": "이미 계정이 있으신가요?",
"emailLabel": "이메일",
"emailPlaceholder": "이메일을 입력하세요",
"passwordLabel": "비밀번호",
"passwordPlaceholder": "새 비밀번호를 입력하세요",
"signIn": "로그인",
"signUpButton": "가입하기",
"signUpWithGithub": "Github로 가입하기",
"signUpWithGoogle": "구글로 가입하기",
"title": "계정 생성"
}
},
"clipboard": {
"errorMessage": "클립보드에 복사하지 못했습니다",
"errorNotSupported": "브라우저가 클립보드 API를 지원하지 않습니다.",
@@ -1043,6 +1079,21 @@
"next": "다음",
"selectUser": "사용자 선택"
},
"validation": {
"invalidEmail": "유효하지 않은 이메일 주소",
"maxLength": "{length}자를 초과할 수 없습니다",
"minLength": "{length}자 이상이어야 합니다",
"password": {
"lowercase": "적어도 하나의 소문자를 포함해야 합니다",
"match": "비밀번호가 일치해야 합니다",
"minLength": "8자에서 32자 사이여야 합니다",
"number": "적어도 하나의 숫자를 포함해야 합니다",
"requirements": "비밀번호 요구사항",
"special": "적어도 하나의 특수 문자를 포함해야 합니다",
"uppercase": "적어도 하나의 대문자를 포함해야 합니다"
},
"required": "필수"
},
"welcome": {
"getStarted": "시작하기",
"title": "ComfyUI에 오신 것을 환영합니다"

View File

@@ -8,6 +8,42 @@
"message": "Этот рабочий процесс содержит API Nodes, которые требуют входа в вашу учетную запись для выполнения.",
"title": "Требуется вход для использования API Nodes"
},
"auth": {
"login": {
"andText": "и",
"confirmPasswordLabel": "Подтвердите пароль",
"confirmPasswordPlaceholder": "Введите тот же пароль еще раз",
"emailLabel": "Электронная почта",
"emailPlaceholder": "Введите вашу электронную почту",
"failed": "Вход не удался",
"forgotPassword": "Забыли пароль?",
"loginButton": "Войти",
"loginWithGithub": "Войти через Github",
"loginWithGoogle": "Войти через Google",
"newUser": "Вы здесь впервые?",
"orContinueWith": "Или продолжить с",
"passwordLabel": "Пароль",
"passwordPlaceholder": "Введите ваш пароль",
"privacyLink": "Политикой конфиденциальности",
"signUp": "Зарегистрироваться",
"success": "Вход выполнен успешно",
"termsLink": "Условиями использования",
"termsText": "Нажимая \"Далее\" или \"Зарегистрироваться\", вы соглашаетесь с нашими",
"title": "Войдите в свой аккаунт"
},
"signup": {
"alreadyHaveAccount": "Уже есть аккаунт?",
"emailLabel": "Электронная почта",
"emailPlaceholder": "Введите вашу электронную почту",
"passwordLabel": "Пароль",
"passwordPlaceholder": "Введите новый пароль",
"signIn": "Войти",
"signUpButton": "Зарегистрироваться",
"signUpWithGithub": "Зарегистрироваться через Github",
"signUpWithGoogle": "Зарегистрироваться через Google",
"title": "Создать аккаунт"
}
},
"clipboard": {
"errorMessage": "Не удалось скопировать в буфер обмена",
"errorNotSupported": "API буфера обмена не поддерживается в вашем браузере",
@@ -1043,6 +1079,21 @@
"next": "Далее",
"selectUser": "Выберите пользователя"
},
"validation": {
"invalidEmail": "Недействительный адрес электронной почты",
"maxLength": "Должно быть не более {length} символов",
"minLength": "Должно быть не менее {length} символов",
"password": {
"lowercase": "Должен содержать хотя бы одну строчную букву",
"match": "Пароли должны совпадать",
"minLength": "Должно быть от 8 до 32 символов",
"number": "Должен содержать хотя бы одну цифру",
"requirements": "Требования к паролю",
"special": "Должен содержать хотя бы один специальный символ",
"uppercase": "Должен содержать хотя бы одну заглавную букву"
},
"required": "Обязательно"
},
"welcome": {
"getStarted": "Начать",
"title": "Добро пожаловать в ComfyUI"

View File

@@ -8,6 +8,42 @@
"message": "此工作流包含API节点需要您登录账户才能运行。",
"title": "使用API节点需要登录"
},
"auth": {
"login": {
"andText": "和",
"confirmPasswordLabel": "确认密码",
"confirmPasswordPlaceholder": "再次输入相同的密码",
"emailLabel": "电子邮件",
"emailPlaceholder": "输入您的电子邮件",
"failed": "登录失败",
"forgotPassword": "忘记密码?",
"loginButton": "登录",
"loginWithGithub": "使用Github登录",
"loginWithGoogle": "使用Google登录",
"newUser": "新来的?",
"orContinueWith": "或者继续使用",
"passwordLabel": "密码",
"passwordPlaceholder": "输入您的密码",
"privacyLink": "隐私政策",
"signUp": "注册",
"success": "登录成功",
"termsLink": "使用条款",
"termsText": "点击“下一步”或“注册”即表示您同意我们的",
"title": "登录您的账户"
},
"signup": {
"alreadyHaveAccount": "已经有账户了?",
"emailLabel": "电子邮件",
"emailPlaceholder": "输入您的电子邮件",
"passwordLabel": "密码",
"passwordPlaceholder": "输入新密码",
"signIn": "登录",
"signUpButton": "注册",
"signUpWithGithub": "使用Github注册",
"signUpWithGoogle": "使用Google注册",
"title": "创建一个账户"
}
},
"clipboard": {
"errorMessage": "复制到剪贴板失败",
"errorNotSupported": "您的浏览器不支持剪贴板API",
@@ -1043,6 +1079,21 @@
"next": "下一步",
"selectUser": "选择用户"
},
"validation": {
"invalidEmail": "无效的电子邮件地址",
"maxLength": "不能超过{length}个字符",
"minLength": "必须至少有{length}个字符",
"password": {
"lowercase": "必须包含至少一个小写字母",
"match": "密码必须匹配",
"minLength": "必须在8到32个字符之间",
"number": "必须包含至少一个数字",
"requirements": "密码要求",
"special": "必须包含至少一个特殊字符",
"uppercase": "必须包含至少一个大写字母"
},
"required": "必填"
},
"welcome": {
"getStarted": "开始使用",
"title": "欢迎使用 ComfyUI"

View File

@@ -0,0 +1,36 @@
import { z } from 'zod'
import { t } from '@/i18n'
export const signInSchema = z.object({
email: z
.string()
.email(t('validation.invalidEmail'))
.min(1, t('validation.required')),
password: z.string().min(1, t('validation.required'))
})
export type SignInData = z.infer<typeof signInSchema>
export const signUpSchema = z
.object({
email: z
.string()
.email(t('validation.invalidEmail'))
.min(1, t('validation.required')),
password: z
.string()
.min(8, t('validation.minLength', { length: 8 }))
.max(32, t('validation.maxLength', { length: 32 }))
.regex(/[A-Z]/, t('validation.password.uppercase'))
.regex(/[a-z]/, t('validation.password.lowercase'))
.regex(/\d/, t('validation.password.number'))
.regex(/[^A-Za-z0-9]/, t('validation.password.special')),
confirmPassword: z.string().min(1, t('validation.required'))
})
.refine((data) => data.password === data.confirmPassword, {
message: t('validation.password.match'),
path: ['confirmPassword']
})
export type SignUpData = z.infer<typeof signUpSchema>

View File

@@ -7,6 +7,7 @@ import ManagerProgressDialogContent from '@/components/dialog/content/ManagerPro
import MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue'
import PromptDialogContent from '@/components/dialog/content/PromptDialogContent.vue'
import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue'
import SignInContent from '@/components/dialog/content/SignInContent.vue'
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'
@@ -232,7 +233,7 @@ export const useDialogService = () => {
component: ApiNodesSignInContent,
props: {
apiNodes,
onLogin: () => resolve(true),
onLogin: () => showSignInDialog().then((result) => resolve(result)),
onCancel: () => resolve(false)
},
headerComponent: ComfyOrgHeader,
@@ -247,6 +248,26 @@ export const useDialogService = () => {
})
}
async function showSignInDialog(): Promise<boolean> {
return new Promise<boolean>((resolve) => {
dialogStore.showDialog({
key: 'global-signin',
component: SignInContent,
headerComponent: ComfyOrgHeader,
props: {
onSuccess: () => resolve(true)
},
dialogComponentProps: {
closable: false,
onClose: () => resolve(false)
}
})
}).then((result) => {
dialogStore.closeDialog({ key: 'global-signin' })
return result
})
}
async function prompt({
title,
message,
@@ -332,6 +353,7 @@ export const useDialogService = () => {
showManagerProgressDialog,
showErrorDialog,
showApiNodesSignInDialog,
showSignInDialog,
prompt,
confirm
}