增加VoceSpace视频会议功能 (#275)
* vocespace video conf page [feat]: get and update vocespace config [feat]: get and update vocespace config [feat]: vocespace setting box in home page remove zip send vocespace link [feat]: vocespace license upgrade * docs(readme): add Chinese readme and update package mananger to pnpm * docs: Remove outdated contact information and completed feature requests (#272) * feat: 增加passkey支持 (#270) * chore: add .pnpm-store to .gitignore * feat: implement passkey authentication and management features - Added passkey login and registration endpoints to the auth service. - Introduced PasskeyManagement component for user passkey management. - Updated localization files to include passkey-related strings in English and Chinese. - Created utility functions for handling passkey operations in WebAuthn. - Adjusted login page to support passkey login flow. - Modified configuration for local development server. * feat: enhance passkey management with modal input and localization updates - Added a modal for entering passkey names during registration in the PasskeyManagement component. - Updated English and Chinese localization files to include new strings for passkey name input. - Improved user experience by validating passkey name input before registration. * feat: enhance passkey login experience with localization updates - Added new localization strings for passkey authentication errors and status messages in English and Chinese. - Updated the login page to utilize localized strings for improved user feedback during passkey login attempts. * feat: add name field to UserPasskey interface for enhanced user identification * feat: integrate passkey support across login and settings - Added passkey option to login configuration and updated related components to conditionally render passkey login button. - Enhanced MyAccount and Logins components to manage passkey settings and display passkey management options based on configuration. - Updated English and Chinese localization files to include new strings for passkey functionality. * feat: added server version check for passkey * chore: update version to 0.9.30 in package.json * chore: update version to 0.9.31 in package.json and add magic link authentication strings in English and Chinese * i18n [feat]: undeploy or vocespace.com [fix]: theme fit for ui [feat]: ip or alert version v0.9.32 [fix]: use version compare for feat [fix]: api hook inner useEffect --------- Co-authored-by: haorwen <haorwen@qq.com>
This commit is contained in:
@@ -24,3 +24,6 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
web.vocechat.*
|
||||
*.zip
|
||||
|
||||
Vendored
+2
-1
@@ -69,5 +69,6 @@
|
||||
"[svg]": {
|
||||
"editor.defaultFormatter": "jock.svg"
|
||||
},
|
||||
"i18n-ally.localesPaths": ["public/locales"]
|
||||
"i18n-ally.localesPaths": ["public/locales"],
|
||||
"i18n-ally.keystyle": "nested"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vocechat-web",
|
||||
"version": "0.9.31",
|
||||
"version": "0.9.34",
|
||||
"homepage": "https://voce.chat",
|
||||
"dependencies": {
|
||||
"@metamask/onboarding": "^1.0.1",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "VoceChat SDK-Anpassung",
|
||||
"title": "VoceSpace Private Deployment Video [499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
|
||||
@@ -22,7 +22,23 @@
|
||||
"license": "Lizenz",
|
||||
"about": "Über",
|
||||
"api_doc": "API-Dokumentation",
|
||||
"version": "Version"
|
||||
"version": "Version",
|
||||
"video": "Videoanrufe"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Selbstgehosteter Vocespace (Automatisierte Bereitstellung)",
|
||||
"prerequisite": [
|
||||
"Voraussetzungen",
|
||||
"Docker-Umgebung",
|
||||
"Die Ports 80, 443, 7880, 7881, 3008 und 3000 sind geöffnet",
|
||||
"Die Domain ist registriert",
|
||||
"Für Unternehmensanpassungen oder bei Fragen wenden Sie sich bitte an han@privoce.com oder fügen Sie uns auf WeChat hinzu: Privoce"
|
||||
],
|
||||
"sub_desc": "Jedes Vocespace-Konto bietet unbegrenzte Gesprächsminuten, 5 gleichzeitige Teilnehmer und eine kostenlose 4K-Auflösung. Es ist keine Kontoerstellung erforderlich. Befolgen Sie die unten stehenden Anweisungen, um Ihren Vocespace korrekt zu konfigurieren.",
|
||||
"domain_desc": "Diese Domain benötigt einen DNS-A-Eintrag, der auf die IP-Adresse des aktuellen Servers verweist, auf dem Sie VoceChat hosten.",
|
||||
"enable": "Aktivieren",
|
||||
"how_to": "Nach der Aktivierung legen Sie bitte eine benutzerdefinierte Domain fest. Klicken Sie auf „Speichern“, und das System aktiviert VoceSpace automatisch."
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Kanal verlassen",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "Mobile Apps herunterladen",
|
||||
"help": "Fragen? Besuche unser Hilfe-Center",
|
||||
"sign_in_tip": "Zum Senden anmelden",
|
||||
"vocespace": " VoceSpace Videoanruf",
|
||||
"license": "VoceSpace Upgrade-Lizenz",
|
||||
"onboarding": {
|
||||
"title": "VoceChat-Einrichtung",
|
||||
"welcome": "Willkommen bei VoceChat!",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "VoceChat SDK Customization and Integration",
|
||||
"title": "VoceSpace Private Deployment Video [499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"bot": "Bot & Webhook",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"vocespace": "Video",
|
||||
"smtp": "SMTP",
|
||||
"login_method": "Login Methods",
|
||||
"third_app": "Third-party Login",
|
||||
@@ -22,7 +23,8 @@
|
||||
"license": "License",
|
||||
"about": "About",
|
||||
"api_doc": "API Documentation",
|
||||
"version": "Version"
|
||||
"version": "Version",
|
||||
"video": "Video"
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Leave Channel",
|
||||
@@ -267,6 +269,21 @@
|
||||
"enable": "Enable",
|
||||
"how_to": "How to setup Agora?"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Use self-hosted Vocespace (Automated Deployment)",
|
||||
"prerequisite": [
|
||||
"Prerequisites",
|
||||
"Docker environment",
|
||||
"Ports 80, 443, 7880, 7881, 3008, 3000 are open",
|
||||
"The domain has been registered",
|
||||
"For enterprises customization or need help, please contact han@privoce.com or add WeChat: Privoce"
|
||||
],
|
||||
"sub_desc": "Each Vocespace account will enjoy no limit minutes, 5 person concurrent and 4k resolution for free. No account creation required. Follow the instruction below to configure your Vocespace correctly.",
|
||||
"domain_desc": "This domain needs to have a DNS A record pointing to the current server IP you are hosting VoceChat.",
|
||||
"enable": "Enable",
|
||||
"how_to": "After enabling, please set a custom domain then click save, and the system will automatically enable VoceSpace."
|
||||
},
|
||||
"third_app": {
|
||||
"key": "API Secure Key",
|
||||
"update": "Update Secret",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "Download Mobile apps",
|
||||
"help": "Got questions? Visit our help center",
|
||||
"sign_in_tip": "Please sign in to send message",
|
||||
"vocespace": "VoceSpace Video Call",
|
||||
"license": "VoceSpace Upgrade License",
|
||||
"onboarding": {
|
||||
"title": "VoceChat Setup",
|
||||
"welcome": "Welcome to your VoceChat!",
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "Personalización e Integración de VoceChat SDK",
|
||||
"title": "Vídeo de implementación privada de VoceSpace [499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
|
||||
@@ -22,7 +22,23 @@
|
||||
"license": "Licencia",
|
||||
"about": "Acerca de",
|
||||
"api_doc": "Documentación de API",
|
||||
"version": "Versión"
|
||||
"version": "Versión",
|
||||
"video": "Video"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Vocespace implementado de forma privada (implementación automática)",
|
||||
"sub_desc": "Cada cuenta de Vocespace disfrutará de llamadas ilimitadas gratuitas, 5 usuarios simultáneos y resolución 4K. No es necesario crear una cuenta. Siga las instrucciones a continuación para configurar correctamente su Vocespace.",
|
||||
"prerequisite": [
|
||||
"Prerrequisitos",
|
||||
"Entorno Docker",
|
||||
"Puertos 80, 443, 7880, 7881, 3008 y 3000 abiertos",
|
||||
"Nombre de dominio que resuelve a la dirección IP local (para usuarios en China continental, como Alibaba Cloud y Tencent Cloud, se requiere el registro ICP)",
|
||||
"Para personalización empresarial o asistencia, contacte con han@privoce.com o agregue WeChat: Privoce"
|
||||
],
|
||||
"domain_desc": "Los nombres de dominio personalizados deben resolver a la dirección IP local (para usuarios en China continental, como Alibaba Cloud y Tencent Cloud, se requiere el registro ICP)",
|
||||
"enable": "Habilitar",
|
||||
"how_to": "Después de habilitar, configure el nombre de dominio y guarde. Los pasos restantes se completarán automáticamente."
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Abandonar Canal",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "Descarga las aplicaciones móviles",
|
||||
"help": "¿Tienes preguntas? Visita nuestro centro de ayuda",
|
||||
"sign_in_tip": "Por favor, inicia sesión para enviar mensajes",
|
||||
"vocespace": "Llamada de video VoceSpace",
|
||||
"license": "VoceSpace Licencia de Actualización",
|
||||
"onboarding": {
|
||||
"title": "Configuración de VoceChat",
|
||||
"welcome": "¡Bienvenido a tu VoceChat!",
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
{
|
||||
"chat": "Chat",
|
||||
"members": "Membres",
|
||||
"favs": "Éléments Enregistrés",
|
||||
"files": "Fichiers",
|
||||
"setting": "Paramètres",
|
||||
"more": "Plus",
|
||||
"action": {
|
||||
"login": "Se Connecter",
|
||||
"re_login": "Déconnexion",
|
||||
"logout": "Se Déconnecter",
|
||||
"change_avatar": "Changer",
|
||||
"cancel": "Annuler",
|
||||
"dismiss": "Rejeter",
|
||||
"search": "Rechercher",
|
||||
"search_user": "Rechercher par nom",
|
||||
"new_channel": "Nouveau Canal",
|
||||
"new_private_channel": "Nouveau Canal Privé",
|
||||
"new_msg": "Nouveau Message",
|
||||
"invite_people": "Inviter des Personnes",
|
||||
"search_people": "Rechercher des Personnes",
|
||||
"create": "Créer",
|
||||
"open": "Ouvrir",
|
||||
"update": "Mettre à Jour",
|
||||
"upload": "Téléverser",
|
||||
"done": "Fait",
|
||||
"add": "Ajouter",
|
||||
"yes": "Oui",
|
||||
"delete_channel": "Supprimer le Canal",
|
||||
"remove": "Retirer",
|
||||
"remove_user": "Retirer l'Utilisateur",
|
||||
"edit": "Modifier",
|
||||
"edit_msg": "Modifier le Message",
|
||||
"add_reaction": "Ajouter une Réaction",
|
||||
"add_to_fav": "Ajouter aux Favoris",
|
||||
"select": "Sélectionner",
|
||||
"forward": "Transférer",
|
||||
"reply": "Répondre",
|
||||
"pin": "Épingler",
|
||||
"unpin": "Désépingler",
|
||||
"send": "Envoyer",
|
||||
"send_msg": "Message",
|
||||
"copy": "Copier",
|
||||
"copy_email": "Copier l'Email",
|
||||
"mute": "Couper le son",
|
||||
"unmute": "Réactiver le son",
|
||||
"mark_read": "Marquer comme Lu",
|
||||
"hide_session": "Masquer la Session",
|
||||
"reload": "Recharger",
|
||||
"install": "Installer",
|
||||
"download_origin": "Télécharger l'original",
|
||||
"close": "Fermer"
|
||||
},
|
||||
"status": {
|
||||
"uploading": "Téléversement"
|
||||
},
|
||||
"tip": {
|
||||
"update": "Mise à Jour Réussie !",
|
||||
"delete": "Suppression Réussie !",
|
||||
"reg": "Inscription Réussie !",
|
||||
"login": "Connexion Réussie !",
|
||||
"logout": "Déconnexion Réussie !",
|
||||
"pin": "Message Épinglé avec Succès !",
|
||||
"pwa_install_title": "Installer l'application web sur le bureau ?",
|
||||
"pwa_install_desc": "Ajoutez au bureau pour un accès rapide à cette application."
|
||||
},
|
||||
"new_version": "<0>Nouvelle Version</0> Disponible",
|
||||
"mobile_app": "Découvrez notre <0>Application Mobile</0>",
|
||||
"price": {
|
||||
"pro": {
|
||||
"title": "VoceChat Pro",
|
||||
"desc": "",
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "Personnalisation et Intégration de VoceChat SDK",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
},
|
||||
"server_update": {
|
||||
"version_needed": "Cette fonction nécessite la version serveur :<0>{{version}}</0> au minimum 🚨",
|
||||
"current_version": "Votre version actuelle :<0>{{version}}</0>",
|
||||
"update_tip": "Veuillez mettre à niveau le Serveur !",
|
||||
"howto": "Comment Mettre à Jour le Serveur VoceChat"
|
||||
},
|
||||
"inactive": {
|
||||
"title": "Un seul onglet actif est autorisé pour VoceChat",
|
||||
"desc": "Veuillez recharger cette page pour continuer à utiliser cet onglet ou le fermer"
|
||||
}
|
||||
}
|
||||
{
|
||||
"chat": "Chat",
|
||||
"members": "Membres",
|
||||
"favs": "Éléments Enregistrés",
|
||||
"files": "Fichiers",
|
||||
"setting": "Paramètres",
|
||||
"more": "Plus",
|
||||
"action": {
|
||||
"login": "Se Connecter",
|
||||
"re_login": "Déconnexion",
|
||||
"logout": "Se Déconnecter",
|
||||
"change_avatar": "Changer",
|
||||
"cancel": "Annuler",
|
||||
"dismiss": "Rejeter",
|
||||
"search": "Rechercher",
|
||||
"search_user": "Rechercher par nom",
|
||||
"new_channel": "Nouveau Canal",
|
||||
"new_private_channel": "Nouveau Canal Privé",
|
||||
"new_msg": "Nouveau Message",
|
||||
"invite_people": "Inviter des Personnes",
|
||||
"search_people": "Rechercher des Personnes",
|
||||
"create": "Créer",
|
||||
"open": "Ouvrir",
|
||||
"update": "Mettre à Jour",
|
||||
"upload": "Téléverser",
|
||||
"done": "Fait",
|
||||
"add": "Ajouter",
|
||||
"yes": "Oui",
|
||||
"delete_channel": "Supprimer le Canal",
|
||||
"remove": "Retirer",
|
||||
"remove_user": "Retirer l'Utilisateur",
|
||||
"edit": "Modifier",
|
||||
"edit_msg": "Modifier le Message",
|
||||
"add_reaction": "Ajouter une Réaction",
|
||||
"add_to_fav": "Ajouter aux Favoris",
|
||||
"select": "Sélectionner",
|
||||
"forward": "Transférer",
|
||||
"reply": "Répondre",
|
||||
"pin": "Épingler",
|
||||
"unpin": "Désépingler",
|
||||
"send": "Envoyer",
|
||||
"send_msg": "Message",
|
||||
"copy": "Copier",
|
||||
"copy_email": "Copier l'Email",
|
||||
"mute": "Couper le son",
|
||||
"unmute": "Réactiver le son",
|
||||
"mark_read": "Marquer comme Lu",
|
||||
"hide_session": "Masquer la Session",
|
||||
"reload": "Recharger",
|
||||
"install": "Installer",
|
||||
"download_origin": "Télécharger l'original",
|
||||
"close": "Fermer"
|
||||
},
|
||||
"status": {
|
||||
"uploading": "Téléversement"
|
||||
},
|
||||
"tip": {
|
||||
"update": "Mise à Jour Réussie !",
|
||||
"delete": "Suppression Réussie !",
|
||||
"reg": "Inscription Réussie !",
|
||||
"login": "Connexion Réussie !",
|
||||
"logout": "Déconnexion Réussie !",
|
||||
"pin": "Message Épinglé avec Succès !",
|
||||
"pwa_install_title": "Installer l'application web sur le bureau ?",
|
||||
"pwa_install_desc": "Ajoutez au bureau pour un accès rapide à cette application."
|
||||
},
|
||||
"new_version": "<0>Nouvelle Version</0> Disponible",
|
||||
"mobile_app": "Découvrez notre <0>Application Mobile</0>",
|
||||
"price": {
|
||||
"pro": {
|
||||
"title": "VoceChat Pro",
|
||||
"desc": "",
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "Vidéo de déploiement privé VoceSpace [499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
},
|
||||
"server_update": {
|
||||
"version_needed": "Cette fonction nécessite la version serveur :<0>{{version}}</0> au minimum 🚨",
|
||||
"current_version": "Votre version actuelle :<0>{{version}}</0>",
|
||||
"update_tip": "Veuillez mettre à niveau le Serveur !",
|
||||
"howto": "Comment Mettre à Jour le Serveur VoceChat"
|
||||
},
|
||||
"inactive": {
|
||||
"title": "Un seul onglet actif est autorisé pour VoceChat",
|
||||
"desc": "Veuillez recharger cette page pour continuer à utiliser cet onglet ou le fermer"
|
||||
}
|
||||
}
|
||||
|
||||
+298
-282
@@ -1,282 +1,298 @@
|
||||
{
|
||||
"title": "Paramètres",
|
||||
"save_change": "Sauvegarder les Modifications",
|
||||
"reset": "Réinitialiser",
|
||||
"save_tip": "Vous avez des modifications non enregistrées !",
|
||||
"nav": {
|
||||
"general": "Général",
|
||||
"overview": "Aperçu",
|
||||
"members": "Membres",
|
||||
"data_management": "Gestion des Données",
|
||||
"auto_delete_msg": "Auto-suppression des Messages",
|
||||
"user": "Utilisateur",
|
||||
"my_account": "Mon Compte",
|
||||
"config": "Configuration",
|
||||
"bot": "Bot & Webhook",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
"login_method": "Méthodes de Connexion",
|
||||
"third_app": "Connexion Tiers",
|
||||
"widget": "Widget",
|
||||
"license": "Licence",
|
||||
"about": "À Propos",
|
||||
"api_doc": "Documentation API",
|
||||
"version": "Version"
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Quitter le Canal",
|
||||
"leave_desc": "Êtes-vous sûr de vouloir quitter ce canal ?",
|
||||
"transfer_desc": "Vous devez transférer la propriété de votre canal à quelqu'un d'autre avant de quitter le canal.",
|
||||
"delete": "Supprimer le Canal",
|
||||
"delete_desc": "Êtes-vous sûr de vouloir supprimer ce canal ?",
|
||||
"id": "ID du Canal",
|
||||
"name": "Nom du Canal",
|
||||
"topic": "Sujet du Canal",
|
||||
"topic_placeholder": "Faites savoir à tout le monde comment utiliser ce canal.",
|
||||
"visibility": "Visibilité du Canal",
|
||||
"public": "Public",
|
||||
"private": "Privé"
|
||||
},
|
||||
"dm": {
|
||||
"delete": "Supprimer l'Utilisateur",
|
||||
"delete_desc": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?"
|
||||
},
|
||||
"overview": {
|
||||
"title_feat": "Fonctionnalités",
|
||||
"name": "Nom du Serveur",
|
||||
"desc": "Description du Serveur",
|
||||
"upload_desc": "La taille minimale est de 128x128, nous recommandons au moins 512x512 pour le serveur. Taille maximale limitée à 5 Mo.",
|
||||
"sign_up": {
|
||||
"title": "Inscription par Défaut",
|
||||
"desc": "Qui peut s'inscrire sur ce serveur.",
|
||||
"everyone": "Tout le monde",
|
||||
"invite": "Lien d'Invitation Seulement"
|
||||
},
|
||||
"guest_mode": {
|
||||
"title": "Mode Invité",
|
||||
"desc": "S'il est activé, les visiteurs verront les canaux publics sur ce serveur.",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactivé"
|
||||
},
|
||||
"url": {
|
||||
"title": "Mettre à Jour l'URL de Frontend",
|
||||
"desc": "Mettre à jour l'URL de frontend"
|
||||
},
|
||||
"lang": {
|
||||
"title": "Langues",
|
||||
"desc": "Définir la langue du système"
|
||||
},
|
||||
"theme": {
|
||||
"title": "Thème",
|
||||
"desc": "Choisissez votre thème",
|
||||
"auto": "Automatique",
|
||||
"dark": "Sombre",
|
||||
"light": "Clair"
|
||||
},
|
||||
"message_sound": {
|
||||
"title": "Son des Messages",
|
||||
"desc": "Jouez un son lors de la réception d'un nouveau message.",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver"
|
||||
},
|
||||
"online_status": {
|
||||
"title": "Statut En Ligne",
|
||||
"desc": "S'il est activé, les membres verront le statut en ligne/hors ligne des autres.",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver"
|
||||
},
|
||||
"chat_layout": {
|
||||
"title": "Mise en Page du Chat",
|
||||
"desc": "Quel côté pour afficher les messages envoyés par vous-même",
|
||||
"left": "Gauche",
|
||||
"self_right": "Droite"
|
||||
},
|
||||
"contact_verify": {
|
||||
"title": "Vérification des Contacts",
|
||||
"desc": "Par défaut, tous les membres du serveur peuvent se voir. Si vous souhaitez que les membres du serveur ajoutent et gèrent leurs propres contacts, veuillez activer la fonctionnalité de contact (l'administrateur sera dans la liste des contacts par défaut).",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"clear_files": {
|
||||
"title": "Fichiers et Données d'Images",
|
||||
"desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de fichiers et d'images de ce serveur.",
|
||||
"btn": "Effacer les Données de Fichiers"
|
||||
},
|
||||
"clear_msgs": {
|
||||
"title": "Toutes les Données de Messages",
|
||||
"desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de chat, y compris les textes et les fichiers de ce serveur.",
|
||||
"btn": "Effacer les Données de Chats"
|
||||
},
|
||||
"auto_delete_file": {
|
||||
"title": "Auto-suppression des Données de Fichiers",
|
||||
"desc": "Définir le temps d'expiration pour toutes les données de fichiers de ce serveur.",
|
||||
"confirm_desc": "Tous les fichiers plus anciens que le temps défini seront supprimés.",
|
||||
"off": "Désactivé",
|
||||
"day1": "1 Jour",
|
||||
"day7": "7 Jours",
|
||||
"day30": "30 Jours",
|
||||
"day90": "90 Jours",
|
||||
"day180": "180 Jours"
|
||||
}
|
||||
},
|
||||
"license": {
|
||||
"signed": "Signé",
|
||||
"domain": "Domaines",
|
||||
"user_limit": "Limite d'Utilisateur",
|
||||
"expire": "Expiré À",
|
||||
"create": "Créé À",
|
||||
"value": "Valeur de la Licence",
|
||||
"renew": "Renouveler la Licence",
|
||||
"update": "Mettre à Jour Manuellement",
|
||||
"update_placeholder": "Veuillez saisir la nouvelle valeur de la licence",
|
||||
"renew_select": "Veuillez sélectionner le prix",
|
||||
"tip": {
|
||||
"title": "Une chance d'obtenir une mise à niveau de licence gratuite !",
|
||||
"user_test": "Obtenez une mise à niveau de licence gratuite grâce à un test utilisateur de 25 minutes",
|
||||
"contact": "Réservez un créneau ici : "
|
||||
},
|
||||
"payment_success": "Paiement Réussi !",
|
||||
"back_home": "Retour à l'Accueil",
|
||||
"tip_renewing": "Renouvellement de la Licence, ne fermez pas la fenêtre !",
|
||||
"tip_renewed": "Licence Renouvelée avec Succès !",
|
||||
"tip_renew_error": "ID de Session Stripe Invalidé",
|
||||
"tip_domain": "La licence est liée au domaine (sans port), veuillez confirmer ou mettre à jour vos paramètres :",
|
||||
"tip_port": "Remplissez votre nom de domaine sans port.",
|
||||
"tip_confirm": "Commencer le Paiement"
|
||||
},
|
||||
"bot": {
|
||||
"add_api_key": "Ajouter une Clé API",
|
||||
"no_api_key": "Aucune Clé API pour l'instant",
|
||||
"bot_tip_title": "Les bots peuvent envoyer des messages aux canaux.",
|
||||
"bot_tip_desc": "Veuillez noter que nous ne montrons pas la clé API d'un Bot à nouveau après sa génération. Vous pouvez toujours générer une nouvelle clé API pour un Bot en cliquant sur \"Ajouter une Clé API\" et cela n'annulera pas les clés API précédentes.",
|
||||
"webhook_tip_title": "Les Webhooks peuvent recevoir des messages dans un canal et devraient avoir une URL fournie par vous.",
|
||||
"webhook_tip_desc": "Veuillez fournir une URL de webhook qui retournera un code 200.",
|
||||
"manage": "Gérer",
|
||||
"how_to_use": "Comment Utiliser",
|
||||
"manage_desc": "Vous pouvez supprimer un bot à tout moment, l'utilisateur bot sera immédiatement supprimé et toutes les API seront annulées.",
|
||||
"create_title": "Créer un Bot",
|
||||
"create_desc": "Donnez un nom à votre bot. Vous pouvez également ajouter une URL de webhook d’endpoint pour recevoir des données.",
|
||||
"create_key_title": "Créer une Clé API pour le Bot",
|
||||
"create_key_desc": "Créer une Clé API pour authentifier votre communication",
|
||||
"create_key_warning": "Assurez-vous de copier la Clé API maintenant. Vous ne pourrez pas la voir à nouveau !",
|
||||
"key_copy_and_close": "Copier et Fermer",
|
||||
"delete_title": "Supprimer le Bot",
|
||||
"delete_desc": "Êtes-vous sûr de vouloir supprimer le Bot ?",
|
||||
"delete_key_title": "Supprimer la Clé API du Bot",
|
||||
"delete_key_desc": "Êtes-vous sûr de vouloir supprimer cette Clé API de Bot ?",
|
||||
"webhook_title": "Mettre à Jour l'URL du Webhook",
|
||||
"webhook_desc": "Veuillez saisir une URL valide",
|
||||
"col_avatar": "Avatar",
|
||||
"col_name": "Nom du Bot",
|
||||
"col_api_key": "Clé API (Messages Entrants)",
|
||||
"col_webhook": "URL du Webhook d'Endpoint (Sortant)",
|
||||
"col_opt": "Opérations",
|
||||
"col_key_name": "Nom",
|
||||
"col_key_value": "Clé",
|
||||
"col_key_create_time": "Créé À",
|
||||
"col_key_last_used": "Dernière Utilisation"
|
||||
},
|
||||
"auto_delete_msg": {
|
||||
"title": "Supprimer les messages envoyés par moi-même après une certaine période.",
|
||||
"desc": "Cela supprimera les données de messages stockées à la fois sur le serveur et du côté client.",
|
||||
"off": "Désactivé",
|
||||
"5_min": "5 minutes",
|
||||
"10_min": "10 minutes",
|
||||
"1_hour": "1 heure",
|
||||
"1_day": "1 jour",
|
||||
"1_week": "1 semaine",
|
||||
"clear": "Effacer tous les messages du canal",
|
||||
"clear_title": "Effacer tous les messages dans ce canal.",
|
||||
"clear_desc": "Cela supprimera toutes les données de messages stockées à la fois sur le serveur et du côté client."
|
||||
},
|
||||
"firebase": {
|
||||
"disable": "Désactiver",
|
||||
"use_official": "Utiliser la Configuration Officielle",
|
||||
"custom": "Personnalisé",
|
||||
"enable": "Activer",
|
||||
"token_url": "URL du Token",
|
||||
"project_id": "ID du Projet",
|
||||
"private_key": "Clé Privée",
|
||||
"client_email": "Email du Client"
|
||||
},
|
||||
"smtp": {
|
||||
"enable": "Activer",
|
||||
"host": "Hôte",
|
||||
"port": "Port",
|
||||
"from": "De",
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de Passe",
|
||||
"how_to": "Comment configurer SMTP ?",
|
||||
"send_test_email": "Envoyer un Email de Test"
|
||||
},
|
||||
"agora": {
|
||||
"enable": "Activer",
|
||||
"how_to": "Comment configurer Agora ?"
|
||||
},
|
||||
"third_app": {
|
||||
"key": "Clé Sécurisée API",
|
||||
"update": "Mettre à Jour le Secret",
|
||||
"update_tip": "Êtes-vous sûr de vouloir mettre à jour le secret API ? Le secret précédent sera invalidé",
|
||||
"key_tip": "La clé de sécurité convenue entre le serveur et l'application tierce est utilisée pour chiffrer les données de communication.",
|
||||
"how_to": "Comment utiliser ?"
|
||||
},
|
||||
"login": {
|
||||
"password": "Mot de Passe",
|
||||
"password_desc": "Permet aux membres de se connecter avec un mot de passe.",
|
||||
"magic_link": "Lien Magique",
|
||||
"magic_link_desc": "Permet aux membres de se connecter avec un Lien Magique.",
|
||||
"google": "Google",
|
||||
"google_desc": "Permet aux membres de se connecter avec Google.",
|
||||
"github": "GitHub",
|
||||
"github_desc": "Permet aux membres de se connecter avec GitHub.",
|
||||
"metamask": "Metamask",
|
||||
"metamask_desc": "Permet aux membres de se connecter avec Metamask.",
|
||||
"oidc": "OIDC",
|
||||
"oidc_desc": "Permet aux membres de se connecter avec OIDC.",
|
||||
"oidc_custom": "Personnalisé",
|
||||
"more_details": "Besoin de plus de détails ? Consultez notre <0>documentation</0>."
|
||||
},
|
||||
"widget": {
|
||||
"tip": "Étendre VoceChat en intégrant le widget SDK VoceChat !",
|
||||
"code": "Exemple de Code",
|
||||
"code_comment": "mettez ce fragment de code dans votre fichier html",
|
||||
"config": "Description de la Configuration",
|
||||
"param_key": "Clé de Paramètre",
|
||||
"default_value": "Valeur par Défaut",
|
||||
"remark": "Remarques",
|
||||
"custom_style_tip": "si vous souhaitez personnaliser plus précisément la position du widget, utilisez l'ID de l'élément du widget dans votre fichier HTML ou CSS, comme ceci",
|
||||
"param_id": "ID du Widget (version serveur >= v0.3.12)",
|
||||
"param_host": "Affecter l'utilisateur discutant avec le visiteur (ID Utilisateur)",
|
||||
"param_title": "titre de la fenêtre de chat popup",
|
||||
"param_logo": "URL de l'icône du logo de la fenêtre de chat popup",
|
||||
"param_auto_reg": "true : Enregistrer automatiquement un nouvel utilisateur s'il n'existe pas, false : l'utilisateur doit saisir son email et son nom pour s'inscrire",
|
||||
"param_login_token": "Si vous utilisez les informations existantes d'utilisateur de votre propre plateforme pour générer un nouveau compte VoceChat, ceci est le token de connexion généré par votre plateforme, pour plus de détails, veuillez vous référer à : https://doc.voce.chat/zh-cn/login-with-other-account. Ne remplissez pas ceci pour des scénarios par défaut (aucun système d'utilisateurs tiers) !",
|
||||
"param_theme_color": "La couleur de thème du widget",
|
||||
"param_close_width": "La largeur lorsque le widget est fermé",
|
||||
"param_close_height": "La hauteur lorsque le widget est fermé",
|
||||
"param_open_width": "La largeur lorsque le widget est ouvert",
|
||||
"param_open_height": "La hauteur lorsque le widget est ouvert",
|
||||
"param_welcome": "Message de bienvenue personnalisé",
|
||||
"param_position": "Position du widget : gauche/droite",
|
||||
"share_link": "Partagez le lien du widget afin que les gens puissent discuter avec vous directement",
|
||||
"widget_faq": "Activer Laisser Tout le Monde Rejoindre en Premier"
|
||||
},
|
||||
"version": {
|
||||
"client_version": "Version Client",
|
||||
"server_version": "Version Serveur",
|
||||
"build_time": "Horodatage de Construction",
|
||||
"sync": "Synchroniser avec la dernière version du client web"
|
||||
},
|
||||
"api_doc": {
|
||||
"desc": "Nous avons une documentation API détaillée sur laquelle vous pouvez baser votre propre outil de chat.",
|
||||
"access": "URL d'accès :",
|
||||
"use_method": "Méthode d'Utilisation :",
|
||||
"step_1": "Étape Une : Changez pour votre propre Serveur",
|
||||
"step_2": "Étape Deux : Entrez le token de connexion",
|
||||
"step_2_desc": "Vous pouvez obtenir le token en accédant à l'en-tête http X-API-Key ou en copiant simplement le texte ci-dessous :",
|
||||
"last": "Chaque API a un bouton 'Essayez-le', et vous pouvez déboguer l'API avec cela"
|
||||
}
|
||||
}
|
||||
{
|
||||
"title": "Paramètres",
|
||||
"save_change": "Sauvegarder les Modifications",
|
||||
"reset": "Réinitialiser",
|
||||
"save_tip": "Vous avez des modifications non enregistrées !",
|
||||
"nav": {
|
||||
"general": "Général",
|
||||
"overview": "Aperçu",
|
||||
"members": "Membres",
|
||||
"data_management": "Gestion des Données",
|
||||
"auto_delete_msg": "Auto-suppression des Messages",
|
||||
"user": "Utilisateur",
|
||||
"my_account": "Mon Compte",
|
||||
"config": "Configuration",
|
||||
"bot": "Bot & Webhook",
|
||||
"firebase": "Firebase",
|
||||
"agora": "Agora",
|
||||
"smtp": "SMTP",
|
||||
"login_method": "Méthodes de Connexion",
|
||||
"third_app": "Connexion Tiers",
|
||||
"widget": "Widget",
|
||||
"license": "Licence",
|
||||
"about": "À Propos",
|
||||
"api_doc": "Documentation API",
|
||||
"version": "Version",
|
||||
"video": "Vidéo"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Vocespace déployé en mode privé (déploiement automatique)",
|
||||
"sub_desc": "Chaque compte Vocespace bénéficie d'appels illimités gratuits, de 5 utilisateurs simultanés et d'une résolution 4K. Aucune création de compte n'est requise. Suivez les instructions ci-dessous pour configurer correctement votre Vocespace.",
|
||||
"prerequisite": [
|
||||
"Prérequis",
|
||||
"Environnement Docker",
|
||||
"Les ports 80, 443, 7880, 7881, 3008 et 3000 sont ouverts",
|
||||
"Nom de domaine pointant vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)",
|
||||
"Pour toute personnalisation ou assistance en entreprise, veuillez contacter han@privoce.com ou ajouter Privoce sur WeChat"
|
||||
],
|
||||
"domain_desc": "Les noms de domaine personnalisés doivent pointer vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)",
|
||||
"enable": "Activer",
|
||||
"how_to": "Après l'activation, veuillez définir le nom de domaine et enregistrer. Les étapes suivantes seront effectuées automatiquement."
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Quitter le Canal",
|
||||
"leave_desc": "Êtes-vous sûr de vouloir quitter ce canal ?",
|
||||
"transfer_desc": "Vous devez transférer la propriété de votre canal à quelqu'un d'autre avant de quitter le canal.",
|
||||
"delete": "Supprimer le Canal",
|
||||
"delete_desc": "Êtes-vous sûr de vouloir supprimer ce canal ?",
|
||||
"id": "ID du Canal",
|
||||
"name": "Nom du Canal",
|
||||
"topic": "Sujet du Canal",
|
||||
"topic_placeholder": "Faites savoir à tout le monde comment utiliser ce canal.",
|
||||
"visibility": "Visibilité du Canal",
|
||||
"public": "Public",
|
||||
"private": "Privé"
|
||||
},
|
||||
"dm": {
|
||||
"delete": "Supprimer l'Utilisateur",
|
||||
"delete_desc": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?"
|
||||
},
|
||||
"overview": {
|
||||
"title_feat": "Fonctionnalités",
|
||||
"name": "Nom du Serveur",
|
||||
"desc": "Description du Serveur",
|
||||
"upload_desc": "La taille minimale est de 128x128, nous recommandons au moins 512x512 pour le serveur. Taille maximale limitée à 5 Mo.",
|
||||
"sign_up": {
|
||||
"title": "Inscription par Défaut",
|
||||
"desc": "Qui peut s'inscrire sur ce serveur.",
|
||||
"everyone": "Tout le monde",
|
||||
"invite": "Lien d'Invitation Seulement"
|
||||
},
|
||||
"guest_mode": {
|
||||
"title": "Mode Invité",
|
||||
"desc": "S'il est activé, les visiteurs verront les canaux publics sur ce serveur.",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactivé"
|
||||
},
|
||||
"url": {
|
||||
"title": "Mettre à Jour l'URL de Frontend",
|
||||
"desc": "Mettre à jour l'URL de frontend"
|
||||
},
|
||||
"lang": {
|
||||
"title": "Langues",
|
||||
"desc": "Définir la langue du système"
|
||||
},
|
||||
"theme": {
|
||||
"title": "Thème",
|
||||
"desc": "Choisissez votre thème",
|
||||
"auto": "Automatique",
|
||||
"dark": "Sombre",
|
||||
"light": "Clair"
|
||||
},
|
||||
"message_sound": {
|
||||
"title": "Son des Messages",
|
||||
"desc": "Jouez un son lors de la réception d'un nouveau message.",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver"
|
||||
},
|
||||
"online_status": {
|
||||
"title": "Statut En Ligne",
|
||||
"desc": "S'il est activé, les membres verront le statut en ligne/hors ligne des autres.",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver"
|
||||
},
|
||||
"chat_layout": {
|
||||
"title": "Mise en Page du Chat",
|
||||
"desc": "Quel côté pour afficher les messages envoyés par vous-même",
|
||||
"left": "Gauche",
|
||||
"self_right": "Droite"
|
||||
},
|
||||
"contact_verify": {
|
||||
"title": "Vérification des Contacts",
|
||||
"desc": "Par défaut, tous les membres du serveur peuvent se voir. Si vous souhaitez que les membres du serveur ajoutent et gèrent leurs propres contacts, veuillez activer la fonctionnalité de contact (l'administrateur sera dans la liste des contacts par défaut).",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"clear_files": {
|
||||
"title": "Fichiers et Données d'Images",
|
||||
"desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de fichiers et d'images de ce serveur.",
|
||||
"btn": "Effacer les Données de Fichiers"
|
||||
},
|
||||
"clear_msgs": {
|
||||
"title": "Toutes les Données de Messages",
|
||||
"desc": "Cliquez sur le bouton ci-dessous pour supprimer toutes les données de chat, y compris les textes et les fichiers de ce serveur.",
|
||||
"btn": "Effacer les Données de Chats"
|
||||
},
|
||||
"auto_delete_file": {
|
||||
"title": "Auto-suppression des Données de Fichiers",
|
||||
"desc": "Définir le temps d'expiration pour toutes les données de fichiers de ce serveur.",
|
||||
"confirm_desc": "Tous les fichiers plus anciens que le temps défini seront supprimés.",
|
||||
"off": "Désactivé",
|
||||
"day1": "1 Jour",
|
||||
"day7": "7 Jours",
|
||||
"day30": "30 Jours",
|
||||
"day90": "90 Jours",
|
||||
"day180": "180 Jours"
|
||||
}
|
||||
},
|
||||
"license": {
|
||||
"signed": "Signé",
|
||||
"domain": "Domaines",
|
||||
"user_limit": "Limite d'Utilisateur",
|
||||
"expire": "Expiré À",
|
||||
"create": "Créé À",
|
||||
"value": "Valeur de la Licence",
|
||||
"renew": "Renouveler la Licence",
|
||||
"update": "Mettre à Jour Manuellement",
|
||||
"update_placeholder": "Veuillez saisir la nouvelle valeur de la licence",
|
||||
"renew_select": "Veuillez sélectionner le prix",
|
||||
"tip": {
|
||||
"title": "Une chance d'obtenir une mise à niveau de licence gratuite !",
|
||||
"user_test": "Obtenez une mise à niveau de licence gratuite grâce à un test utilisateur de 25 minutes",
|
||||
"contact": "Réservez un créneau ici : "
|
||||
},
|
||||
"payment_success": "Paiement Réussi !",
|
||||
"back_home": "Retour à l'Accueil",
|
||||
"tip_renewing": "Renouvellement de la Licence, ne fermez pas la fenêtre !",
|
||||
"tip_renewed": "Licence Renouvelée avec Succès !",
|
||||
"tip_renew_error": "ID de Session Stripe Invalidé",
|
||||
"tip_domain": "La licence est liée au domaine (sans port), veuillez confirmer ou mettre à jour vos paramètres :",
|
||||
"tip_port": "Remplissez votre nom de domaine sans port.",
|
||||
"tip_confirm": "Commencer le Paiement"
|
||||
},
|
||||
"bot": {
|
||||
"add_api_key": "Ajouter une Clé API",
|
||||
"no_api_key": "Aucune Clé API pour l'instant",
|
||||
"bot_tip_title": "Les bots peuvent envoyer des messages aux canaux.",
|
||||
"bot_tip_desc": "Veuillez noter que nous ne montrons pas la clé API d'un Bot à nouveau après sa génération. Vous pouvez toujours générer une nouvelle clé API pour un Bot en cliquant sur \"Ajouter une Clé API\" et cela n'annulera pas les clés API précédentes.",
|
||||
"webhook_tip_title": "Les Webhooks peuvent recevoir des messages dans un canal et devraient avoir une URL fournie par vous.",
|
||||
"webhook_tip_desc": "Veuillez fournir une URL de webhook qui retournera un code 200.",
|
||||
"manage": "Gérer",
|
||||
"how_to_use": "Comment Utiliser",
|
||||
"manage_desc": "Vous pouvez supprimer un bot à tout moment, l'utilisateur bot sera immédiatement supprimé et toutes les API seront annulées.",
|
||||
"create_title": "Créer un Bot",
|
||||
"create_desc": "Donnez un nom à votre bot. Vous pouvez également ajouter une URL de webhook d’endpoint pour recevoir des données.",
|
||||
"create_key_title": "Créer une Clé API pour le Bot",
|
||||
"create_key_desc": "Créer une Clé API pour authentifier votre communication",
|
||||
"create_key_warning": "Assurez-vous de copier la Clé API maintenant. Vous ne pourrez pas la voir à nouveau !",
|
||||
"key_copy_and_close": "Copier et Fermer",
|
||||
"delete_title": "Supprimer le Bot",
|
||||
"delete_desc": "Êtes-vous sûr de vouloir supprimer le Bot ?",
|
||||
"delete_key_title": "Supprimer la Clé API du Bot",
|
||||
"delete_key_desc": "Êtes-vous sûr de vouloir supprimer cette Clé API de Bot ?",
|
||||
"webhook_title": "Mettre à Jour l'URL du Webhook",
|
||||
"webhook_desc": "Veuillez saisir une URL valide",
|
||||
"col_avatar": "Avatar",
|
||||
"col_name": "Nom du Bot",
|
||||
"col_api_key": "Clé API (Messages Entrants)",
|
||||
"col_webhook": "URL du Webhook d'Endpoint (Sortant)",
|
||||
"col_opt": "Opérations",
|
||||
"col_key_name": "Nom",
|
||||
"col_key_value": "Clé",
|
||||
"col_key_create_time": "Créé À",
|
||||
"col_key_last_used": "Dernière Utilisation"
|
||||
},
|
||||
"auto_delete_msg": {
|
||||
"title": "Supprimer les messages envoyés par moi-même après une certaine période.",
|
||||
"desc": "Cela supprimera les données de messages stockées à la fois sur le serveur et du côté client.",
|
||||
"off": "Désactivé",
|
||||
"5_min": "5 minutes",
|
||||
"10_min": "10 minutes",
|
||||
"1_hour": "1 heure",
|
||||
"1_day": "1 jour",
|
||||
"1_week": "1 semaine",
|
||||
"clear": "Effacer tous les messages du canal",
|
||||
"clear_title": "Effacer tous les messages dans ce canal.",
|
||||
"clear_desc": "Cela supprimera toutes les données de messages stockées à la fois sur le serveur et du côté client."
|
||||
},
|
||||
"firebase": {
|
||||
"disable": "Désactiver",
|
||||
"use_official": "Utiliser la Configuration Officielle",
|
||||
"custom": "Personnalisé",
|
||||
"enable": "Activer",
|
||||
"token_url": "URL du Token",
|
||||
"project_id": "ID du Projet",
|
||||
"private_key": "Clé Privée",
|
||||
"client_email": "Email du Client"
|
||||
},
|
||||
"smtp": {
|
||||
"enable": "Activer",
|
||||
"host": "Hôte",
|
||||
"port": "Port",
|
||||
"from": "De",
|
||||
"username": "Nom d'utilisateur",
|
||||
"password": "Mot de Passe",
|
||||
"how_to": "Comment configurer SMTP ?",
|
||||
"send_test_email": "Envoyer un Email de Test"
|
||||
},
|
||||
"agora": {
|
||||
"enable": "Activer",
|
||||
"how_to": "Comment configurer Agora ?"
|
||||
},
|
||||
"third_app": {
|
||||
"key": "Clé Sécurisée API",
|
||||
"update": "Mettre à Jour le Secret",
|
||||
"update_tip": "Êtes-vous sûr de vouloir mettre à jour le secret API ? Le secret précédent sera invalidé",
|
||||
"key_tip": "La clé de sécurité convenue entre le serveur et l'application tierce est utilisée pour chiffrer les données de communication.",
|
||||
"how_to": "Comment utiliser ?"
|
||||
},
|
||||
"login": {
|
||||
"password": "Mot de Passe",
|
||||
"password_desc": "Permet aux membres de se connecter avec un mot de passe.",
|
||||
"magic_link": "Lien Magique",
|
||||
"magic_link_desc": "Permet aux membres de se connecter avec un Lien Magique.",
|
||||
"google": "Google",
|
||||
"google_desc": "Permet aux membres de se connecter avec Google.",
|
||||
"github": "GitHub",
|
||||
"github_desc": "Permet aux membres de se connecter avec GitHub.",
|
||||
"metamask": "Metamask",
|
||||
"metamask_desc": "Permet aux membres de se connecter avec Metamask.",
|
||||
"oidc": "OIDC",
|
||||
"oidc_desc": "Permet aux membres de se connecter avec OIDC.",
|
||||
"oidc_custom": "Personnalisé",
|
||||
"more_details": "Besoin de plus de détails ? Consultez notre <0>documentation</0>."
|
||||
},
|
||||
"widget": {
|
||||
"tip": "Étendre VoceChat en intégrant le widget SDK VoceChat !",
|
||||
"code": "Exemple de Code",
|
||||
"code_comment": "mettez ce fragment de code dans votre fichier html",
|
||||
"config": "Description de la Configuration",
|
||||
"param_key": "Clé de Paramètre",
|
||||
"default_value": "Valeur par Défaut",
|
||||
"remark": "Remarques",
|
||||
"custom_style_tip": "si vous souhaitez personnaliser plus précisément la position du widget, utilisez l'ID de l'élément du widget dans votre fichier HTML ou CSS, comme ceci",
|
||||
"param_id": "ID du Widget (version serveur >= v0.3.12)",
|
||||
"param_host": "Affecter l'utilisateur discutant avec le visiteur (ID Utilisateur)",
|
||||
"param_title": "titre de la fenêtre de chat popup",
|
||||
"param_logo": "URL de l'icône du logo de la fenêtre de chat popup",
|
||||
"param_auto_reg": "true : Enregistrer automatiquement un nouvel utilisateur s'il n'existe pas, false : l'utilisateur doit saisir son email et son nom pour s'inscrire",
|
||||
"param_login_token": "Si vous utilisez les informations existantes d'utilisateur de votre propre plateforme pour générer un nouveau compte VoceChat, ceci est le token de connexion généré par votre plateforme, pour plus de détails, veuillez vous référer à : https://doc.voce.chat/zh-cn/login-with-other-account. Ne remplissez pas ceci pour des scénarios par défaut (aucun système d'utilisateurs tiers) !",
|
||||
"param_theme_color": "La couleur de thème du widget",
|
||||
"param_close_width": "La largeur lorsque le widget est fermé",
|
||||
"param_close_height": "La hauteur lorsque le widget est fermé",
|
||||
"param_open_width": "La largeur lorsque le widget est ouvert",
|
||||
"param_open_height": "La hauteur lorsque le widget est ouvert",
|
||||
"param_welcome": "Message de bienvenue personnalisé",
|
||||
"param_position": "Position du widget : gauche/droite",
|
||||
"share_link": "Partagez le lien du widget afin que les gens puissent discuter avec vous directement",
|
||||
"widget_faq": "Activer Laisser Tout le Monde Rejoindre en Premier"
|
||||
},
|
||||
"version": {
|
||||
"client_version": "Version Client",
|
||||
"server_version": "Version Serveur",
|
||||
"build_time": "Horodatage de Construction",
|
||||
"sync": "Synchroniser avec la dernière version du client web"
|
||||
},
|
||||
"api_doc": {
|
||||
"desc": "Nous avons une documentation API détaillée sur laquelle vous pouvez baser votre propre outil de chat.",
|
||||
"access": "URL d'accès :",
|
||||
"use_method": "Méthode d'Utilisation :",
|
||||
"step_1": "Étape Une : Changez pour votre propre Serveur",
|
||||
"step_2": "Étape Deux : Entrez le token de connexion",
|
||||
"step_2_desc": "Vous pouvez obtenir le token en accédant à l'en-tête http X-API-Key ou en copiant simplement le texte ci-dessous :",
|
||||
"last": "Chaque API a un bouton 'Essayez-le', et vous pouvez déboguer l'API avec cela"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
{
|
||||
"title": "Bienvenue sur le serveur {{name}}",
|
||||
"desc": "Voici quelques étapes pour vous aider à commencer.",
|
||||
"invite": "Invitez vos amis ou coéquipiers",
|
||||
"start_by_channel": "Créez un Canal pour Commencer une Conversation",
|
||||
"start_by_dm": "Envoyez un Message Direct",
|
||||
"download": "Téléchargez les applications mobiles",
|
||||
"help": "Vous avez des questions ? Visitez notre centre d'aide",
|
||||
"sign_in_tip": "Veuillez vous connecter pour envoyer un message",
|
||||
"onboarding": {
|
||||
"title": "Configuration de VoceChat",
|
||||
"welcome": "Bienvenue dans votre VoceChat !",
|
||||
"welcome_desc": "Tout dans cet espace vous appartient. Configurons votre espace !",
|
||||
"start": "Commencer",
|
||||
"new_server": "Nommez votre nouveau serveur",
|
||||
"server_desc": "Les serveurs sont des environnements partagés où les équipes peuvent travailler sur des projets et discuter.",
|
||||
"placeholder_server": "Entrez le nom du serveur",
|
||||
"create_server": "Créer un Serveur",
|
||||
"admin_title": "Maintenant, configurons votre compte administrateur",
|
||||
"admin_desc": "Vous êtes le 1er utilisateur et l'administrateur de votre espace !",
|
||||
"sign": "S'inscrire",
|
||||
"invite_title": "Dernière étape : invitez les autres !",
|
||||
"invite_desc": "Tout d'abord, qui peut s'inscrire sur ce serveur ?",
|
||||
"confirm": "Confirmer",
|
||||
"done_welcome": "Bienvenue dans {{serverName}}",
|
||||
"done_title": "Fièrement présenté par VoceChat",
|
||||
"done_desc": "Plus de paramètres, y compris la résolution de domaine, les privilèges, la sécurité et les invitations sont disponibles dans <0>Paramètres</0>",
|
||||
"enter": "Entrer",
|
||||
"last_tip": "Maintenant, invitons les autres !",
|
||||
"last_desc": "Envoyez le lien d'invitation à vos futurs membres de la communauté :",
|
||||
"done": "Fait",
|
||||
"welcome_page": "Page de Bienvenue",
|
||||
"set_name": "Définir le Nom",
|
||||
"admin_account": "Compte Administrateur",
|
||||
"who_sign_up": "Qui Peut S'inscrire",
|
||||
"invites": "Invitations",
|
||||
"update_domain_tip": "Lien de domaine incorrect ? Veuillez le mettre à jour ici :"
|
||||
}
|
||||
}
|
||||
{
|
||||
"title": "Bienvenue sur le serveur {{name}}",
|
||||
"desc": "Voici quelques étapes pour vous aider à commencer.",
|
||||
"invite": "Invitez vos amis ou coéquipiers",
|
||||
"start_by_channel": "Créez un Canal pour Commencer une Conversation",
|
||||
"start_by_dm": "Envoyez un Message Direct",
|
||||
"download": "Téléchargez les applications mobiles",
|
||||
"help": "Vous avez des questions ? Visitez notre centre d'aide",
|
||||
"sign_in_tip": "Veuillez vous connecter pour envoyer un message",
|
||||
"vocespace": "Appel Vidéo VoceSpace",
|
||||
"license": "VoceSpace Licence de Mise à Niveau",
|
||||
"onboarding": {
|
||||
"title": "Configuration de VoceChat",
|
||||
"welcome": "Bienvenue dans votre VoceChat !",
|
||||
"welcome_desc": "Tout dans cet espace vous appartient. Configurons votre espace !",
|
||||
"start": "Commencer",
|
||||
"new_server": "Nommez votre nouveau serveur",
|
||||
"server_desc": "Les serveurs sont des environnements partagés où les équipes peuvent travailler sur des projets et discuter.",
|
||||
"placeholder_server": "Entrez le nom du serveur",
|
||||
"create_server": "Créer un Serveur",
|
||||
"admin_title": "Maintenant, configurons votre compte administrateur",
|
||||
"admin_desc": "Vous êtes le 1er utilisateur et l'administrateur de votre espace !",
|
||||
"sign": "S'inscrire",
|
||||
"invite_title": "Dernière étape : invitez les autres !",
|
||||
"invite_desc": "Tout d'abord, qui peut s'inscrire sur ce serveur ?",
|
||||
"confirm": "Confirmer",
|
||||
"done_welcome": "Bienvenue dans {{serverName}}",
|
||||
"done_title": "Fièrement présenté par VoceChat",
|
||||
"done_desc": "Plus de paramètres, y compris la résolution de domaine, les privilèges, la sécurité et les invitations sont disponibles dans <0>Paramètres</0>",
|
||||
"enter": "Entrer",
|
||||
"last_tip": "Maintenant, invitons les autres !",
|
||||
"last_desc": "Envoyez le lien d'invitation à vos futurs membres de la communauté :",
|
||||
"done": "Fait",
|
||||
"welcome_page": "Page de Bienvenue",
|
||||
"set_name": "Définir le Nom",
|
||||
"admin_account": "Compte Administrateur",
|
||||
"who_sign_up": "Qui Peut S'inscrire",
|
||||
"invites": "Invitations",
|
||||
"update_domain_tip": "Lien de domaine incorrect ? Veuillez le mettre à jour ici :"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,39 @@
|
||||
"status": {
|
||||
"uploading": "アップロード中"
|
||||
},
|
||||
"new_version": "<0>新しいバージョン</0>が利用可能になりました",
|
||||
"mobile_app": "<0>アプリ</0>のダウンロードがおすすめ"
|
||||
"tip": {
|
||||
"email_msg_tip": "お住まいの地域では通知がブロックされています。メール通知をご利用いただくには、Pro にアップグレードしてください。",
|
||||
"update": "更新に成功しました!",
|
||||
"delete": "削除に成功しました!",
|
||||
"reg": "登録に成功しました!",
|
||||
"login": "ログインに成功しました!",
|
||||
"logout": "ログアウトに成功しました!",
|
||||
"pin": "メッセージをピン留めに成功しました!",
|
||||
"pwa_install_title": "デスクトップにウェブアプリをインストールしますか?",
|
||||
"pwa_install_desc": "このアプリにすばやくアクセスするには、デスクトップに追加してください。"
|
||||
},
|
||||
"new_version": "<0>新バージョン</0>をご利用いただけます",
|
||||
"mobile_app": "<0>モバイルアプリ</0>をご覧ください",
|
||||
"price": {
|
||||
"pro": {
|
||||
"title": "VoceChat Pro",
|
||||
"desc": "",
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "VoceSpace プライベート導入ビデオ [$499]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
},
|
||||
"server_update": {
|
||||
"version_needed": "この機能には、サーバーバージョン<0>{{version}} 🚨以上が必要です",
|
||||
"current_version": "現在のバージョン: <0>{{version}} 0>",
|
||||
"update_tip": "サーバーをアップグレードしてください!",
|
||||
"howto": "VoceChatサーバーのアップデート方法"
|
||||
},
|
||||
"inactive": {
|
||||
"title": "アクティブなタブは1つだけですVoceChat",
|
||||
"desc": "このタブを引き続き使用するには、このページを再読み込みするか、タブを閉じてください。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,24 @@
|
||||
"license": "ライセンス",
|
||||
"about": "このアプリについて",
|
||||
"api_doc": "API ドキュメント",
|
||||
"version": "Version"
|
||||
"version": "Version",
|
||||
"video": "ビデオ"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Vocespace をプライベートに導入(自動導入)",
|
||||
"sub_desc": "Vocespace アカウントごとに、通話時間無制限、同時接続ユーザー5名、4K解像度のサービスを無料でご利用いただけます。アカウント作成は不要です。Vocespace を適切に設定するには、以下の手順に従ってください。",
|
||||
"prerequisite": [
|
||||
"前提条件",
|
||||
"Docker 環境",
|
||||
"ポート 80、443、7880、7881、3008、3000 が開いている",
|
||||
"ローカル IP アドレスに解決されるドメイン名(Alibaba Cloud や Tencent Cloud など、中国本土のユーザーの場合は ICP 申請が必要です)",
|
||||
"エンタープライズカスタマイズやサポートについては、han@privoce.com までお問い合わせいただくか、WeChat: Privoce を追加してください"
|
||||
],
|
||||
|
||||
"domain_desc": "カスタムドメイン名はローカル IP アドレスに解決される必要があります(Alibaba Cloud や Tencent Cloud など、中国本土のユーザーの場合は ICP 申請が必要です)",
|
||||
"enable": "有効にする",
|
||||
"how_to": "有効にした後、ドメイン名を設定して保存してください。残りの手順は自動的に完了します。"
|
||||
},
|
||||
"channel": {
|
||||
"leave": "チャンネルを離れる",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "アプリをダウンロードしましょう",
|
||||
"help": "まだ聞きたいことがある? ヘルプにアクセスしましょう",
|
||||
"sign_in_tip": "Please sign in to send message",
|
||||
"vocespace": " VoceSpace ビデオ通話",
|
||||
"license": "VoceSpace アップグレードライセンス",
|
||||
"onboarding": {
|
||||
"title": "VoceChat初期化",
|
||||
"welcome": "あなたのVoceChatへようこそ!",
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "VoceChat SDK Customization and Integration",
|
||||
"title": "VoceSpace Private Deployment Video [499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
|
||||
@@ -22,7 +22,23 @@
|
||||
"license": "License",
|
||||
"about": "About",
|
||||
"api_doc": "API Documentation",
|
||||
"version": "Version"
|
||||
"version": "Version",
|
||||
"video": "Video"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Vocespace déployé en mode privé (déploiement automatique)",
|
||||
"sub_desc": "Chaque compte Vocespace bénéficie d'appels illimités gratuits, de 5 utilisateurs simultanés et d'une résolution 4K. Aucune création de compte n'est requise. Suivez les instructions ci-dessous pour configurer correctement votre Vocespace.",
|
||||
"prerequisite": [
|
||||
"Prérequis",
|
||||
"Environnement Docker",
|
||||
"Les ports 80, 443, 7880, 7881, 3008 et 3000 sont ouverts",
|
||||
"Nom de domaine pointant vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)",
|
||||
"Pour toute personnalisation ou assistance en entreprise, veuillez contacter han@privoce.com ou ajouter Privoce sur WeChat"
|
||||
],
|
||||
"domain_desc": "Les noms de domaine personnalisés doivent pointer vers l'adresse IP locale (pour les utilisateurs de Chine continentale, tels qu'Alibaba Cloud et Tencent Cloud, un enregistrement ICP est requis)",
|
||||
"enable": "Activer",
|
||||
"how_to": "Après l'activation, veuillez définir le nom de domaine et enregistrer. Les étapes suivantes seront effectuées automatiquement."
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Leave Channel",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "Download Mobile apps",
|
||||
"help": "Tem perguntas? Visite nossa central de ajuda",
|
||||
"sign_in_tip": "Faça login para enviar mensagem",
|
||||
"vocespace": "Chamada de vídeo VoceSpace",
|
||||
"license": "VoceSpace Licença de Atualização",
|
||||
"onboarding": {
|
||||
"title": "Configuração do VoceChat",
|
||||
"welcome": "Bem-vindo ao seu VoceChat!",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "VoceChat SDK Customization and Integration",
|
||||
"title": "Видеоинструкция по развертыванию VoceSpace Private [499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
|
||||
@@ -22,7 +22,23 @@
|
||||
"license": "Лицензия",
|
||||
"about": "About",
|
||||
"api_doc": "API Documentation",
|
||||
"version": "Версия"
|
||||
"version": "Версия",
|
||||
"video": "Видео"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Vocespace развернут в частном порядке (автоматическое развертывание)",
|
||||
"sub_desc": "Каждая учетная запись Vocespace будет пользоваться бесплатным неограниченным временем звонков, 5 одновременными пользователями и разрешением 4K. Создание учетной записи не требуется. Следуйте приведенным ниже инструкциям для правильной настройки Vocespace.",
|
||||
"prerequisite": [
|
||||
"Предварительные условия",
|
||||
"Среда Docker",
|
||||
"Открыты порты 80, 443, 7880, 7881, 3008, 3000",
|
||||
"Доменное имя, разрешающееся в локальный IP-адрес (для пользователей в материковом Китае, таких как Alibaba Cloud и Tencent Cloud, требуется подача заявки на ICP)",
|
||||
"Для корпоративной настройки или получения помощи, пожалуйста, свяжитесь с han@privoce.com или добавьте нас в WeChat: Privoce"
|
||||
],
|
||||
"domain_desc": "Пользовательские доменные имена должны разрешаться в локальный IP-адрес (для пользователей в материковом Китае, таких как Alibaba Cloud и Tencent Cloud, требуется подача заявки на ICP)",
|
||||
"enable": "Включить",
|
||||
"how_to": "После включения, пожалуйста, укажите доменное имя и сохраните. Остальные шаги будут выполнены автоматически."
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Покинуть канал",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "Загрузить мобильные приложения",
|
||||
"help": "Есть вопросы? Посетите наш справочный центр",
|
||||
"sign_in_tip": "Пожалуйста, войдите, чтобы отправить сообщение",
|
||||
"vocespace": "Видеозвонок VoceSpace",
|
||||
"license": "VoceSpace Лицензия на обновление",
|
||||
"onboarding": {
|
||||
"title": "Настройка VoceChat",
|
||||
"welcome": "Добро пожаловать в ваш VoceChat!",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "VoceChat SDK Customization and Integration",
|
||||
"title": "VoceSpace Özel Dağıtım Videosu [499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
|
||||
@@ -23,7 +23,23 @@
|
||||
"api_doc": "API Documentation",
|
||||
"version": "Sürüm",
|
||||
"faq": "SSS",
|
||||
"feedback": "Geri Bildirim"
|
||||
"feedback": "Geri Bildirim",
|
||||
"video": "Video"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "Vocespace özel olarak dağıtıldı (otomatik dağıtım)",
|
||||
"sub_desc": "Her Vocespace hesabı ücretsiz sınırsız arama süresi, 5 eş zamanlı kullanıcı ve 4K çözünürlük hizmetinden yararlanacaktır. Hesap oluşturma gerekmez. Vocespace'inizi doğru şekilde yapılandırmak için aşağıdaki talimatları izleyin.",
|
||||
"prerequisite": [
|
||||
"Önkoşullar",
|
||||
"Docker ortamı",
|
||||
"80, 443, 7880, 7881, 3008, 3000 portları açık olmalı",
|
||||
"Yerel IP adresine çözümlenen alan adı (Alibaba Cloud ve Tencent Cloud gibi Çin anakarasındaki kullanıcılar için ICP dosyalama gereklidir)",
|
||||
"Kurumsal özelleştirme veya yardım için lütfen han@privoce.com ile iletişime geçin veya WeChat: Privoce ekleyin"
|
||||
],
|
||||
"domain_desc": "Özel alan adları yerel IP adresine çözümlenmelidir (Alibaba Cloud ve Tencent Cloud gibi Çin anakarasındaki kullanıcılar için ICP dosyalama gereklidir)",
|
||||
"enable": "Etkinleştir",
|
||||
"how_to": "Etkinleştirdikten sonra lütfen alan adını ayarlayın ve kaydedin. Kalan adımlar otomatik olarak tamamlanacaktır."
|
||||
},
|
||||
"channel": {
|
||||
"leave": "Kanalı Terket",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "Mobil uygulamaları indirin",
|
||||
"help": "Sorunuz var mı? Yardım merkezimizi ziyaret edin",
|
||||
"sign_in_tip": "İleti göndermek için lütfen oturum açın",
|
||||
"vocespace": " VoceSpace Video Görüşmesi",
|
||||
"license": "VoceSpace Yükseltme Lisansı",
|
||||
"onboarding": {
|
||||
"title": "VoceChat Kurulumu",
|
||||
"welcome": "VoceChat'e Hoşgeldiniz!",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"price": "$49"
|
||||
},
|
||||
"supreme": {
|
||||
"title": "VoceChat SDK 定制与集成",
|
||||
"title": "VoceSpace 私有部署音视频[499$]",
|
||||
"desc": "",
|
||||
"price": ""
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"about": "关于",
|
||||
"api_doc": "API 文档",
|
||||
"version": "版本信息",
|
||||
"feedback": "反馈"
|
||||
"feedback": "反馈",
|
||||
"video": "音视频"
|
||||
},
|
||||
"channel": {
|
||||
"leave": "离开",
|
||||
@@ -267,6 +268,21 @@
|
||||
"enable": "开启",
|
||||
"how_to": "如何配置 Agora?"
|
||||
},
|
||||
"vocespace": {
|
||||
"title": "Vocespace",
|
||||
"desc": "使用私有部署的Vocespace(自动部署)",
|
||||
"sub_desc": "每个Vocespace账户将享受免费无限制通话时长、5人并发及4K分辨率服务。无需创建账户。按照以下说明正确配置您的Vocespace。",
|
||||
"prerequisite": [
|
||||
"预备条件",
|
||||
"Docker环境",
|
||||
"端口 80, 443, 7880, 7881, 3008, 3000 已开放",
|
||||
"解析到本机IP的域名(中国大陆用户,如阿里云和腾讯云,需备案)",
|
||||
"企业定制或需要帮助,请联系 han@privoce.com 或添加微信:Privoce"
|
||||
],
|
||||
"domain_desc": "自定义域名需要是解析到本机IP的域名 (中国大陆用户,如阿里云和腾讯云,需备案)",
|
||||
"enable": "启用",
|
||||
"how_to": "启用后,请设置域名并保存,其余步骤会自动化完成"
|
||||
},
|
||||
"third_app": {
|
||||
"key": "API 安全密钥",
|
||||
"update": "更新安全密钥",
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"download": "下载移动端APP",
|
||||
"help": "还有问题?访问我们的帮助中心",
|
||||
"sign_in_tip": "请登录后发消息",
|
||||
"vocespace": " VoceSpace 视频通话",
|
||||
"license": "VoceSpace 升级证书",
|
||||
"onboarding": {
|
||||
"title": "VoceChat初始化",
|
||||
"welcome": "欢迎来到你的VoceChat!",
|
||||
|
||||
+99
-85
@@ -18,7 +18,8 @@ import {
|
||||
Server,
|
||||
SMTPConfig,
|
||||
SystemCommon,
|
||||
TestEmailDTO
|
||||
TestEmailDTO,
|
||||
VocespaceConfig,
|
||||
} from "@/types/server";
|
||||
import { User } from "@/types/user";
|
||||
import { compareVersion, encodeBase64 } from "@/utils";
|
||||
@@ -26,7 +27,7 @@ import BASE_URL, {
|
||||
ContentTypes,
|
||||
IS_OFFICIAL_DEMO,
|
||||
KEY_SERVER_VERSION,
|
||||
PAYMENT_URL_PREFIX
|
||||
PAYMENT_URL_PREFIX,
|
||||
} from "../config";
|
||||
import { updateInfo } from "../slices/server";
|
||||
import { updateCallInfo, upsertVoiceList } from "../slices/voice";
|
||||
@@ -48,29 +49,29 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
console.error("get server info error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
getThirdPartySecret: builder.query<string, void>({
|
||||
query: () => ({
|
||||
url: `/admin/system/third_party_secret`,
|
||||
responseHandler: "text"
|
||||
responseHandler: "text",
|
||||
}),
|
||||
keepUnusedDataFor: 0
|
||||
keepUnusedDataFor: 0,
|
||||
}),
|
||||
updateThirdPartySecret: builder.mutation<string, void>({
|
||||
query: () => ({
|
||||
url: `/admin/system/third_party_secret`,
|
||||
method: "POST",
|
||||
responseHandler: "text"
|
||||
})
|
||||
responseHandler: "text",
|
||||
}),
|
||||
}),
|
||||
getServerVersion: builder.query<string, void>({
|
||||
query: () => ({
|
||||
headers: {
|
||||
accept: "text/plain"
|
||||
accept: "text/plain",
|
||||
},
|
||||
url: `/admin/system/version`,
|
||||
responseHandler: "text"
|
||||
responseHandler: "text",
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
@@ -80,60 +81,63 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
console.error("get server version error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
getFirebaseConfig: builder.query<FirebaseConfig, void>({
|
||||
query: () => ({ url: `/admin/fcm/config` })
|
||||
query: () => ({ url: `/admin/fcm/config` }),
|
||||
}),
|
||||
getGoogleAuthConfig: builder.query<GoogleAuthConfig, void>({
|
||||
query: () => ({ url: `/admin/google_auth/config` })
|
||||
query: () => ({ url: `/admin/google_auth/config` }),
|
||||
}),
|
||||
updateGoogleAuthConfig: builder.mutation<void, GoogleAuthConfig>({
|
||||
query: (data) => ({
|
||||
url: `/admin/google_auth/config`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
getGithubAuthConfig: builder.query<GithubAuthConfig, void>({
|
||||
query: () => ({ url: `/admin/github_auth/config` })
|
||||
query: () => ({ url: `/admin/github_auth/config` }),
|
||||
}),
|
||||
updateGithubAuthConfig: builder.mutation<void, GithubAuthConfig>({
|
||||
query: (data) => ({
|
||||
url: `/admin/github_auth/config`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
sendTestEmail: builder.mutation<void, TestEmailDTO>({
|
||||
query: (data) => ({
|
||||
url: `/admin/system/send_mail`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
updateFirebaseConfig: builder.mutation<void, FirebaseConfig>({
|
||||
query: (data) => ({
|
||||
url: `/admin/fcm/config`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
getVocespaceConfig: builder.query<VocespaceConfig, void>({
|
||||
query: () => ({ url: `/admin/vocespace/config` }),
|
||||
}),
|
||||
getAgoraConfig: builder.query<AgoraConfig, void>({
|
||||
query: () => ({ url: `/admin/agora/config` })
|
||||
query: () => ({ url: `/admin/agora/config` }),
|
||||
}),
|
||||
getAgoraChannels: builder.query<
|
||||
AgoraVoicingListResponse,
|
||||
{ page_no: number; page_size: number }
|
||||
>({
|
||||
query: (param = { page_no: 0, page_size: 100 }) => ({
|
||||
url: `/admin/agora/channel/${param.page_no}/${param.page_size}`
|
||||
url: `/admin/agora/channel/${param.page_no}/${param.page_size}`,
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
|
||||
try {
|
||||
const {
|
||||
voice: { callingFrom },
|
||||
authData
|
||||
authData,
|
||||
} = getState() as RootState;
|
||||
const { data: resp } = await queryFulfilled;
|
||||
const { success } = resp;
|
||||
@@ -146,7 +150,7 @@ export const serverApi = createApi({
|
||||
id: +id,
|
||||
context,
|
||||
memberCount: count,
|
||||
channelName: data.channel_name
|
||||
channelName: data.channel_name,
|
||||
};
|
||||
});
|
||||
dispatch(upsertVoiceList(arr));
|
||||
@@ -162,7 +166,7 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
console.error("get voice list error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
getAgoraUsersByChannel: builder.query<number[], string>({
|
||||
query: (channel_name) => ({ url: `/admin/agora/channel/user/${channel_name}/false` }),
|
||||
@@ -171,24 +175,31 @@ export const serverApi = createApi({
|
||||
return resp.data.users ?? [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateAgoraConfig: builder.mutation<void, AgoraConfig>({
|
||||
query: (data) => ({
|
||||
url: `/admin/agora/config`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
updateVocespaceConfig: builder.mutation<void, VocespaceConfig>({
|
||||
query: (data) => ({
|
||||
url: `/admin/vocespace/config`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
getAgoraStatus: builder.query<boolean, void>({
|
||||
query: () => ({ url: `/admin/agora/enabled` })
|
||||
query: () => ({ url: `/admin/agora/enabled` }),
|
||||
}),
|
||||
generateAgoraToken: builder.mutation<AgoraTokenResponse, { uid: number } | { gid: number }>({
|
||||
query: (data) => ({
|
||||
url: `/admin/agora/token`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
getSystemCommon: builder.query<SystemCommon, void>({
|
||||
query: () => ({ url: `/admin/system/common` }),
|
||||
@@ -206,13 +217,13 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
console.error("get server common error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateSystemCommon: builder.mutation<void, Partial<SystemCommon>>({
|
||||
query: (data) => ({
|
||||
url: `/admin/system/common`,
|
||||
method: "PUT",
|
||||
body: data
|
||||
body: data,
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
@@ -221,20 +232,20 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
console.error("update server common error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
getSMTPConfig: builder.query<SMTPConfig, void>({
|
||||
query: () => ({ url: `/admin/smtp/config` })
|
||||
query: () => ({ url: `/admin/smtp/config` }),
|
||||
}),
|
||||
getSMTPStatus: builder.query<boolean, void>({
|
||||
query: () => ({ url: `/admin/smtp/enabled` })
|
||||
query: () => ({ url: `/admin/smtp/enabled` }),
|
||||
}),
|
||||
updateSMTPConfig: builder.mutation<void, SMTPConfig>({
|
||||
query: (data) => ({
|
||||
url: `/admin/smtp/config`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
getLoginConfig: builder.query<LoginConfig, void>({
|
||||
query: () => ({ url: `/admin/login/config` }),
|
||||
@@ -248,43 +259,43 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
console.error("get login config error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
getFiles: builder.query<VoceChatFile[], GetFilesDTO>({
|
||||
query: (params) => ({
|
||||
url: `/admin/system/files?${new URLSearchParams(
|
||||
params as Record<string, string>
|
||||
).toString()}`
|
||||
})
|
||||
).toString()}`,
|
||||
}),
|
||||
}),
|
||||
updateLoginConfig: builder.mutation<void, Partial<LoginConfig>>({
|
||||
query: (data) => ({
|
||||
url: `/admin/login/config`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
updateLogo: builder.mutation<void, File>({
|
||||
query: (data) => ({
|
||||
headers: {
|
||||
"content-type": "image/png"
|
||||
"content-type": "image/png",
|
||||
},
|
||||
url: `/admin/system/organization/logo`,
|
||||
method: "POST",
|
||||
body: data
|
||||
body: data,
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
await queryFulfilled;
|
||||
dispatch(
|
||||
updateInfo({
|
||||
logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}`
|
||||
logo: `${BASE_URL}/resource/organization/logo?t=${+new Date()}`,
|
||||
})
|
||||
);
|
||||
} catch {
|
||||
console.error("update server logo error");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
updateServer: builder.mutation<void, Partial<Server>>({
|
||||
query: (data) => ({
|
||||
@@ -293,7 +304,7 @@ export const serverApi = createApi({
|
||||
compareVersion(localStorage.getItem(KEY_SERVER_VERSION) ?? "", "0.3.8") > 0
|
||||
? "PUT"
|
||||
: "POST",
|
||||
body: data
|
||||
body: data,
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
|
||||
const rootStore = getState() as RootState;
|
||||
@@ -304,34 +315,34 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
dispatch(updateInfo({ name: prevName, description: prevDesc }));
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
createAdmin: builder.mutation<User, CreateAdminDTO>({
|
||||
query: (data) => ({
|
||||
url: "/admin/system/create_admin",
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
getFrontendUrl: builder.query<string, void>({
|
||||
query: () => ({
|
||||
url: `/admin/system/frontend_url`,
|
||||
responseHandler: "text"
|
||||
})
|
||||
responseHandler: "text",
|
||||
}),
|
||||
}),
|
||||
updateFrontendUrl: builder.mutation<void, string>({
|
||||
query: (url) => ({
|
||||
url: `/admin/system/update_frontend_url`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "text/plain"
|
||||
"content-type": "text/plain",
|
||||
},
|
||||
body: url
|
||||
})
|
||||
body: url,
|
||||
}),
|
||||
}),
|
||||
getLicense: builder.query<LicenseResponse, void>({
|
||||
query: () => ({
|
||||
url: `/license`
|
||||
url: `/license`,
|
||||
}),
|
||||
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
|
||||
// vocechat 官方 demo 则忽略
|
||||
@@ -340,7 +351,7 @@ export const serverApi = createApi({
|
||||
const { upgraded: prevValue } = rootStore.server;
|
||||
try {
|
||||
const {
|
||||
data: { user_limit }
|
||||
data: { user_limit },
|
||||
} = await queryFulfilled;
|
||||
const currValue = user_limit > 20;
|
||||
if (prevValue !== currValue) {
|
||||
@@ -349,67 +360,67 @@ export const serverApi = createApi({
|
||||
} catch {
|
||||
console.error("get license failed ");
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
getLicensePaymentUrl: builder.mutation<RenewLicenseResponse, RenewLicense>({
|
||||
query: (data) => ({
|
||||
url: `${PAYMENT_URL_PREFIX}/vocechat/payment/create`,
|
||||
method: "POST",
|
||||
body: data
|
||||
})
|
||||
body: data,
|
||||
}),
|
||||
}),
|
||||
getGeneratedLicense: builder.query<{ license: string }, string>({
|
||||
query: (session_id) => ({
|
||||
url: `${PAYMENT_URL_PREFIX}/vocechat/licenses/${session_id}`
|
||||
})
|
||||
url: `${PAYMENT_URL_PREFIX}/vocechat/licenses/${session_id}`,
|
||||
}),
|
||||
}),
|
||||
checkLicense: builder.mutation<LicenseResponse, string>({
|
||||
query: (license) => ({
|
||||
url: "/license/check",
|
||||
method: "POST",
|
||||
body: { license }
|
||||
})
|
||||
body: { license },
|
||||
}),
|
||||
}),
|
||||
upsertLicense: builder.mutation<boolean, string>({
|
||||
query: (license) => ({
|
||||
url: "/license",
|
||||
method: "PUT",
|
||||
body: { license }
|
||||
})
|
||||
body: { license },
|
||||
}),
|
||||
}),
|
||||
clearAllMessages: builder.query<void, void>({
|
||||
query: () => ({
|
||||
url: "/admin/system/message/clear",
|
||||
method: "DELETE"
|
||||
})
|
||||
method: "DELETE",
|
||||
}),
|
||||
}),
|
||||
clearAllFiles: builder.query<void, void>({
|
||||
query: () => ({
|
||||
url: "/resource/file/delete",
|
||||
method: "DELETE"
|
||||
})
|
||||
method: "DELETE",
|
||||
}),
|
||||
}),
|
||||
getWidgetExtCSS: builder.query<string, void>({
|
||||
query: () => ({
|
||||
url: "/resource/widget-extra.css",
|
||||
responseHandler: "text"
|
||||
})
|
||||
responseHandler: "text",
|
||||
}),
|
||||
}),
|
||||
updateWidgetExtCSS: builder.mutation<boolean, string>({
|
||||
query: (css) => ({
|
||||
url: "/resource/widget-extra.css",
|
||||
method: "PUT",
|
||||
body: { data: css }
|
||||
})
|
||||
body: { data: css },
|
||||
}),
|
||||
}),
|
||||
getBotRelatedChannels: builder.query<Channel[], { api_key: string; public_only?: boolean }>({
|
||||
query: ({ api_key, public_only = false }) => ({
|
||||
url: public_only ? `/bot?public_only=${public_only}` : `/bot`,
|
||||
headers: {
|
||||
"x-api-key": api_key
|
||||
}
|
||||
})
|
||||
"x-api-key": api_key,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
sendMessageByBot: builder.mutation<
|
||||
number,
|
||||
@@ -426,14 +437,14 @@ export const serverApi = createApi({
|
||||
headers: {
|
||||
"x-api-key": api_key,
|
||||
"content-type": ContentTypes[type],
|
||||
"X-Properties": properties ? encodeBase64(JSON.stringify(properties)) : ""
|
||||
"X-Properties": properties ? encodeBase64(JSON.stringify(properties)) : "",
|
||||
},
|
||||
url: cid ? `/bot/send_to_group/${cid}` : `/bot/send_to_user/${uid}`,
|
||||
method: "POST",
|
||||
body: content
|
||||
})
|
||||
})
|
||||
})
|
||||
body: content,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
@@ -484,5 +495,8 @@ export const {
|
||||
useLazyGetAgoraUsersByChannelQuery,
|
||||
useLazyClearAllFilesQuery,
|
||||
useLazyClearAllMessagesQuery,
|
||||
useLazyGetFilesQuery
|
||||
useLazyGetFilesQuery,
|
||||
useGetVocespaceConfigQuery,
|
||||
useLazyGetVocespaceConfigQuery,
|
||||
useUpdateVocespaceConfigMutation,
|
||||
} = serverApi;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC, useState } from "react";
|
||||
import { FC, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { NavLink, useNavigate } from "react-router-dom";
|
||||
import Linkify from "linkify-react";
|
||||
|
||||
import { useAppSelector } from "@/app/store";
|
||||
@@ -15,6 +15,8 @@ import UsersModal from "./UsersModal";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import { KEY_ADMIN_ONLY_INVITE } from "../app/config";
|
||||
import useServerExtSetting from "../hooks/useServerExtSetting";
|
||||
import useLicense from "@/hooks/useLicense";
|
||||
import { compareVersion } from "@/utils";
|
||||
|
||||
interface Props {
|
||||
type?: "chat" | "user";
|
||||
@@ -25,6 +27,7 @@ const classes = {
|
||||
boxTip: "px-5 text-xs md:text-sm text-slate-600 dark:text-gray-100 font-bold text-center",
|
||||
};
|
||||
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
const navigate = useNavigate();
|
||||
const { getExtSetting } = useServerExtSetting();
|
||||
const onlyAdminCanInvite = getExtSetting(KEY_ADMIN_ONLY_INVITE);
|
||||
const { t } = useTranslation("welcome");
|
||||
@@ -44,6 +47,19 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
};
|
||||
const chatTip = type == "chat" ? t("start_by_channel") : t("start_by_dm");
|
||||
const chatHandler = type == "chat" ? toggleChannelModalVisible : toggleUserListVisible;
|
||||
|
||||
const { license: licenseInfo } = useLicense(true);
|
||||
|
||||
const toLicensePage = () => {
|
||||
navigate("/setting/license");
|
||||
};
|
||||
const currentVersion = useAppSelector((store) => store.server.version, shallowEqual);
|
||||
console.warn("currentVersion", currentVersion, compareVersion(currentVersion, "0.5.6"));
|
||||
const showVoceSpace = useMemo(
|
||||
() => compareVersion(currentVersion, "0.5.6") >= 0,
|
||||
[currentVersion]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-8 -mt-[50px] dark:bg-gray-700">
|
||||
@@ -84,18 +100,99 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 m-auto">
|
||||
<div
|
||||
style={{
|
||||
width: "432px",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "16px",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{(isAdmin || !onlyAdminCanInvite) && (
|
||||
<button className={classes.box} onClick={toggleInviteModalVisible}>
|
||||
<IconInvite className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>{t("invite")}</div>
|
||||
</button>
|
||||
)}
|
||||
<button onClick={chatHandler} className={classes.box}>
|
||||
<IconChat className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>{chatTip}</div>
|
||||
</button>
|
||||
{!server.upgraded && (
|
||||
|
||||
{licenseInfo?.user_limit == 20 && showVoceSpace ? (
|
||||
<button onClick={toLicensePage} className={classes.box}>
|
||||
<svg
|
||||
viewBox="0 0 1028 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="5677"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="39.5"
|
||||
height="39"
|
||||
>
|
||||
<path
|
||||
d="M625.669087 392.461597l-47.150172-6.847535c-6.651891 0-13.499427-6.651891-16.825373-13.499427L538.020634 324.964463c-6.651891-10.173481-13.499427-16.825373-23.672908-16.825372s-16.825373 6.651891-20.151318 13.499426l-23.672908 47.150172c-3.325946 6.651891-10.173481 10.173481-16.825372 13.499427l-47.150172 6.651892c-10.173481 0-16.825373 6.847535-20.151319 16.825372-3.325946 10.173481 0 16.825373 6.651892 23.672908l37.172335 40.498281c3.325946 3.325946 6.651891 10.173481 6.651891 13.499426v3.325946l-10.173481 57.323653c0 10.173481 3.325946 16.825373 10.173481 23.672908 6.651891 6.651891 16.825373 6.651891 26.998854 0l40.49828-23.672908h10.173481c3.325946 0 6.847535 0 10.173481 3.325946l43.824227 23.672908c3.325946 3.325946 6.651891 3.325946 10.173481 3.325946 3.325946 0 10.173481 0 13.499426-3.325946 6.847535-6.651891 10.173481-13.499427 10.173482-23.672908l-10.173482-53.997707v-3.325946c0-6.651891 3.325946-10.173481 6.847536-16.825373l37.172335-40.49828c6.651891-6.651891 6.651891-16.825373 6.651891-23.672908-0.391288-9.977837-10.564769-16.629729-17.21666-16.629729z"
|
||||
fill="#FFDB5D"
|
||||
p-id="5678"
|
||||
data-spm-anchor-id="a313x.search_index.0.i1.55193a81XeGIRV"
|
||||
></path>
|
||||
<path
|
||||
d="M512 0C229.294612 0 0 229.294612 0 512s229.294612 512 512 512 512-229.294612 512-512S794.901032 0 512 0z m-116.212457 798.618265s-33.650745-37.172335-63.975545-63.975544c-43.824226-13.499427-97.821934-23.672908-97.821933-23.672908l67.497134-114.647306c33.650745 47.150172 97.821934 84.322507 155.145587 97.821933l-60.845243 104.473825z m-97.821934-350.59381c0-114.647306 101.147879-222.447077 215.795186-222.447076 114.647306 0 215.795185 101.147879 215.795185 215.795185 0 114.647306-101.147879 215.795185-215.795185 215.795185-114.647306-0.195644-215.795185-94.495988-215.795186-209.143294z m401.069928 279.77073c-30.324799 30.324799-67.497134 70.82308-67.497134 70.82308l-60.649599-104.473825c57.323653-13.499427 114.647306-53.997707 148.298051-97.821933l70.82308 114.647306c0.195644-3.325946-47.150172 3.325946-90.974398 16.825372z"
|
||||
fill="#FFDB5D"
|
||||
p-id="5679"
|
||||
data-spm-anchor-id="a313x.search_index.0.i0.55193a81XeGIRV"
|
||||
></path>
|
||||
</svg>
|
||||
<div className={classes.boxTip}>{t("license")}</div>
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={chatHandler} className={classes.box}>
|
||||
<IconChat className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>{chatTip}</div>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Vocespace nav to */}
|
||||
{showVoceSpace ? (
|
||||
<NavLink to={"/setting/video"} className={classes.box}>
|
||||
<svg
|
||||
width="39"
|
||||
height="39"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M29.2884 20.4182C29.7499 19.0293 29.9998 17.5438 29.9998 16C29.9998 8.26806 23.7319 2.00008 15.9999 2.00008C8.26798 2.00008 2 8.26806 2 16C2 17.5744 2.25987 19.088 2.73906 20.5005C3.09861 20.5088 3.43836 20.5246 3.74422 20.539C4.1999 20.5603 4.55678 20.5768 4.8465 20.571L4.85764 20.5707L4.9685 18.3288L4.96959 18.3067L4.97155 18.2847L5.09066 16.9465L5.0947 16.9011L5.1024 16.8562L5.23684 16.0725C5.376 15.2613 5.76653 14.5144 6.35326 13.9372C6.851 13.4475 7.44688 13.1183 8.07693 12.9487C7.43026 12.6087 6.98926 11.9304 6.98926 11.1491C6.98926 10.027 7.8989 9.11737 9.021 9.11737C10.1431 9.11737 11.0527 10.027 11.0527 11.1491C11.0527 11.9091 10.6355 12.5717 10.0175 12.9201C10.5584 13.0484 11.0773 13.2921 11.5345 13.6506C11.7371 13.3464 11.9714 13.0622 12.2347 12.8031C13.0209 12.0296 13.9873 11.55 14.9979 11.3626C14.1354 10.9827 13.5332 10.1204 13.5332 9.11741C13.5332 7.76315 14.631 6.6653 15.9853 6.6653C17.3396 6.6653 18.4374 7.76315 18.4374 9.11741C18.4374 10.1214 17.834 10.9845 16.9701 11.3638C17.9716 11.5504 18.9301 12.0227 19.7133 12.7789C19.9844 13.0407 20.2261 13.3293 20.4354 13.6391C20.8836 13.2519 21.3992 12.9818 21.9412 12.8296C21.3295 12.4794 20.9173 11.8204 20.9173 11.065C20.9173 9.94295 21.827 9.0333 22.9491 9.0333C24.0712 9.0333 24.9808 9.94295 24.9808 11.065C24.9808 11.8041 24.5862 12.4511 23.9961 12.8066C24.6416 12.9728 25.2529 13.3056 25.7617 13.8061C26.3485 14.3833 26.739 15.1303 26.8782 15.9415L27.0126 16.7252L27.0206 16.7717L27.0246 16.8187L27.1858 18.6893L27.1866 18.6993L27.1873 18.7094L27.3044 20.4304C27.337 20.4326 27.3723 20.4346 27.4104 20.4364C27.7961 20.4543 28.2307 20.4435 28.7617 20.4304C28.9274 20.4263 29.1025 20.422 29.2884 20.4182ZM28.3028 22.6866C27.9281 22.6925 27.586 22.6933 27.3064 22.6803C26.9334 22.663 26.476 22.6195 26.0838 22.4527C25.8728 22.3629 25.6172 22.2115 25.4138 21.9497C25.2432 21.7301 25.1474 21.4816 25.1142 21.2295L25.1073 21.23L24.9468 18.872L24.7906 17.0585L24.6642 16.3213C24.6046 15.9742 24.4375 15.6545 24.1864 15.4075C23.5405 14.7721 22.4925 14.7669 21.833 15.4036C21.6016 15.6271 21.4381 15.9122 21.3619 16.2213L21.4801 16.916L21.4824 16.93L21.4845 16.9441L21.7168 18.5553L21.7173 18.5587L21.8892 19.7777L21.9002 19.8558V19.9346V21.1257H21.9004C21.9004 21.6008 21.7303 22.0667 21.3626 22.4051C21.0107 22.7289 20.5693 22.8509 20.1747 22.8361C19.3976 22.807 18.5481 22.2065 18.5333 21.1537H18.5331V19.9907V19.9124L18.5439 19.8348L18.7541 18.3355L18.7541 18.3355L18.7553 18.327L18.9865 16.7647L18.9883 16.7527L18.9903 16.7407L19.081 16.2079L19.0664 16.122C18.9549 15.4666 18.6331 14.8586 18.1529 14.3948C16.9386 13.2223 15.0059 13.2279 13.8102 14.4043C13.3861 14.8216 13.0918 15.3511 12.9606 15.9291C12.9724 15.9842 12.9831 16.0396 12.9926 16.0953L13.1246 16.8717L13.1314 16.9113L13.1353 16.9513L13.2876 18.5165L13.4604 19.8436L13.4698 19.9158V19.9886V21.2518H13.4722C13.4722 21.7247 13.3041 22.1899 12.938 22.5288C12.587 22.8538 12.1455 22.9772 11.7497 22.9623C10.968 22.9328 10.1268 22.3267 10.1121 21.2798H10.1047V19.9486V19.8724L10.115 19.7969L10.2831 18.5639L10.2831 18.5638L10.5057 16.9316L10.5083 16.9124L10.5115 16.8934L10.6538 16.0641C10.5653 15.867 10.4393 15.6866 10.2819 15.5347C9.62251 14.898 8.57449 14.9031 7.92862 15.5385C7.67752 15.7855 7.51039 16.1052 7.45084 16.4524L7.32411 17.1911L7.21101 18.4618L7.08736 20.9622V21.2427H7.07764C7.06269 21.5177 6.97921 21.8038 6.79469 22.0616C6.59679 22.3381 6.33952 22.5026 6.11592 22.6007C5.70919 22.7791 5.24297 22.8098 4.89175 22.8169C4.54388 22.8239 4.14601 22.8065 3.75287 22.7882C6.14194 27.0893 10.731 29.9999 15.9999 29.9999C21.3102 29.9999 25.9299 27.0434 28.3028 22.6866ZM15.9853 8.81264C15.817 8.81264 15.6805 8.94909 15.6805 9.11741C15.6805 9.28573 15.817 9.42218 15.9853 9.42218C16.1536 9.42218 16.2901 9.28573 16.2901 9.11741C16.2901 8.94909 16.1536 8.81264 15.9853 8.81264ZM22.9491 10.8096C22.808 10.8096 22.6936 10.924 22.6936 11.065C22.6936 11.2061 22.808 11.3205 22.9491 11.3205C23.0901 11.3205 23.2045 11.2061 23.2045 11.065C23.2045 10.924 23.0901 10.8096 22.9491 10.8096ZM27.3471 21.0237H27.3448L27.3369 20.9082C27.3439 20.9486 27.3471 20.9875 27.3471 21.0237ZM4.84102 21.1507V21.0613C4.83587 21.0944 4.83379 21.1247 4.83379 21.1507H4.84102ZM8.76556 11.1491C8.76556 11.008 8.87993 10.8937 9.021 10.8937C9.16208 10.8937 9.27644 11.008 9.27644 11.1491C9.27644 11.2902 9.16208 11.4046 9.021 11.4046C8.87993 11.4046 8.76556 11.2902 8.76556 11.1491Z"
|
||||
fill="url(#paint0_linear_1_116)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_1_116"
|
||||
x1="7.69527"
|
||||
y1="22.0583"
|
||||
x2="21.1597"
|
||||
y2="11.4392"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#056CF2" />
|
||||
<stop offset="1" stopColor="#4ECCC6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<div className={classes.boxTip}>{t("vocespace")}</div>
|
||||
</NavLink>
|
||||
) : (
|
||||
<a
|
||||
href={"https://voce.chat#download"}
|
||||
target={"_blank"}
|
||||
rel="noreferrer"
|
||||
className={classes.box}
|
||||
>
|
||||
<IconDownload className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>{t("download")}</div>
|
||||
</a>
|
||||
)}
|
||||
{/* {!server.upgraded && (
|
||||
<>
|
||||
<a
|
||||
href={"https://voce.chat#download"}
|
||||
@@ -116,12 +213,23 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
<div className={classes.boxTip}>{t("help")}</div>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
<a
|
||||
href={"https://doc.voce.chat"}
|
||||
target={"_blank"}
|
||||
rel="noreferrer"
|
||||
className={classes.box}
|
||||
>
|
||||
<IconAsk className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>{t("help")}</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{createChannelVisible && (
|
||||
<ChannelModal personal={true} closeModal={toggleChannelModalVisible} />
|
||||
)}
|
||||
|
||||
{userListVisible && <UsersModal closeModal={toggleUserListVisible} />}
|
||||
{inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
@@ -8,6 +9,7 @@ interface Props {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
compact?: boolean;
|
||||
footer?: ReactNode;
|
||||
}
|
||||
|
||||
const StyledModal: FC<Props> = ({
|
||||
@@ -16,7 +18,8 @@ const StyledModal: FC<Props> = ({
|
||||
description = "",
|
||||
buttons,
|
||||
children,
|
||||
className
|
||||
className,
|
||||
footer,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@@ -34,6 +37,7 @@ const StyledModal: FC<Props> = ({
|
||||
)}
|
||||
{children}
|
||||
{buttons && <div className="pt-4 w-full flex justify-end gap-4 items-center">{buttons}</div>}
|
||||
{footer && footer}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+39
-15
@@ -8,16 +8,27 @@ import {
|
||||
useGetFirebaseConfigQuery,
|
||||
useGetLoginConfigQuery,
|
||||
useGetSMTPConfigQuery,
|
||||
useGetVocespaceConfigQuery,
|
||||
useUpdateAgoraConfigMutation,
|
||||
useUpdateFirebaseConfigMutation,
|
||||
useUpdateLoginConfigMutation,
|
||||
useUpdateSMTPConfigMutation
|
||||
useUpdateSMTPConfigMutation,
|
||||
useUpdateVocespaceConfigMutation,
|
||||
} from "@/app/services/server";
|
||||
import { AgoraConfig, FirebaseConfig, LoginConfig, SMTPConfig } from "@/types/server";
|
||||
import {
|
||||
AgoraConfig,
|
||||
FirebaseConfig,
|
||||
LoginConfig,
|
||||
SMTPConfig,
|
||||
VocespaceConfig,
|
||||
} from "@/types/server";
|
||||
|
||||
// config: smtp agora login firebase
|
||||
type ConfigType = "smtp" | "agora" | "login" | "firebase";
|
||||
type ConfigMap = Record<ConfigType, AgoraConfig | FirebaseConfig | LoginConfig | SMTPConfig>;
|
||||
type ConfigType = "smtp" | "agora" | "login" | "firebase" | "vocespace";
|
||||
type ConfigMap = Record<
|
||||
ConfigType,
|
||||
AgoraConfig | FirebaseConfig | LoginConfig | SMTPConfig | VocespaceConfig
|
||||
>;
|
||||
type valuesOf<T> = T[keyof T];
|
||||
let originalValue: valuesOf<ConfigMap> | undefined = undefined;
|
||||
// type valueOf<T,config as ConfigType> = T[config];
|
||||
@@ -31,21 +42,29 @@ export default function useConfig(config: keyof ConfigMap = "smtp") {
|
||||
useUpdateSMTPConfigMutation();
|
||||
const [updateAgoraConfig, { isSuccess: AgoraUpdated, isLoading: AgoraUpdating }] =
|
||||
useUpdateAgoraConfigMutation();
|
||||
const [updateVocespaceConfig, { isSuccess: VocespaceUpdated, isLoading: VocespaceUpdating }] =
|
||||
useUpdateVocespaceConfigMutation();
|
||||
const [updateFirebaseConfig, { isSuccess: FirebaseUpdated, isLoading: FirebaseUpdating }] =
|
||||
useUpdateFirebaseConfigMutation();
|
||||
const { refetch: getAgoraConfig, data: agoraConfig } = useGetAgoraConfigQuery(undefined, {
|
||||
skip: config !== "agora"
|
||||
skip: config !== "agora",
|
||||
});
|
||||
const { refetch: getVocespaceConfig, data: vocespaceConfig } = useGetVocespaceConfigQuery(
|
||||
undefined,
|
||||
{
|
||||
skip: config !== "vocespace",
|
||||
}
|
||||
);
|
||||
const { refetch: getLoginConfig, data: loginConfig } = useGetLoginConfigQuery(undefined, {
|
||||
skip: config !== "login"
|
||||
skip: config !== "login",
|
||||
});
|
||||
const { refetch: getSMTPConfig, data: smtpConfig } = useGetSMTPConfigQuery(undefined, {
|
||||
skip: config !== "smtp"
|
||||
skip: config !== "smtp",
|
||||
});
|
||||
const { refetch: getFirebaseConfig, data: firebaseConfig } = useGetFirebaseConfigQuery(
|
||||
undefined,
|
||||
{
|
||||
skip: config !== "firebase"
|
||||
skip: config !== "firebase",
|
||||
}
|
||||
);
|
||||
|
||||
@@ -53,25 +72,29 @@ export default function useConfig(config: keyof ConfigMap = "smtp") {
|
||||
login: updateLoginConfig,
|
||||
smtp: updateSMTPConfig,
|
||||
agora: updateAgoraConfig,
|
||||
firebase: updateFirebaseConfig
|
||||
firebase: updateFirebaseConfig,
|
||||
vocespace: updateVocespaceConfig,
|
||||
};
|
||||
const requests = {
|
||||
smtp: getSMTPConfig,
|
||||
agora: getAgoraConfig,
|
||||
firebase: getFirebaseConfig,
|
||||
login: getLoginConfig
|
||||
login: getLoginConfig,
|
||||
vocespace: getVocespaceConfig,
|
||||
};
|
||||
const updates = {
|
||||
login: LoginUpdated,
|
||||
smtp: SMTPUpdated,
|
||||
agora: AgoraUpdated,
|
||||
firebase: FirebaseUpdated
|
||||
firebase: FirebaseUpdated,
|
||||
vocespace: VocespaceUpdated,
|
||||
};
|
||||
const updatings = {
|
||||
login: LoginUpdating,
|
||||
smtp: SMTPUpdating,
|
||||
agora: AgoraUpdating,
|
||||
firebase: FirebaseUpdating
|
||||
firebase: FirebaseUpdating,
|
||||
vocespace: VocespaceUpdating,
|
||||
};
|
||||
const updateConfig = updateFns[config];
|
||||
const refetch = requests[config];
|
||||
@@ -98,12 +121,12 @@ export default function useConfig(config: keyof ConfigMap = "smtp") {
|
||||
}
|
||||
}, [updated]);
|
||||
useEffect(() => {
|
||||
const _config = smtpConfig || firebaseConfig || loginConfig || agoraConfig;
|
||||
const _config = smtpConfig || firebaseConfig || loginConfig || agoraConfig || vocespaceConfig;
|
||||
if (_config) {
|
||||
originalValue = _config;
|
||||
setValues(_config);
|
||||
}
|
||||
}, [smtpConfig, firebaseConfig, loginConfig, agoraConfig]);
|
||||
}, [smtpConfig, firebaseConfig, loginConfig, agoraConfig, vocespaceConfig]);
|
||||
useEffect(() => {
|
||||
// 空对象
|
||||
if (!values || Object.keys(values).length == 0) return;
|
||||
@@ -124,6 +147,7 @@ export default function useConfig(config: keyof ConfigMap = "smtp") {
|
||||
agoraConfig,
|
||||
values,
|
||||
setValues,
|
||||
toggleEnable
|
||||
toggleEnable,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
|
||||
dispatch(
|
||||
updateChannelVisibleAside({
|
||||
id: cid,
|
||||
aside: visibleAside !== "members" ? "members" : null
|
||||
aside: visibleAside !== "members" ? "members" : null,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,36 +2,62 @@ import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { shallowEqual, useDispatch } from "react-redux";
|
||||
|
||||
import { useGetAgoraStatusQuery } from "@/app/services/server";
|
||||
import { useGetAgoraStatusQuery, useGetVocespaceConfigQuery } from "@/app/services/server";
|
||||
import { updateChannelVisibleAside, updateDMVisibleAside } from "@/app/slices/footprint";
|
||||
import { updateCallInfo } from "@/app/slices/voice";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { ChatContext } from "@/types/common";
|
||||
import Tooltip from "@/components/Tooltip";
|
||||
import { useVoice } from "@/components/Voice";
|
||||
import { isInIframe } from "@/utils";
|
||||
import { compareVersion, isInIframe } from "@/utils";
|
||||
import IconHeadphone from "@/assets/icons/headphone.svg";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import useSendMessage from "@/hooks/useSendMessage";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { VocespaceConfig } from "@/types/server";
|
||||
|
||||
type Props = {
|
||||
context?: ChatContext;
|
||||
id: number;
|
||||
};
|
||||
|
||||
const isIframe = isInIframe();
|
||||
const VoiceChat = ({ id, context = "channel" }: Props) => {
|
||||
const [chatType, setChatType] = useState<"agora" | "vocespace">("vocespace");
|
||||
const currentVersion = useAppSelector((store) => store.server.version, shallowEqual);
|
||||
const showVoceSpace = useMemo(() => {
|
||||
return compareVersion(currentVersion, "0.5.6") >= 0;
|
||||
}, [currentVersion]);
|
||||
|
||||
const { joinVoice, joined, joining = false, joinedAtThisContext } = useVoice({ id, context });
|
||||
const dispatch = useDispatch();
|
||||
const loginUid = useAppSelector((store) => store.authData.user?.uid ?? 0, shallowEqual);
|
||||
const visibleAside = useAppSelector(
|
||||
(store) => (context == "channel" ? store.footprint.channelAsides[id] : null),
|
||||
(store) => (context == "channel" ? "voice" : null),
|
||||
shallowEqual
|
||||
);
|
||||
|
||||
const voiceList = useAppSelector((store) => store.voice.list, shallowEqual);
|
||||
const { data: enabled } = useGetAgoraStatusQuery();
|
||||
const [vocespaceConfig, setVoceSpaceConfig] = useState<VocespaceConfig | undefined>(undefined);
|
||||
const { t } = useTranslation("chat");
|
||||
const { data: conf } = showVoceSpace ? useGetVocespaceConfigQuery() : { data: undefined };
|
||||
|
||||
useEffect(() => {
|
||||
if (!showVoceSpace) {
|
||||
setChatType("agora");
|
||||
} else {
|
||||
if (conf) {
|
||||
setVoceSpaceConfig(conf);
|
||||
setChatType("vocespace");
|
||||
}
|
||||
}
|
||||
}, [showVoceSpace]);
|
||||
|
||||
const toggleDashboard = () => {
|
||||
const data = {
|
||||
id,
|
||||
aside: visibleAside == "voice" ? null : ("voice" as const)
|
||||
aside: visibleAside == "voice" ? null : ("voice" as const),
|
||||
};
|
||||
dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data));
|
||||
};
|
||||
@@ -43,7 +69,7 @@ const VoiceChat = ({ id, context = "channel" }: Props) => {
|
||||
joinVoice();
|
||||
const data = {
|
||||
id,
|
||||
aside: "voice" as const
|
||||
aside: "voice" as const,
|
||||
};
|
||||
dispatch(context == "channel" ? updateChannelVisibleAside(data) : updateDMVisibleAside(data));
|
||||
// 实时显示calling box
|
||||
@@ -55,19 +81,57 @@ const VoiceChat = ({ id, context = "channel" }: Props) => {
|
||||
// todo
|
||||
toast.error("Voice is not supported in iframe");
|
||||
};
|
||||
if (loginUid == 0 || !enabled) return null;
|
||||
if (loginUid == 0) return null;
|
||||
const visible = visibleAside == "voice";
|
||||
const memberCount = voiceList.find((v) => v.context == context && v.id == id)?.memberCount ?? 0;
|
||||
const badgeClass = `absolute -top-2 -right-2 w-4 h-4 rounded-full bg-primary-400 text-white `;
|
||||
const { sendMessage } = useSendMessage({ context, from: loginUid, to: id });
|
||||
const replying_mid = useAppSelector(
|
||||
(store) => store.message.replying[`${context}_${id}`],
|
||||
shallowEqual
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const sendVoceSpaceMsg = async (url: string) => {
|
||||
await sendMessage({
|
||||
reply_mid: replying_mid,
|
||||
type: "text",
|
||||
content: `Join Vocespace Meeting: ${url}`,
|
||||
from_uid: loginUid,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSendVocespaceRequest = async () => {
|
||||
if (vocespaceConfig && vocespaceConfig.state && vocespaceConfig.state === "undeployed") {
|
||||
await sendVoceSpaceMsg(`https://vocespace.com/${context}_${id}`);
|
||||
} else if (vocespaceConfig && vocespaceConfig?.enabled && vocespaceConfig.state === "success") {
|
||||
let url = vocespaceConfig.url;
|
||||
if (url.includes(":7880")) {
|
||||
url = `http://${url.replace(":7880", ":3008")}/${context}_${id}`;
|
||||
} else {
|
||||
url = `https://${url}/${context}_${id}`;
|
||||
}
|
||||
await sendVoceSpaceMsg(url);
|
||||
} else {
|
||||
// 跳转到/setting/video
|
||||
navigate("/setting/video");
|
||||
}
|
||||
};
|
||||
const handleOnClick = async () => {
|
||||
if (chatType === "agora" && enabled) {
|
||||
return isIframe ? handleInIframe() : joinedAtThisContext ? toggleDashboard() : handleJoin();
|
||||
} else {
|
||||
// 向当前频道中发送Vocespace链接
|
||||
await handleSendVocespaceRequest();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip disabled={visible} tip={t("voice")} placement="left">
|
||||
<li className={`relative group`}>
|
||||
<IconHeadphone
|
||||
className={visible ? "fill-gray-600" : "fill-gray-500"}
|
||||
role="button"
|
||||
onClick={isIframe ? handleInIframe : joinedAtThisContext ? toggleDashboard : handleJoin}
|
||||
/>
|
||||
{visible ? null : (
|
||||
<IconHeadphone className={"fill-gray-500"} role="button" onClick={handleOnClick} />
|
||||
{/* {visible ? null : (
|
||||
<>
|
||||
{memberCount > 0 && (
|
||||
<span
|
||||
@@ -80,7 +144,19 @@ const VoiceChat = ({ id, context = "channel" }: Props) => {
|
||||
<em className="not-italic">+</em>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
)} */}
|
||||
<>
|
||||
{memberCount > 0 && (
|
||||
<span
|
||||
className={`${badgeClass} flex-center font-bold text-[10px] group-hover:invisible`}
|
||||
>
|
||||
{memberCount}
|
||||
</span>
|
||||
)}
|
||||
<span className={`${badgeClass} text-xs flex-center invisible group-hover:visible`}>
|
||||
<em className="not-italic">+</em>
|
||||
</span>
|
||||
</>
|
||||
</li>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -72,7 +72,7 @@ const LicensePriceListModal: FC<Props> = ({ closeModal }) => {
|
||||
user_limit: Number(user_limit),
|
||||
expire: type == "subscription" ? getExpireDay(sub_dur) : getExpireDay("year"),
|
||||
// 本地,则*通配符
|
||||
domain: host.startsWith("localhost") ? "*" : host
|
||||
domain: host.startsWith("localhost") ? "*" : host,
|
||||
};
|
||||
console.log(metadata);
|
||||
// return;
|
||||
@@ -82,7 +82,7 @@ const LicensePriceListModal: FC<Props> = ({ closeModal }) => {
|
||||
priceId,
|
||||
metadata,
|
||||
cancel_url: location.href,
|
||||
success_url: `${location.origin}/#/cb/payment_success`
|
||||
success_url: `${location.origin}/#/cb/payment_success`,
|
||||
});
|
||||
if ("error" in resp) {
|
||||
toast.error("Payment link initialized failed!");
|
||||
@@ -103,13 +103,18 @@ const LicensePriceListModal: FC<Props> = ({ closeModal }) => {
|
||||
setPopUpVisible((prev) => !prev);
|
||||
};
|
||||
const handleTalk = () => {
|
||||
window.open("https://calendly.com/hansu", "_blank");
|
||||
window.open("https://buy.stripe.com/bJeaEX9ex2PUer2aLe6c00O", "_blank");
|
||||
};
|
||||
const isBooking = selectPrice.includes("booking");
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
footer={
|
||||
<div className="text-sm text-gray-400 dark:text-gray-100 mt-3">
|
||||
{t("vocespace.prerequisite.4")}
|
||||
</div>
|
||||
}
|
||||
// className="!min-w-[480px]"
|
||||
title={t("license.renew")}
|
||||
description={t("license.renew_select")}
|
||||
@@ -119,7 +124,12 @@ const LicensePriceListModal: FC<Props> = ({ closeModal }) => {
|
||||
{ct("action.cancel")}
|
||||
</Button>
|
||||
{isBooking ? (
|
||||
<Button onClick={handleTalk}>Booking a meeting!</Button>
|
||||
<Button
|
||||
className="text-sm text-white bg-primary-400 break-keep shadow rounded-lg px-3.5 py-2.5 md:hover:bg-primary-500 active:bg-primary-500 disabled:bg-gray-300"
|
||||
onClick={handleTalk}
|
||||
>
|
||||
{t("license.renew")}
|
||||
</Button>
|
||||
) : (
|
||||
<Tippy
|
||||
visible={popUpVisible}
|
||||
@@ -163,9 +173,6 @@ const LicensePriceListModal: FC<Props> = ({ closeModal }) => {
|
||||
{" "}
|
||||
{t("license.renew")}
|
||||
</button>
|
||||
{/* <Button >
|
||||
{t("license.renew")}
|
||||
</Button> */}
|
||||
</Tippy>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeEvent } from "react";
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { AgoraConfig } from "@/types/server";
|
||||
@@ -9,10 +9,32 @@ import Toggle from "@/components/styled/Toggle";
|
||||
import useConfig from "@/hooks/useConfig";
|
||||
import HowToTip from "./HowToTip";
|
||||
import { ConfigTip } from "@/components/ConfigTip";
|
||||
import { ConfigVocespace } from "./Vocespace";
|
||||
import clsx from "clsx";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import { useAppSelector } from "@/app/store";
|
||||
import { compareVersion } from "@/utils";
|
||||
|
||||
export default function ConfigAgora() {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "agora" });
|
||||
const [isAgora, setIsAgora] = useState(false);
|
||||
const currentVersion = useAppSelector((store) => store.server.version, shallowEqual);
|
||||
const showVoceSpace = useMemo(() => {
|
||||
return compareVersion(currentVersion, "0.5.6") >= 0;
|
||||
}, [currentVersion]);
|
||||
useEffect(() => {
|
||||
if (showVoceSpace) {
|
||||
setIsAgora(false);
|
||||
} else {
|
||||
setIsAgora(true);
|
||||
}
|
||||
}, [showVoceSpace]);
|
||||
const { changed, reset, values, setValues, toggleEnable, updateConfig } = useConfig("agora");
|
||||
const getClass = (selected: boolean) =>
|
||||
clsx(
|
||||
`cursor-pointer p-2 box-border flex-center`,
|
||||
selected && `border border-solid border-primary-400 shadow rounded-lg`
|
||||
);
|
||||
const handleUpdate = () => {
|
||||
// const { token_url, description } = values;
|
||||
const _values = values as AgoraConfig;
|
||||
@@ -30,6 +52,7 @@ export default function ConfigAgora() {
|
||||
return { ...prev, [type]: newValue };
|
||||
});
|
||||
};
|
||||
|
||||
if (!values) return null;
|
||||
const {
|
||||
url,
|
||||
@@ -38,95 +61,114 @@ export default function ConfigAgora() {
|
||||
app_certificate,
|
||||
customer_id,
|
||||
customer_secret,
|
||||
enabled = false
|
||||
enabled = false,
|
||||
} = values as AgoraConfig;
|
||||
const _url = url || "https://api.agora.io";
|
||||
return (
|
||||
<div className="setting-container">
|
||||
<ConfigTip title={t("desc")} desc={t("sub_desc")} />
|
||||
<div className="inputs">
|
||||
<div className="input row">
|
||||
<Label className="flex items-center gap-2">
|
||||
Enable
|
||||
<HowToTip link="https://doc.voce.chat/setting/setting-agora" text={t("how_to")} />
|
||||
</Label>
|
||||
<Toggle onClick={toggleEnable} checked={enabled}></Toggle>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="url">Agora URL</Label>
|
||||
<Input
|
||||
disabled={!enabled}
|
||||
data-type="url"
|
||||
onChange={handleChange}
|
||||
value={_url}
|
||||
name="url"
|
||||
placeholder="Agora URL"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="project_id">Project ID</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="project_id"
|
||||
onChange={handleChange}
|
||||
value={project_id}
|
||||
name="project_id"
|
||||
placeholder="Project ID"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="app_id">App ID</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="app_id"
|
||||
onChange={handleChange}
|
||||
value={app_id}
|
||||
name="app_id"
|
||||
placeholder="APP ID"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="app_certificate">APP Certificate</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="app_certificate"
|
||||
onChange={handleChange}
|
||||
value={app_certificate}
|
||||
name="app_certificate"
|
||||
placeholder="APP Certificate"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="customer_id">Customer ID</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="customer_id"
|
||||
onChange={handleChange}
|
||||
value={customer_id}
|
||||
name="customer_id"
|
||||
placeholder="Customer ID for RESTful API"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="customer_secret">Customer Secret</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="customer_secret"
|
||||
onChange={handleChange}
|
||||
value={customer_secret}
|
||||
name="customer_secret"
|
||||
placeholder="Customer Secret for RESTful API"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{showVoceSpace && (
|
||||
<ul
|
||||
className={`hidden md:flex border border-solid dark:border-gray-400 shadow rounded-lg box-border`}
|
||||
style={{ width: "fit-content" }}
|
||||
>
|
||||
<li className={getClass(!isAgora)} data-view={"item"} onClick={() => setIsAgora(false)}>
|
||||
<Label>Vocespace</Label>
|
||||
</li>
|
||||
<li className={getClass(isAgora)} data-view={"grid"} onClick={() => setIsAgora(true)}>
|
||||
<Label>Agora</Label>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
|
||||
{/* <button onClick={handleUpdate} className="btn">update</button> */}
|
||||
{isAgora ? (
|
||||
<>
|
||||
<ConfigTip title={t("desc")} desc={t("sub_desc")} />
|
||||
<div className="inputs">
|
||||
<div className="input row">
|
||||
<Label className="flex items-center gap-2">
|
||||
Enable
|
||||
<HowToTip link="https://doc.voce.chat/setting/setting-agora" text={t("how_to")} />
|
||||
</Label>
|
||||
<Toggle onClick={toggleEnable} checked={enabled}></Toggle>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="url">Agora URL</Label>
|
||||
<Input
|
||||
disabled={!enabled}
|
||||
data-type="url"
|
||||
onChange={handleChange}
|
||||
value={_url}
|
||||
name="url"
|
||||
placeholder="Agora URL"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="project_id">Project ID</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="project_id"
|
||||
onChange={handleChange}
|
||||
value={project_id}
|
||||
name="project_id"
|
||||
placeholder="Project ID"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="app_id">App ID</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="app_id"
|
||||
onChange={handleChange}
|
||||
value={app_id}
|
||||
name="app_id"
|
||||
placeholder="APP ID"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="app_certificate">APP Certificate</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="app_certificate"
|
||||
onChange={handleChange}
|
||||
value={app_certificate}
|
||||
name="app_certificate"
|
||||
placeholder="APP Certificate"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="customer_id">Customer ID</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="customer_id"
|
||||
onChange={handleChange}
|
||||
value={customer_id}
|
||||
name="customer_id"
|
||||
placeholder="Customer ID for RESTful API"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="customer_secret">Customer Secret</Label>
|
||||
<Input
|
||||
spellCheck={false}
|
||||
disabled={!enabled}
|
||||
data-type="customer_secret"
|
||||
onChange={handleChange}
|
||||
value={customer_secret}
|
||||
name="customer_secret"
|
||||
placeholder="Customer Secret for RESTful API"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
|
||||
</>
|
||||
) : (
|
||||
<ConfigVocespace></ConfigVocespace>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { ConfigTip } from "@/components/ConfigTip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import HowToTip from "./HowToTip";
|
||||
import Toggle from "@/components/styled/Toggle";
|
||||
import Input from "@/components/styled/Input";
|
||||
import Label from "@/components/styled/Label";
|
||||
import SaveTip from "@/components/SaveTip";
|
||||
import useConfig from "@/hooks/useConfig";
|
||||
import { VocespaceConfig } from "@/types/server";
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import { isNull } from "lodash";
|
||||
|
||||
export function ConfigVocespace() {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "vocespace" });
|
||||
const [fetchData, setFetchData] = useState<VocespaceConfig | null>(null);
|
||||
useEffect(() => {
|
||||
if (!isNull(fetchData?.state) && fetchData?.state !== "in_progress") {
|
||||
// 停止轮询
|
||||
if (interval.current) {
|
||||
clearInterval(interval.current);
|
||||
}
|
||||
}
|
||||
}, [fetchData]);
|
||||
const { changed, reset, values, setValues, toggleEnable, updateConfig, refetch } =
|
||||
useConfig("vocespace");
|
||||
// 定时器,用来每隔2分钟获取一次最新的配置状态,只要发现有state字段就能知道自动化部署是否成功,没有则继续轮询
|
||||
const interval = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleUpdate = async () => {
|
||||
const _values = values as VocespaceConfig;
|
||||
if (_values.url.trim() === "" && _values.enabled) {
|
||||
alert("Custom domain is required when enabling Vocespace.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 后端快速返回结果,只要检测环境可以执行就立即返回成功,不然会一直pending等待,导致前端超时
|
||||
const { error } = await updateConfig(_values);
|
||||
if (error) {
|
||||
alert(`Auto Deploy Failed: ${error}`);
|
||||
} else {
|
||||
// 创建一个轮询定时器,每隔2分钟获取一次最新的配置状态
|
||||
if (interval.current) {
|
||||
clearInterval(interval.current);
|
||||
}
|
||||
// 先fetch一次,避免等待2分钟的空档期
|
||||
const { data } = await refetch();
|
||||
setFetchData(data as VocespaceConfig);
|
||||
interval.current = setInterval(async () => {
|
||||
console.warn("Fetching Vocespace Config for deployment status...");
|
||||
const { data } = await refetch();
|
||||
setFetchData(data as VocespaceConfig);
|
||||
}, 2 * 60 * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (evt: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const newValue = evt.target.value;
|
||||
const { type = "" } = evt.target.dataset;
|
||||
setValues((prev) => {
|
||||
if (!prev) return prev;
|
||||
return { ...prev, [type]: newValue };
|
||||
});
|
||||
};
|
||||
if (!values) return null;
|
||||
const { url, license, enabled = false, password, state } = values as VocespaceConfig;
|
||||
|
||||
return (
|
||||
<div className="setting-container">
|
||||
<ConfigTip title={t("desc")} desc={`${t("sub_desc")}`} />
|
||||
<div className="inputs">
|
||||
<div className="input row">
|
||||
<Label style={{ color: "#fff", display: "flex", flexWrap: "wrap" }}>
|
||||
<Label style={{ display: "inline", width: "100%" }}>Enable</Label>
|
||||
{/* <HowToTip link="https://doc.vocespace.com/zh/doc/guide/overview" text={t("how_to")} /> */}
|
||||
<div
|
||||
className="text-gray-400"
|
||||
style={{
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{t("how_to")}
|
||||
</div>
|
||||
</Label>
|
||||
|
||||
<Toggle onClick={toggleEnable} checked={enabled}></Toggle>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="list-disc list-inside text-sm" style={{ fontWeight: 700 }}>
|
||||
{t("prerequisite.0")}
|
||||
</Label>
|
||||
<ol
|
||||
className="list-disc list-inside text-sm text-gray-400 mb-4"
|
||||
style={{ fontSize: 12, margin: 0 }}
|
||||
>
|
||||
<li>{t("prerequisite.1")}</li>
|
||||
<li>{t("prerequisite.2")}</li>
|
||||
<li>{t("prerequisite.3")}</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="input">
|
||||
<div className="flex flex-col text-sm">
|
||||
<Label htmlFor="url" style={{ position: "relative", width: "100%" }}>
|
||||
{url.trim() === "" && enabled && (
|
||||
<span
|
||||
style={{ color: "red", position: "absolute", top: -4, left: -8, fontSize: 16 }}
|
||||
>
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
Custom domain for VoceSpace
|
||||
</Label>
|
||||
<p className="text-gray-400 text-xs">{t("domain_desc")}</p>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
disabled={!enabled}
|
||||
data-type="url"
|
||||
onChange={handleChange}
|
||||
value={url}
|
||||
name="url"
|
||||
placeholder="Your Domain Pointing to the Current Server IP"
|
||||
/>
|
||||
</div>
|
||||
<div className="input">
|
||||
<Label htmlFor="password">Redis Password (Optional)</Label>
|
||||
<Input
|
||||
disabled={!enabled}
|
||||
data-type="password"
|
||||
onChange={handleChange}
|
||||
value={password}
|
||||
name="password"
|
||||
placeholder="this is optional field"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{state ? (
|
||||
<div style={{ fontSize: 12 }}>
|
||||
{state === "success" && (
|
||||
<StateDot
|
||||
color="green"
|
||||
className="text-green-400"
|
||||
word="Vocespace deployed successfully."
|
||||
/>
|
||||
)}
|
||||
{state === "in_progress" && (
|
||||
<StateDot
|
||||
color="yellow"
|
||||
className="text-yellow-400"
|
||||
word="Vocespace deployment in progress..."
|
||||
/>
|
||||
)}
|
||||
{state === "failed" && (
|
||||
<StateDot color="red" className="text-red-400" word="Vocespace deployment failed." />
|
||||
)}
|
||||
{state === "undeployed" && (
|
||||
<StateDot
|
||||
color="gray"
|
||||
className="text-gray-400"
|
||||
word="Vocespace is not deployed yet."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<StateDot color="gray" className="text-gray-400" word="Vocespace is not deployed yet." />
|
||||
)}
|
||||
<div
|
||||
className="list-disc list-inside text-sm text-gray-400 mb-4"
|
||||
style={{ fontSize: 12, margin: 0, paddingTop: 8 }}
|
||||
>
|
||||
{t("prerequisite.4")}
|
||||
</div>
|
||||
</div>
|
||||
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
|
||||
{/* <button onClick={handleUpdate} className="btn">update</button> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StateDot({ color, className, word }: { color: string; className?: string; word: string }) {
|
||||
return (
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12 }}
|
||||
className={className}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
height: 12,
|
||||
width: 12,
|
||||
backgroundColor: color,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
></div>
|
||||
{word}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import MyAccount from "./MyAccount";
|
||||
import Overview from "./Overview";
|
||||
import Widget from "./Widget";
|
||||
import { shallowEqual } from "react-redux";
|
||||
import { ConfigVocespace } from "./config/Vocespace";
|
||||
|
||||
const dataManagementNav = {
|
||||
name: "data_management",
|
||||
@@ -54,7 +55,7 @@ const navs = [
|
||||
component: <ConfigFirebase />,
|
||||
},
|
||||
{
|
||||
name: "agora",
|
||||
name: "video",
|
||||
component: <ConfigAgora />,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -41,6 +41,15 @@ export interface AgoraConfig {
|
||||
customer_id: string;
|
||||
customer_secret: string;
|
||||
}
|
||||
|
||||
export interface VocespaceConfig {
|
||||
enabled: boolean;
|
||||
password: string;
|
||||
url: string;
|
||||
license: string;
|
||||
state: "in_progress" | "success" | "failed" | "undeployed" | "";
|
||||
}
|
||||
|
||||
export interface AgoraVoicingListResponse {
|
||||
success: boolean;
|
||||
data: {
|
||||
|
||||
Reference in New Issue
Block a user