♻️ refactor(channels): rebuild channel create/edit drawer with modular sections and improved form UX

Restructure the default-theme channel create/edit experience to align with
classic frontend behavior, modern form UX patterns, and the project's Base UI
design system.

Channel editor architecture:
- Split the monolithic channel mutate drawer into focused section components
  (basic, API access, auth, models, advanced) with shared drawer layout
  primitives
- Extract submission, toast handling, and react-query cache invalidation into
  `useChannelMutateForm`
- Add a dedicated loading skeleton for channel detail fetch during edit mode
- Remove the top-level configuration summary block per UX feedback

Form validation and data handling:
- Strengthen `channel-form` Zod schema with JSON, model mapping, status code
  mapping, Codex credential, and Vertex AI key refinements
- Move type-specific conditional validation into `superRefine`
- Normalize base URL formatting and tighten model mapping value validation

Model mapping editor:
- Add Visual/JSON tabbed editing with inline JSON and duplicate-key feedback
- Improve accessibility for icon-only actions and add model suggestion datalists

MultiSelect component:
- Replace the custom cmdk-based implementation with Base UI Combobox chips
- Align focus, border, ring, disabled, and invalid states with standard Input
  styling via `ComboboxChips`
- Preserve existing API (`options`, `selected`, `onChange`, `allowCreate`,
  `createLabel`) for all current callers
- Support inline custom value creation, comma/newline batch input, searchable
  options, portal-based dropdown positioning, and chip removal

Models & groups UX:
- Integrate manual custom model entry directly into the model MultiSelect
- Remove the separate manual model input/button block
- Keep selected-model count and existing model-mapping guardrail behavior

i18n:
- Add and sync translation keys for new editor sections, validation messages,
  model mapping UI, and MultiSelect empty/create labels across en, zh, fr, ja,
  ru, and vi
- Remove obsolete keys tied to the deprecated summary and manual model entry UI

Affected areas:
- `web/default/src/features/channels/components/drawers/`
- `web/default/src/features/channels/hooks/use-channel-mutate-form.ts`
- `web/default/src/features/channels/lib/channel-form.ts`
- `web/default/src/features/channels/lib/model-mapping-validation.ts`
- `web/default/src/features/channels/components/model-mapping-editor.tsx`
- `web/default/src/components/multi-select.tsx`
- `web/default/src/i18n/locales/*.json`
This commit is contained in:
t0ng7u
2026-05-26 01:22:49 +08:00
parent 583da45296
commit 3d850d38b6
19 changed files with 3465 additions and 2478 deletions
+34 -13
View File
@@ -37,7 +37,6 @@
"{{count}} IP(s)": "{{count}} IP",
"{{count}} log entries removed.": "{{count}} entrées de journal supprimées.",
"{{count}} minutes ago": "il y a {{count}} minutes",
"{{count}} model(s)": "{{count}} modèle(s)",
"{{count}} models": "{{count}} modèles",
"{{count}} months ago": "il y a {{count}} mois",
"{{count}} override": "{{count}} remplacement",
@@ -135,6 +134,7 @@
"Actual Model": "Modèle réel",
"Actual Model:": "Modèle réel :",
"Add": "Ajouter",
"Add \"{{value}}\"": "Ajouter \"{{value}}\"",
"Add {{title}}": "Ajouter {{title}}",
"Add a group identifier to the auto assignment list.": "Ajouter un identifiant de groupe à la liste d'affectation automatique.",
"Add a new API key by providing necessary info.": "Ajoutez une nouvelle clé API en fournissant les informations nécessaires.",
@@ -152,7 +152,7 @@
"Add condition": "Ajouter une condition",
"Add Condition": "Ajouter une condition",
"Add credits": "Ajouter des crédits",
"Add custom model(s), comma-separated": "Ajouter un ou plusieurs modèles personnalisés, séparés par des virgules",
"Add custom model \"{{value}}\"": "Ajouter le modèle personnalisé « {{value}} »",
"Add discount tier": "Ajouter un niveau de réduction",
"Add each model or tag you want to include.": "Ajoutez chaque modèle ou étiquette que vous souhaitez inclure.",
"Add FAQ": "Ajouter une FAQ",
@@ -164,6 +164,7 @@
"Add group rules": "Ajouter des règles de groupe",
"Add Mapping": "Ajouter un mappage",
"Add method": "Ajouter une méthode",
"Add missing models": "Ajouter les modèles manquants",
"Add Mode": "Ajouter un mode",
"Add model": "Ajouter un modèle",
"Add Model": "Ajouter un modèle",
@@ -192,7 +193,6 @@
"Add User": "Ajouter un utilisateur",
"Add user group": "Ajouter un groupe d'utilisateurs",
"Add your API keys, set up channels and configure access permissions": "Ajoutez vos clés API, configurez les canaux et les permissions d'accès",
"Added {{count}} custom model(s)": "{{count}} modèle(s) personnalisé(s) ajouté(s)",
"Added {{count}} model(s)": "{{count}} modèle(s) ajouté(s)",
"Added successfully": "Ajouté avec succès",
"Additional Conditions": "Conditions supplémentaires",
@@ -201,8 +201,8 @@
"Additional Limit": "Limite supplémentaire",
"Additional Limits": "Limites supplémentaires",
"Additional metered capability": "Fonctionnalité supplémentaire facturée à lusage",
"Adjust Quota": "Ajuster le quota",
"Adjust filters, then search to refresh the logs.": "Ajustez les filtres, puis lancez la recherche pour actualiser les journaux.",
"Adjust Quota": "Ajuster le quota",
"Adjust response formatting, prompt behavior, proxy, and upstream automation.": "Ajustez le formatage des réponses, le comportement des prompts, le proxy et lautomatisation amont.",
"Adjust the appearance and layout to suit your preferences.": "Ajustez l'apparence et la mise en page selon vos préférences.",
"Admin": "Administrateur",
@@ -424,6 +424,11 @@
"Audio Tokens": "Jetons audio",
"Auth configured": "Authentification configurée",
"Auth Style": "Style d'authentification",
"auth.resetPasswordConfirm.backToLogin": "Retour à la connexion",
"auth.resetPasswordConfirm.confirm": "Confirmer la réinitialisation du mot de passe",
"auth.resetPasswordConfirm.description": "Confirmez la demande de réinitialisation pour générer un nouveau mot de passe.",
"auth.resetPasswordConfirm.retry": "Réessayer ({{seconds}}s)",
"auth.resetPasswordConfirm.success": "Votre mot de passe a été réinitialisé avec succès",
"Authentication": "Authentification",
"Authenticator code": "Code d'authentification",
"Authorization Endpoint": "Point de terminaison d'autorisation",
@@ -502,6 +507,7 @@
"Base Price": "Prix de base",
"Base rate limit windows for this account.": "Fenêtres de limitation de débit de base pour ce compte.",
"Base URL": "URL de base",
"Base URL is required for this channel type": "L'URL de base est requise pour ce type de canal",
"Base URL of your Uptime Kuma instance": "URL de base de votre instance Uptime Kuma",
"Basic Authentication": "Authentification de base",
"Basic Configuration": "Configuration de base",
@@ -759,8 +765,10 @@
"Codex": "Codex",
"Codex Account & Usage": "Compte et utilisation Codex",
"Codex Authorization": "Autorisation Codex",
"Codex channels do not support batch creation": "Les canaux Codex ne prennent pas en charge la création par lot",
"Codex channels use an OAuth JSON credential as the key.": "Les canaux Codex utilisent un identifiant OAuth JSON comme clé.",
"Codex CLI Header Passthrough": "Passthrough en-tête Codex CLI",
"Codex credential must be a JSON object with access_token and account_id": "L'identifiant Codex doit être un objet JSON avec access_token et account_id",
"Cohere": "Cohere",
"Collapse": "Réduire",
"Collapse All": "Tout réduire",
@@ -867,8 +875,6 @@
"Confirm New Password": "Confirmer le nouveau mot de passe",
"Confirm password": "Confirmer le mot de passe",
"Confirm Payment": "Confirmer le paiement",
"auth.resetPasswordConfirm.confirm": "Confirmer la réinitialisation du mot de passe",
"auth.resetPasswordConfirm.description": "Confirmez la demande de réinitialisation pour générer un nouveau mot de passe.",
"Confirm Selection": "Confirmer la sélection",
"Confirm settings and finish setup": "Confirmez les paramètres et terminez la configuration",
"confirm that I bear legal responsibility arising from deployment": "confirme assumer la responsabilité juridique découlant du déploiement",
@@ -999,6 +1005,7 @@
"Create, revoke, and audit API tokens.": "Créer, révoquer et auditer les jetons API.",
"Created": "Créé",
"Created At": "Créé le",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Crée un produit Pancake dans la boutique enregistrée avec le titre et le prix de ce forfait. Waffo Pancake doit dabord être entièrement configuré dans les paramètres de paiement.",
"Creating...": "Création...",
"Creation failed": "Échec de la création",
"Credential generated": "Identifiant généré",
@@ -1122,6 +1129,7 @@
"Delete invalid redemption codes": "Supprimer les codes de rachat invalides",
"Delete Invalid Redemption Codes?": "Supprimer les codes de rachat invalides ?",
"Delete logs": "Supprimer les journaux",
"Delete mapping": "Supprimer le mappage",
"Delete Model": "Supprimer le modèle",
"Delete Models?": "Supprimer les modèles ?",
"Delete Provider": "Supprimer le fournisseur",
@@ -1249,6 +1257,8 @@
"Drawing task records": "Historique des tâches de dessin",
"Duplicate": "Dupliquer",
"Duplicate group names: {{names}}": "Noms de groupe en double : {{names}}",
"Duplicate source model mappings are not allowed": "Les mappages de modèles source en double ne sont pas autorisés",
"Duplicate source model(s): {{models}}": "Modèle(s) source en double : {{models}}",
"Duration": "Durée",
"Duration (hours)": "Durée (heures)",
"Duration Settings": "Paramètres de durée",
@@ -1935,7 +1945,7 @@
"How client credentials are sent to the token endpoint": "Comment les informations d'identification client sont envoyées au point de terminaison de jeton",
"How frequently the system tests all channels": "Fréquence à laquelle le système teste tous les canaux",
"How It Works": "Comment ça marche",
"How model mapping works": "Comment fonctionne le mappage de modèle",
"How model mapping works": "Fonctionnement du mappage des modèles",
"How much to charge for each US dollar of balance (Epay)": "Montant à facturer pour chaque dollar US de solde (Epay)",
"How this model name should match requests": "Comment ce nom de modèle doit correspondre aux requêtes",
"How to deliver the resulting image": "Comment délivrer l'image résultante",
@@ -2177,6 +2187,7 @@
"Load template...": "Charger le modèle...",
"Loader": "Chargeur",
"Loading": "Chargement",
"Loading channel details": "Chargement des détails du canal",
"Loading configuration": "Chargement de la configuration",
"Loading content settings...": "Chargement des paramètres de contenu...",
"Loading current models...": "Chargement des modèles actuels...",
@@ -2293,7 +2304,6 @@
"Minimum:": "Minimum :",
"Minor blips in the last 30 days": "Légères perturbations sur les 30 derniers jours",
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Créez une nouvelle paire ci-dessous, ou choisissez une paire existante plus bas. Cliquez sur Enregistrer lorsque vous êtes prêt.",
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Crée un produit Pancake dans la boutique enregistrée avec le titre et le prix de ce forfait. Waffo Pancake doit dabord être entièrement configuré dans les paramètres de paiement.",
"Minute": "Minute",
"minutes": "minutes",
"Missing code": "Code manquant",
@@ -2324,7 +2334,9 @@
"Model Mapping": "Mappage de modèle",
"Model Mapping (JSON)": "Mappage de modèle (JSON)",
"Model Mapping must be a JSON object like": "Le mappage de modèle doit être un objet JSON tel que",
"Model mapping must be a JSON object with string values": "Le mappage de modèles doit être un objet JSON avec des valeurs de chaîne",
"Model mapping must be valid JSON": "La cartographie des modèles doit être un JSON valide",
"Model mapping values must be strings": "Les valeurs du mappage de modèles doivent être des chaînes",
"Model name": "Nom du modèle",
"Model Name": "Nom du modèle",
"Model Name *": "Nom du modèle *",
@@ -2333,8 +2345,8 @@
"Model not found": "Modèle introuvable",
"Model performance metrics": "Indicateurs de performance des modèles",
"Model Price": "Prix du modèle",
"Model Price Not Configured": "Prix du modèle non configuré",
"Model price is not configured. Please complete model pricing in settings.": "Le prix du modèle n'est pas configuré. Veuillez compléter la tarification du modèle dans les paramètres.",
"Model Price Not Configured": "Prix du modèle non configuré",
"Model prices": "Prix des modèles",
"Model prices reset successfully": "Prix des modèles réinitialisés avec succès",
"Model Pricing": "Tarification des modèles",
@@ -2532,6 +2544,7 @@
"No Logs Found": "Aucun journal trouvé",
"No mappings configured. Click \"Add Row\" to get started.": "Aucun mappage configuré. Cliquez sur « Ajouter une ligne » pour commencer.",
"No matches found": "Aucune correspondance trouvée",
"No matching items": "Aucun élément correspondant",
"No matching results": "Aucun résultat correspondant",
"No matching rules": "Aucune règle correspondante",
"No messages yet": "Pas encore de messages",
@@ -2950,6 +2963,7 @@
"Please upload key file(s)": "Veuillez télécharger le (s) fichier(s) clé (s)",
"Please wait a moment before trying again.": "Veuillez patienter un instant avant de réessayer.",
"Please wait a moment, human check is initializing...": "Veuillez patienter un instant, la vérification humaine s'initialise...",
"Please wait before editing to avoid overwriting saved values.": "Veuillez patienter avant de modifier afin d'éviter d'écraser les valeurs enregistrées.",
"Policy JSON": "JSON de stratégie",
"Polling": "Sondage",
"Polling mode requires Redis and memory cache, otherwise performance will be significantly degraded": "Le mode d'interrogation nécessite Redis et un cache mémoire, sinon les performances seront considérablement dégradées",
@@ -2983,6 +2997,7 @@
"Prepend to Start": "Ajouter au début",
"Prepend value to array / string / object start": "Ajouter la valeur au début du tableau / chaîne / objet",
"Preserve the original field when applying this rule": "Conserver le champ original lors de lapplication de cette règle",
"Preset groups": "Groupes prédéfinis",
"Preset recharge amounts (JSON array)": "Montants de recharge prédéfinis (tableau JSON)",
"Preset recharge amounts displayed to users": "Montants de recharge prédéfinis affichés aux utilisateurs",
"Preset Template": "Modèle prédéfini",
@@ -3098,6 +3113,7 @@
"Querying...": "Recherche en cours...",
"Question": "Question",
"Queued": "En file",
"Quick actions": "Actions rapides",
"Quick insert common payment methods": "Insérer rapidement les méthodes de paiement courantes",
"Quick Range": "Plage rapide",
"Quick Setup from Preset": "Configuration rapide à partir d'un préréglage",
@@ -3219,6 +3235,7 @@
"Remaining:": "Restant :",
"Remark": "Remarque",
"Remove": "Supprimer",
"Remove {{value}}": "Retirer {{value}}",
"Remove ${{amount}}": "Supprimer ${{amount}}",
"Remove all log entries created before the selected timestamp.": "Supprimer toutes les entrées de journal créées avant l'horodatage sélectionné.",
"Remove attachment": "Supprimer la pièce jointe",
@@ -3226,6 +3243,7 @@
"Remove Duplicates": "Supprimer les doublons",
"Remove filter": "Supprimer le filtre",
"Remove functionResponse.id field": "Supprimer le champ functionResponse.id",
"Remove mapped targets": "Retirer les cibles mappées",
"Remove Models": "Supprimer des modèles",
"Remove Passkey": "Supprimer le Passkey",
"Remove Passkey?": "Supprimer la clé d'accès ?",
@@ -3329,7 +3347,6 @@
"Retain last N files": "Conserver les N derniers fichiers",
"Retention days": "Jours de rétention",
"Retry": "Réessayer",
"auth.resetPasswordConfirm.retry": "Réessayer ({{seconds}}s)",
"Retry Chain": "Chaîne de tentatives",
"Retry Suggestion": "Suggestion de relance",
"Retry Times": "Nombre de tentatives",
@@ -3339,7 +3356,6 @@
"Return Error": "Retourner l'erreur",
"Return per-token log probabilities": "Retourner les log-probabilités par jeton",
"Return to dashboard": "Retour au tableau de bord",
"auth.resetPasswordConfirm.backToLogin": "Retour à la connexion",
"Return vector embeddings for inputs": "Renvoyer des embeddings vectoriels pour les entrées",
"Reveal API key": "Afficher la clé API",
"Reveal key": "Révéler la clé",
@@ -3491,7 +3507,6 @@
"Select all (filtered)": "Tout sélectionner (filtré)",
"Select all models": "Sélectionner tous les modèles",
"Select All Visible": "Sélectionner tout ce qui est visible",
"Select model {{model}}": "Sélectionner le modèle {{model}}",
"Select an operation mode and enter the amount": "Sélectionnez un mode d'opération et entrez le montant",
"Select announcement type": "Sélectionner le type d'annonce",
"Select at least one field to overwrite.": "Sélectionnez au moins un champ à écraser.",
@@ -3518,6 +3533,7 @@
"Select layout style": "Sélectionner le style de mise en page",
"Select locations": "Sélectionner des emplacements",
"Select Model": "Sélectionner le modèle",
"Select model {{model}}": "Sélectionner le modèle {{model}}",
"Select models (empty for allow all)": "Sélectionner les modèles (vide pour autoriser tout)",
"Select models and apply to channel models list.": "Sélectionnez les modèles et appliquez-les à la liste des modèles de canaux.",
"Select models or add custom ones": "Sélectionner des modèles ou en ajouter des personnalisés",
@@ -3547,6 +3563,7 @@
"Select vendor": "Sélectionner le fournisseur",
"Selectable groups": "Groupes sélectionnables",
"selected": "sélectionné",
"Selected {{count}}": "{{count}} sélectionné(s)",
"selected channel(s). Leave empty to remove tag.": "canal(aux) sélectionné(s). Laisser vide pour supprimer l'étiquette.",
"Selected conflicts were overwritten successfully.": "Les conflits sélectionnés ont été écrasés avec succès.",
"Selected when creating a token and used as the default billing group for API calls.": "Sélectionné lors de la création dun jeton et utilisé comme groupe de facturation par défaut pour les appels API.",
@@ -3690,6 +3707,7 @@
"Status & Sync": "Statut et synchronisation",
"Status Code": "Code de statut",
"Status Code Mapping": "Mappage des codes d'état",
"Status code mapping must use valid HTTP status codes": "Le mappage des codes d'état doit utiliser des codes d'état HTTP valides",
"Status Page Slug": "Slug de la page d'état",
"Status short": "État",
"Status:": "Statut :",
@@ -3897,6 +3915,7 @@
"This action will permanently remove 2FA protection from your account.": "Cette action supprimera définitivement la protection 2FA de votre compte.",
"This channel is not an Ollama channel.": "Ce canal n'est pas un canal Ollama.",
"This channel type does not support fetching models": "Ce type de canal ne prend pas en charge la récupération de modèles",
"This channel type requires additional configuration": "Ce type de canal nécessite une configuration supplémentaire",
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "Cette confirmation déverrouille les fonctionnalités de paiement, de codes de兑换, de forfaits dabonnement et de récompenses dinvitation. Veuillez lire attentivement les déclarations.",
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "Ce réglage contrôle la limitation des requêtes de modèles. La limitation des routes Web/API se configure via les variables d'environnement et peut encore renvoyer 429.",
"This data may be unreliable, use with caution": "Ces données peuvent être peu fiables, utilisez-les avec prudence",
@@ -4215,6 +4234,7 @@
"Use external tools to extend capabilities": "Utiliser des outils externes pour étendre les capacités",
"Use our unified OpenAI-compatible endpoint in your applications": "Utilisez notre point de terminaison unifié compatible OpenAI dans vos applications",
"Use Passkey to sign in without entering your password.": "Utilisez une clé d'accès (Passkey) pour vous connecter sans saisir votre mot de passe.",
"Use presets or upstream discovery to populate the model list faster.": "Utilisez des préréglages ou la découverte en amont pour remplir plus vite la liste des modèles.",
"Use secure connection when sending emails": "Utiliser une connexion sécurisée lors de l'envoi d'e-mails",
"Use sidebar shortcut": "Utiliser le raccourci de la barre latérale",
"Use the full-width table to scan prices, then select a row to edit it here.": "Parcourez les prix dans le tableau, puis sélectionnez une ligne pour la modifier ici.",
@@ -4302,8 +4322,10 @@
"Verifying credentials and pulling stores from your Pancake account...": "Vérification des identifiants et récupération des boutiques depuis votre compte Pancake...",
"Version Overrides": "Remplacements de version",
"Vertex AI": "Vertex AI",
"Vertex AI API Key mode does not support batch creation": "Le mode clé API Vertex AI ne prend pas en charge la création par lot",
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI ne prend pas en charge functionResponse.id. Activez ceci pour supprimer automatiquement ce champ.",
"Vertex AI Key Format": "Format de clé Vertex AI",
"Vertex AI service account key must be valid JSON": "La clé de compte de service Vertex AI doit être un JSON valide",
"Video": "Vidéo",
"Video length in seconds": "Durée de la vidéo en secondes",
"Video Remix": "Remix vidéo",
@@ -4466,7 +4488,6 @@
"Your GitHub OAuth Client ID": "Votre ID Client OAuth GitHub",
"Your GitHub OAuth Client Secret": "Votre Secret Client OAuth GitHub",
"Your new backup codes are ready": "Vos nouveaux codes de secours sont prêts",
"auth.resetPasswordConfirm.success": "Votre mot de passe a été réinitialisé avec succès",
"Your Referral Link": "Votre lien de parrainage",
"Your setup guide is collapsed so usage stays in focus.": "Le guide de configuration est réduit afin de garder l'utilisation au premier plan.",
"Your system access token for API authentication. Keep it secure and don't share it with others.": "Votre jeton d'accès système pour l'authentification API. Gardez-le en sécurité et ne le partagez pas avec d'autres.",