-
-diff --git a/web/default/src/features/users/components/users-table.tsx b/web/default/src/features/users/components/users-table.tsx
-index 5d30c86..0a9a545 100644
---- a/web/default/src/features/users/components/users-table.tsx
-+++ b/web/default/src/features/users/components/users-table.tsx
-@@ -42,6 +42,7 @@ import {
- import { getUsers, searchUsers } from '../api'
- import {
- USER_STATUS,
-+ getUserQuotaStatusOptions,
- getUserStatusOptions,
- getUserRoleOptions,
- isUserDeleted,
-@@ -64,7 +65,9 @@ export function UsersTable() {
- const isMobile = useMediaQuery('(max-width: 640px)')
- const [rowSelection, setRowSelection] = useState({})
- const [sorting, setSorting] = useState([])
-- const [columnVisibility, setColumnVisibility] = useState({})
-+ const [columnVisibility, setColumnVisibility] = useState({
-+ quota_status: false,
-+ })
-
- const {
- globalFilter,
-@@ -81,6 +84,11 @@ export function UsersTable() {
- globalFilter: { enabled: true, key: 'filter' },
- columnFilters: [
- { columnId: 'status', searchKey: 'status', type: 'array' },
-+ {
-+ columnId: 'quota_status',
-+ searchKey: 'quota_status',
-+ type: 'array',
-+ },
- { columnId: 'role', searchKey: 'role', type: 'array' },
- { columnId: 'group', searchKey: 'group', type: 'string' },
- ],
-@@ -89,6 +97,10 @@ export function UsersTable() {
- (columnFilters.find((filter) => filter.id === 'status')?.value as
- | string[]
- | undefined) ?? []
-+ const quotaStatusFilter =
-+ (columnFilters.find((filter) => filter.id === 'quota_status')?.value as
-+ | string[]
-+ | undefined) ?? []
- const roleFilter =
- (columnFilters.find((filter) => filter.id === 'role')?.value as
- | string[]
-@@ -97,6 +109,7 @@ export function UsersTable() {
- (columnFilters.find((filter) => filter.id === 'group')?.value as string) ??
- ''
- const selectedStatus = statusFilter[0] ?? ''
-+ const selectedQuotaStatus = quotaStatusFilter[0] ?? ''
- const selectedRole = roleFilter[0] ?? ''
-
- // Fetch data with React Query
-@@ -107,6 +120,7 @@ export function UsersTable() {
- pagination.pageSize,
- globalFilter,
- selectedStatus,
-+ selectedQuotaStatus,
- selectedRole,
- groupFilter,
- refreshTrigger,
-@@ -114,7 +128,10 @@ export function UsersTable() {
- queryFn: async () => {
- const hasFilter = globalFilter?.trim()
- const hasColumnFilter =
-- Boolean(selectedStatus) || Boolean(selectedRole) || Boolean(groupFilter)
-+ Boolean(selectedStatus) ||
-+ Boolean(selectedQuotaStatus) ||
-+ Boolean(selectedRole) ||
-+ Boolean(groupFilter)
- const params = {
- p: pagination.pageIndex + 1,
- page_size: pagination.pageSize,
-@@ -126,6 +143,7 @@ export function UsersTable() {
- ...params,
- keyword: globalFilter,
- status: selectedStatus,
-+ quota_status: selectedQuotaStatus,
- role: selectedRole,
- group: groupFilter,
- })
-@@ -211,10 +229,16 @@ export function UsersTable() {
- filters: [
- {
- columnId: 'status',
-- title: t('Status'),
-+ title: t('Account Status'),
- options: getUserStatusOptions(t),
- singleSelect: true,
- },
-+ {
-+ columnId: 'quota_status',
-+ title: t('Balance Status'),
-+ options: getUserQuotaStatusOptions(t),
-+ singleSelect: true,
-+ },
- {
- columnId: 'role',
- title: t('Role'),
-diff --git a/web/default/src/features/users/constants.ts b/web/default/src/features/users/constants.ts
-index fb4e198..63335a8 100644
---- a/web/default/src/features/users/constants.ts
-+++ b/web/default/src/features/users/constants.ts
-@@ -61,6 +61,25 @@ export const getUserStatusOptions = (t: (key: string) => string) => [
- { label: t('Deleted'), value: String(USER_STATUS.DELETED) },
- ]
-
-+// ============================================================================
-+// User Quota Status Configuration
-+// ============================================================================
-+
-+export const USER_QUOTA_STATUS = {
-+ NEGATIVE: 'negative',
-+ ZERO: 'zero',
-+ POSITIVE: 'positive',
-+} as const
-+
-+export type UserQuotaStatus =
-+ (typeof USER_QUOTA_STATUS)[keyof typeof USER_QUOTA_STATUS]
-+
-+export const getUserQuotaStatusOptions = (t: (key: string) => string) => [
-+ { label: t('Negative Balance'), value: USER_QUOTA_STATUS.NEGATIVE },
-+ { label: t('Zero Balance'), value: USER_QUOTA_STATUS.ZERO },
-+ { label: t('Positive Balance'), value: USER_QUOTA_STATUS.POSITIVE },
-+]
-+
- // ============================================================================
- // User Role Configuration
- // ============================================================================
-diff --git a/web/default/src/features/users/types.ts b/web/default/src/features/users/types.ts
-index c03349f..1fc6cd0 100644
---- a/web/default/src/features/users/types.ts
-+++ b/web/default/src/features/users/types.ts
-@@ -94,6 +94,7 @@ export interface SearchUsersParams {
- group?: string
- role?: string
- status?: string
-+ quota_status?: string
- p?: number
- page_size?: number
- }
-diff --git a/web/default/src/hooks/use-top-nav-links.ts b/web/default/src/hooks/use-top-nav-links.ts
-index 02f8a05..7a2834c 100644
---- a/web/default/src/hooks/use-top-nav-links.ts
-+++ b/web/default/src/hooks/use-top-nav-links.ts
-@@ -28,6 +28,15 @@ export type TopNavLink = {
- disabled?: boolean
- requiresAuth?: boolean
- external?: boolean
-+ literalTitle?: boolean
-+}
-+
-+function isExternalHref(href: string): boolean {
-+ return /^https?:\/\//i.test(href)
-+}
-+
-+function isSupportedHref(href: string): boolean {
-+ return href.startsWith('/') || isExternalHref(href)
- }
-
- /**
-@@ -39,7 +48,8 @@ export type TopNavLink = {
- * pricing: { enabled: true, requireAuth: false },
- * rankings: { enabled: true, requireAuth: false },
- * docs: true,
-- * about: true
-+ * about: true,
-+ * custom: { enabled: false, title: "", href: "" }
- * }
- */
- export function useTopNavLinks(): TopNavLink[] {
-@@ -99,5 +109,19 @@ export function useTopNavLinks(): TopNavLink[] {
- links.push({ title: t('About'), href: '/about' })
- }
-
-+ const custom = modules?.custom
-+ if (custom?.enabled) {
-+ const title = custom.title.trim()
-+ const href = custom.href.trim()
-+ if (title && href && isSupportedHref(href)) {
-+ links.push({
-+ title,
-+ href,
-+ external: isExternalHref(href),
-+ literalTitle: true,
-+ })
-+ }
-+ }
-+
- return links
- }
-diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json
-index 414470c..dd0943d 100644
---- a/web/default/src/i18n/locales/en.json
-+++ b/web/default/src/i18n/locales/en.json
-@@ -133,6 +133,7 @@
- "Account deleted successfully": "Account deleted successfully",
- "Account ID *": "Account ID *",
- "Account Info": "Account Info",
-+ "Account Status": "Account Status",
- "Account used when authenticating with the SMTP server": "Account used when authenticating with the SMTP server",
- "acknowledge the related legal risks": "acknowledge the related legal risks",
- "Across all groups": "Across all groups",
-@@ -590,6 +591,7 @@
- "Balance depleted": "Balance depleted",
- "Balance is shown in quota units": "Balance is shown in quota units",
- "Balance queried successfully": "Balance queried successfully",
-+ "Balance Status": "Balance Status",
- "Balance updated successfully": "Balance updated successfully",
- "Balance updated: {{balance}}": "Balance updated: {{balance}}",
- "Bar Chart": "Bar Chart",
-@@ -861,6 +863,7 @@
- "Click for details": "Click for details",
- "Click save when you're done.": "Click save when you're done.",
- "Click save when you're done.": "Click save when you're done.",
-+ "Click Search to load logs": "Click Search to load logs",
- "Click the button below to bind your Telegram account": "Click the button below to bind your Telegram account",
- "Click to open deployment": "Click to open deployment",
- "Click to preview audio": "Click to preview audio",
-@@ -1215,6 +1218,7 @@
- "Custom model (comma-separated)": "Custom model (comma-separated)",
- "Custom module": "Custom module",
- "Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using \"edit_this\" group tokens.": "Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using \"edit_this\" group tokens.",
-+ "Custom navigation item": "Custom navigation item",
- "Custom OAuth": "Custom OAuth",
- "Custom OAuth Providers": "Custom OAuth Providers",
- "Custom Seconds": "Custom Seconds",
-@@ -1333,6 +1337,7 @@
- "Designed and Developed by": "Designed and Developed by",
- "designed for scale": "designed for scale",
- "Designed for the continuous operation of AI models and Agents": "Designed for the continuous operation of AI models and Agents",
-+ "Destination path or URL.": "Destination path or URL.",
- "Destroyed": "Destroyed",
- "Detailed request logs for investigations.": "Detailed request logs for investigations.",
- "Details": "Details",
-@@ -1403,6 +1408,7 @@
- "Display Options": "Display Options",
- "Display Token Statistics": "Display Token Statistics",
- "Displayed in": "Displayed in",
-+ "Displayed label in the top navigation.": "Displayed label in the top navigation.",
- "Displays the mobile sidebar.": "Displays the mobile sidebar.",
- "Do not over-trust this feature. IP may be spoofed. Please use with nginx, CDN and other gateways.": "Do not over-trust this feature. IP may be spoofed. Please use with nginx, CDN and other gateways.",
- "Do not repeat check-in; only once per day": "Do not repeat check-in; only once per day",
-@@ -1538,6 +1544,7 @@
- "Enable Discord OAuth": "Enable Discord OAuth",
- "Enable Disk Cache": "Enable Disk Cache",
- "Enable drawing features": "Enable drawing features",
-+ "Enable Empty Completion Retry": "Enable Empty Completion Retry",
- "Enable filtering": "Enable filtering",
- "Enable FunctionCall thoughtSignature Fill": "Enable FunctionCall thoughtSignature Fill",
- "Enable GitHub OAuth": "Enable GitHub OAuth",
-@@ -1807,6 +1814,7 @@
- "Failed to load home page content": "Failed to load home page content",
- "Failed to load image": "Failed to load image",
- "Failed to load key status": "Failed to load key status",
-+ "Failed to load log details": "Failed to load log details",
- "Failed to load logs": "Failed to load logs",
- "Failed to load Passkey status": "Failed to load Passkey status",
- "Failed to load playground groups": "Failed to load playground groups",
-@@ -2561,6 +2569,7 @@
- "Maximum tokens including hidden reasoning tokens": "Maximum tokens including hidden reasoning tokens",
- "Maximum tokens per response": "Maximum tokens per response",
- "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647",
-+ "May be caused by concurrent billing or manual adjustment.": "May be caused by concurrent billing or manual adjustment.",
- "May be used for training by upstream provider": "May be used for training by upstream provider",
- "Media pricing": "Media pricing",
- "Median time-to-first-token (TTFT) sampled hourly per group": "Median time-to-first-token (TTFT) sampled hourly per group",
-@@ -2762,8 +2771,12 @@
- "name@example.com": "name@example.com",
- "Native format": "Native format",
- "Native forwarding": "Native forwarding",
-+ "Navigation link": "Navigation link",
-+ "Navigation name": "Navigation name",
- "Need a redemption code?": "Need a redemption code?",
- "Needs API key": "Needs API key",
-+ "Negative": "Negative",
-+ "Negative Balance": "Negative Balance",
- "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.",
- "Nested JSON: source group →": "Nested JSON: source group →",
- "Network proxy for this channel (supports socks5 protocol)": "Network proxy for this channel (supports socks5 protocol)",
-@@ -3344,6 +3357,7 @@
- "Port": "Port",
- "Port must be a positive integer": "Port must be a positive integer",
- "Position in the stack": "Position in the stack",
-+ "Positive Balance": "Positive Balance",
- "Post Delta": "Post Delta",
- "PostgreSQL detected": "PostgreSQL detected",
- "PostgreSQL offers advanced reliability and data integrity for production workloads.": "PostgreSQL offers advanced reliability and data integrity for production workloads.",
-@@ -3781,6 +3795,7 @@
- "Retention days": "Retention days",
- "Retry": "Retry",
- "Retry Chain": "Retry Chain",
-+ "Retry once when upstream returns an empty Chat or Responses completion before any stream data is sent. Requires Retry Times greater than 0.": "Retry once when upstream returns an empty Chat or Responses completion before any stream data is sent. Requires Retry Times greater than 0.",
- "Retry policy": "Retry policy",
- "Retry Suggestion": "Retry Suggestion",
- "Retry Times": "Retry Times",
-@@ -4063,6 +4078,7 @@
- "Set API key access restrictions": "Set API key access restrictions",
- "Set API key basic information": "Set API key basic information",
- "Set Field": "Set Field",
-+ "Set filters and click Search to load usage logs.": "Set filters and click Search to load usage logs.",
- "Set filters to customize your dashboard statistics and charts.": "Set filters to customize your dashboard statistics and charts.",
- "Set filters to narrow down your log search results.": "Set filters to narrow down your log search results.",
- "Set Header": "Set Header",
-@@ -4097,6 +4113,7 @@
- "Show": "Show",
- "Show All": "Show All",
- "Show all providers including unbound": "Show all providers including unbound",
-+ "Show one custom link in the top navigation.": "Show one custom link in the top navigation.",
- "Show only bound providers": "Show only bound providers",
- "Show prices in currency instead of quota.": "Show prices in currency instead of quota.",
- "Show setup guide": "Show setup guide",
-@@ -4603,6 +4620,7 @@
- "Total earned": "Total earned",
- "Total Earned": "Total Earned",
- "Total GPUs": "Total GPUs",
-+ "Total Granted": "Total Granted",
- "Total invitation revenue": "Total invitation revenue",
- "Total Log Size": "Total Log Size",
- "Total Quota": "Total Quota",
-@@ -5087,6 +5105,7 @@
- "Your transaction history will appear here": "Your transaction history will appear here",
- "Your Turnstile secret key": "Your Turnstile secret key",
- "Your Turnstile site key": "Your Turnstile site key",
-+ "Zero Balance": "Zero Balance",
- "Zero retention": "Zero retention",
- "Zhipu": "Zhipu",
- "Zhipu V4": "Zhipu V4",
-diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json
-index 1f4e5dd..f617796 100644
---- a/web/default/src/i18n/locales/fr.json
-+++ b/web/default/src/i18n/locales/fr.json
-@@ -133,6 +133,7 @@
- "Account deleted successfully": "Compte supprimé avec succès",
- "Account ID *": "ID de compte *",
- "Account Info": "Informations du compte",
-+ "Account Status": "Statut du compte",
- "Account used when authenticating with the SMTP server": "Compte utilisé lors de l'authentification auprès du serveur SMTP",
- "acknowledge the related legal risks": "reconnais les risques juridiques associés",
- "Across all groups": "Tous groupes confondus",
-@@ -590,6 +591,7 @@
- "Balance depleted": "Solde épuisé",
- "Balance is shown in quota units": "Le solde est affiché en unités de quota",
- "Balance queried successfully": "Solde interrogé avec succès",
-+ "Balance Status": "Statut du solde",
- "Balance updated successfully": "Solde mis à jour avec succès",
- "Balance updated: {{balance}}": "Solde mis à jour : {{balance}}",
- "Bar Chart": "Graphique en barres",
-@@ -861,6 +863,7 @@
- "Click for details": "Cliquez pour les détails",
- "Click save when you're done.": "Cliquez sur Enregistrer lorsque vous avez terminé.",
- "Click save when you're done.": "Cliquez sur Enregistrer lorsque vous avez terminé.",
-+ "Click Search to load logs": "Cliquez sur Rechercher pour charger les journaux",
- "Click the button below to bind your Telegram account": "Cliquez sur le bouton ci-dessous pour lier votre compte Telegram",
- "Click to open deployment": "Cliquez pour ouvrir le déploiement",
- "Click to preview audio": "Cliquer pour prévisualiser l'audio",
-@@ -1215,6 +1218,7 @@
- "Custom model (comma-separated)": "Modèle personnalisé (séparé par des virgules)",
- "Custom module": "Module personnalisé",
- "Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using \"edit_this\" group tokens.": "Multiplicateurs personnalisés lorsque des groupes d'utilisateurs spécifiques utilisent des groupes de jetons spécifiques. Exemple : les utilisateurs VIP obtiennent un taux de 0,9x lorsqu'ils utilisent les jetons du groupe \"edit_this\".",
-+ "Custom navigation item": "Élément de navigation personnalisé",
- "Custom OAuth": "OAuth personnalisé",
- "Custom OAuth Providers": "Fournisseurs OAuth personnalisés",
- "Custom Seconds": "Secondes personnalisées",
-@@ -1333,6 +1337,7 @@
- "Designed and Developed by": "Conçu et développé par",
- "designed for scale": "conçu pour la scalabilité",
- "Designed for the continuous operation of AI models and Agents": "Designed for the continuous operation of AI models and Agents",
-+ "Destination path or URL.": "Chemin ou URL de destination.",
- "Destroyed": "Détruit",
- "Detailed request logs for investigations.": "Journaux détaillés des requêtes pour les enquêtes.",
- "Details": "Détails",
-@@ -1403,6 +1408,7 @@
- "Display Options": "Options d'affichage",
- "Display Token Statistics": "Afficher les statistiques des jetons",
- "Displayed in": "Affiché en",
-+ "Displayed label in the top navigation.": "Libellé affiché dans la navigation supérieure.",
- "Displays the mobile sidebar.": "Affiche la barre latérale mobile.",
- "Do not over-trust this feature. IP may be spoofed. Please use with nginx, CDN and other gateways.": "Ne faites pas trop confiance à cette fonctionnalité. L'IP peut être usurpée. Veuillez l'utiliser avec nginx, CDN et autres passerelles.",
- "Do not repeat check-in; only once per day": "Ne répétez pas le check-in ; une seule fois par jour",
-@@ -1538,6 +1544,7 @@
- "Enable Discord OAuth": "Activer OAuth Discord",
- "Enable Disk Cache": "Activer le cache disque",
- "Enable drawing features": "Activer les fonctionnalités de dessin",
-+ "Enable Empty Completion Retry": "Activer la relance des complétions vides",
- "Enable filtering": "Activer le filtrage",
- "Enable FunctionCall thoughtSignature Fill": "Activer le remplissage de thoughtSignature pour FunctionCall",
- "Enable GitHub OAuth": "Activer GitHub OAuth",
-@@ -1807,6 +1814,7 @@
- "Failed to load home page content": "Échec du chargement du contenu de la page d'accueil",
- "Failed to load image": "Échec du chargement de l'image",
- "Failed to load key status": "Échec du chargement du statut des clés",
-+ "Failed to load log details": "Échec du chargement des détails du journal",
- "Failed to load logs": "Échec du chargement des journaux",
- "Failed to load Passkey status": "Échec du chargement du statut Passkey",
- "Failed to load playground groups": "Échec du chargement des groupes du playground",
-@@ -2561,6 +2569,7 @@
- "Maximum tokens including hidden reasoning tokens": "Jetons maximum, y compris les jetons de raisonnement masqués",
- "Maximum tokens per response": "Nombre maximal de jetons par réponse",
- "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, les deux ≤ 2 147 483 647",
-+ "May be caused by concurrent billing or manual adjustment.": "Peut être causé par une facturation concurrente ou un ajustement manuel.",
- "May be used for training by upstream provider": "Peut être utilisé pour l'entraînement par le fournisseur amont",
- "Media pricing": "Tarification multimédia",
- "Median time-to-first-token (TTFT) sampled hourly per group": "Latence médiane jusqu'au premier jeton (TTFT) échantillonnée par heure et par groupe",
-@@ -2762,8 +2771,12 @@
- "name@example.com": "name@example.com",
- "Native format": "Format natif",
- "Native forwarding": "Transfert natif",
-+ "Navigation link": "Lien de navigation",
-+ "Navigation name": "Nom de navigation",
- "Need a redemption code?": "Besoin d'un code d'échange ?",
- "Needs API key": "Clé API requise",
-+ "Negative": "Négatif",
-+ "Negative Balance": "Solde négatif",
- "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "JSON imbriqué définissant des règles par groupe pour ajouter (+:), supprimer (-:), ou ajouter des groupes utilisables.",
- "Nested JSON: source group →": "JSON imbriqué : groupe source →",
- "Network proxy for this channel (supports socks5 protocol)": "Proxy réseau pour ce canal (supporte le protocole socks5)",
-@@ -3344,6 +3357,7 @@
- "Port": "Port",
- "Port must be a positive integer": "Le port doit être un entier positif",
- "Position in the stack": "Position dans la pile",
-+ "Positive Balance": "Solde positif",
- "Post Delta": "Delta post-calcul",
- "PostgreSQL detected": "PostgreSQL détecté",
- "PostgreSQL offers advanced reliability and data integrity for production workloads.": "PostgreSQL offre une fiabilité avancée et une intégrité des données pour les charges de travail en production.",
-@@ -3781,6 +3795,7 @@
- "Retention days": "Jours de rétention",
- "Retry": "Réessayer",
- "Retry Chain": "Chaîne de tentatives",
-+ "Retry once when upstream returns an empty Chat or Responses completion before any stream data is sent. Requires Retry Times greater than 0.": "Réessaie une fois lorsque l'amont renvoie une complétion Chat ou Responses vide avant l'envoi de données de flux. Nécessite un nombre de tentatives supérieur à 0.",
- "Retry policy": "Politique de reprise",
- "Retry Suggestion": "Suggestion de relance",
- "Retry Times": "Nombre de tentatives",
-@@ -4063,6 +4078,7 @@
- "Set API key access restrictions": "Définir les restrictions d'accès de la clé API",
- "Set API key basic information": "Définir les informations de base de la clé API",
- "Set Field": "Définir le champ",
-+ "Set filters and click Search to load usage logs.": "Définissez les filtres puis cliquez sur Rechercher pour charger les journaux d'utilisation.",
- "Set filters to customize your dashboard statistics and charts.": "Définir des filtres pour personnaliser les statistiques et les graphiques de votre tableau de bord.",
- "Set filters to narrow down your log search results.": "Définir des filtres pour affiner vos résultats de recherche de journaux.",
- "Set Header": "Définir l'en-tête",
-@@ -4097,6 +4113,7 @@
- "Show": "Afficher",
- "Show All": "Tout afficher",
- "Show all providers including unbound": "Afficher tous les fournisseurs (y compris non liés)",
-+ "Show one custom link in the top navigation.": "Afficher un lien personnalisé dans la navigation supérieure.",
- "Show only bound providers": "Afficher uniquement les fournisseurs liés",
- "Show prices in currency instead of quota.": "Afficher les prix en devise au lieu du quota.",
- "Show setup guide": "Afficher le guide de configuration",
-@@ -4603,6 +4620,7 @@
- "Total earned": "Total gagné",
- "Total Earned": "Total gagné",
- "Total GPUs": "GPUs totaux",
-+ "Total Granted": "Quota total accordé",
- "Total invitation revenue": "Revenu total des invitations",
- "Total Log Size": "Taille totale des journaux",
- "Total Quota": "Quota total",
-@@ -5087,6 +5105,7 @@
- "Your transaction history will appear here": "Votre historique de transactions apparaîtra ici",
- "Your Turnstile secret key": "Votre clé secrète Turnstile",
- "Your Turnstile site key": "Votre clé de site Turnstile",
-+ "Zero Balance": "Solde nul",
- "Zero retention": "Aucune rétention",
- "Zhipu": "Zhipu",
- "Zhipu V4": "Zhipu V4",
-diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json
-index 54042d8..981e037 100644
---- a/web/default/src/i18n/locales/ja.json
-+++ b/web/default/src/i18n/locales/ja.json
-@@ -133,6 +133,7 @@
- "Account deleted successfully": "アカウントが正常に削除されました",
- "Account ID *": "アカウントID *",
- "Account Info": "アカウント情報",
-+ "Account Status": "アカウント状態",
- "Account used when authenticating with the SMTP server": "SMTPサーバーで認証する際に使用されるアカウント",
- "acknowledge the related legal risks": "関連する法的リスクを認識します",
- "Across all groups": "全グループを通じて",
-@@ -590,6 +591,7 @@
- "Balance depleted": "残高なし",
- "Balance is shown in quota units": "残高はクォータ単位で表示されます",
- "Balance queried successfully": "残高の取得に成功しました",
-+ "Balance Status": "残高状態",
- "Balance updated successfully": "残高が正常に更新されました",
- "Balance updated: {{balance}}": "残高更新:{{balance}}",
- "Bar Chart": "棒グラフ",
-@@ -861,6 +863,7 @@
- "Click for details": "クリックして詳細を表示",
- "Click save when you're done.": "完了したら「保存」をクリックしてください。",
- "Click save when you're done.": "完了したら保存をクリックしてください。",
-+ "Click Search to load logs": "検索をクリックしてログを読み込む",
- "Click the button below to bind your Telegram account": "下のボタンをクリックしてTelegramアカウントをバインドしてください",
- "Click to open deployment": "クリックして展開を開く",
- "Click to preview audio": "クリックして音声をプレビュー",
-@@ -1215,6 +1218,7 @@
- "Custom model (comma-separated)": "カスタムモデル (コンマ区切り)",
- "Custom module": "カスタムモジュール",
- "Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using \"edit_this\" group tokens.": "特定のユーザーグループが特定のトークングループを使用する場合のカスタム乗数。例: VIPユーザーが「edit_this」グループトークンを使用する場合、0.9倍のレートが適用されます。",
-+ "Custom navigation item": "カスタムナビゲーション項目",
- "Custom OAuth": "カスタム OAuth",
- "Custom OAuth Providers": "カスタムOAuthプロバイダー",
- "Custom Seconds": "カスタム秒数",
-@@ -1333,6 +1337,7 @@
- "Designed and Developed by": "設計・開発",
- "designed for scale": "スケールのために設計",
- "Designed for the continuous operation of AI models and Agents": "Designed for the continuous operation of AI models and Agents",
-+ "Destination path or URL.": "移動先のパスまたは URL。",
- "Destroyed": "破棄済み",
- "Detailed request logs for investigations.": "調査のための詳細なリクエストログ。",
- "Details": "詳細",
-@@ -1403,6 +1408,7 @@
- "Display Options": "表示オプション",
- "Display Token Statistics": "トークン統計を表示",
- "Displayed in": "表示単位",
-+ "Displayed label in the top navigation.": "トップナビゲーションに表示されるラベルです。",
- "Displays the mobile sidebar.": "モバイルサイドバーを表示します。",
- "Do not over-trust this feature. IP may be spoofed. Please use with nginx, CDN and other gateways.": "この機能を過信しないでください。IPは偽装される可能性があります。nginx、CDNなどのゲートウェイと併用してください。",
- "Do not repeat check-in; only once per day": "チェックインを繰り返さないでください;1日1回のみ",
-@@ -1538,6 +1544,7 @@
- "Enable Discord OAuth": "Discord OAuthを有効にする",
- "Enable Disk Cache": "ディスクキャッシュを有効にする",
- "Enable drawing features": "描画機能を有効にする",
-+ "Enable Empty Completion Retry": "空の補完の再試行を有効化",
- "Enable filtering": "フィルタリングを有効にする",
- "Enable FunctionCall thoughtSignature Fill": "FunctionCall用のthoughtSignature自動付与を有効化",
- "Enable GitHub OAuth": "GitHub OAuthを有効にする",
-@@ -1807,6 +1814,7 @@
- "Failed to load home page content": "ホームページの内容の読み込みに失敗しました",
- "Failed to load image": "画像の読み込みに失敗しました",
- "Failed to load key status": "キー状態の読み込みに失敗しました",
-+ "Failed to load log details": "ログ詳細の読み込みに失敗しました",
- "Failed to load logs": "ログの読み込みに失敗しました",
- "Failed to load Passkey status": "Passkeyのステータスの読み込みに失敗しました",
- "Failed to load playground groups": "Playground グループの読み込みに失敗しました",
-@@ -2561,6 +2569,7 @@
- "Maximum tokens including hidden reasoning tokens": "隠れ推論トークンを含む最大トークン数",
- "Maximum tokens per response": "1 回の応答あたりの最大トークン数",
- "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0、maxSuccess ≥ 1、両方とも ≤ 2,147,483,647",
-+ "May be caused by concurrent billing or manual adjustment.": "同時課金または手動調整によって発生した可能性があります。",
- "May be used for training by upstream provider": "上流プロバイダーが学習に利用する可能性があります",
- "Media pricing": "メディア料金",
- "Median time-to-first-token (TTFT) sampled hourly per group": "グループ別に毎時サンプリングした最初のトークンまでの中央値レイテンシ (TTFT)",
-@@ -2762,8 +2771,12 @@
- "name@example.com": "name@example.com",
- "Native format": "ネイティブ形式",
- "Native forwarding": "ネイティブ転送",
-+ "Navigation link": "ナビゲーションリンク",
-+ "Navigation name": "ナビゲーション名",
- "Need a redemption code?": "引き換えコードが必要ですか?",
- "Needs API key": "API キーが必要",
-+ "Negative": "マイナス",
-+ "Negative Balance": "マイナス残高",
- "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "追加 (+:)、削除 (-:)、または使用可能なグループの追加を行うグループごとのルールを定義するネストされたJSON。",
- "Nested JSON: source group →": "ネストされたJSON: ソースグループ →",
- "Network proxy for this channel (supports socks5 protocol)": "このチャネルのネットワークプロキシ (socks5プロトコルをサポート)",
-@@ -3344,6 +3357,7 @@
- "Port": "ポート",
- "Port must be a positive integer": "ポートは正の整数でなければなりません",
- "Position in the stack": "スタック内の位置",
-+ "Positive Balance": "プラス残高",
- "Post Delta": "事後差額",
- "PostgreSQL detected": "PostgreSQLが検出されました",
- "PostgreSQL offers advanced reliability and data integrity for production workloads.": "PostgreSQL は本番ワークロード向けの高度な信頼性とデータ整合性を提供します。",
-@@ -3781,6 +3795,7 @@
- "Retention days": "保持日数",
- "Retry": "再試行",
- "Retry Chain": "リトライチェーン",
-+ "Retry once when upstream returns an empty Chat or Responses completion before any stream data is sent. Requires Retry Times greater than 0.": "ストリームデータを送信する前に上流が空の Chat または Responses 補完を返した場合に 1 回だけ再試行します。Retry Times は 0 より大きくする必要があります。",
- "Retry policy": "リトライポリシー",
- "Retry Suggestion": "リトライ提案",
- "Retry Times": "再試行回数",
-@@ -4063,6 +4078,7 @@
- "Set API key access restrictions": "API キーのアクセス制限を設定",
- "Set API key basic information": "API キーの基本情報を設定",
- "Set Field": "フィールドを設定",
-+ "Set filters and click Search to load usage logs.": "フィルターを設定し、検索をクリックして使用ログを読み込んでください。",
- "Set filters to customize your dashboard statistics and charts.": "ダッシュボードの統計とグラフをカスタマイズするためにフィルターを設定します。",
- "Set filters to narrow down your log search results.": "ログ検索結果を絞り込むためにフィルターを設定します。",
- "Set Header": "ヘッダーを設定",
-@@ -4097,6 +4113,7 @@
- "Show": "表示",
- "Show All": "すべて表示",
- "Show all providers including unbound": "未バインドを含むすべてのプロバイダーを表示",
-+ "Show one custom link in the top navigation.": "トップナビゲーションにカスタムリンクを1つ表示します。",
- "Show only bound providers": "バインド済みのプロバイダーのみ表示",
- "Show prices in currency instead of quota.": "クォータではなく通貨で価格を表示。",
- "Show setup guide": "セットアップガイドを表示",
-@@ -4603,6 +4620,7 @@
- "Total earned": "总收入",
- "Total Earned": "総収益",
- "Total GPUs": "合計 GPU",
-+ "Total Granted": "付与総額",
- "Total invitation revenue": "招待による総収益",
- "Total Log Size": "ログ合計サイズ",
- "Total Quota": "合計クォータ",
-@@ -5087,6 +5105,7 @@
- "Your transaction history will appear here": "取引履歴はここに表示されます",
- "Your Turnstile secret key": "あなたのTurnstileシークレットキー",
- "Your Turnstile site key": "あなたのTurnstileサイトキー",
-+ "Zero Balance": "ゼロ残高",
- "Zero retention": "データ保持なし",
- "Zhipu": "Zhipu",
- "Zhipu V4": "Zhipu V 4",
-diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json
-index f9a9f56..d2ca6cd 100644
---- a/web/default/src/i18n/locales/ru.json
-+++ b/web/default/src/i18n/locales/ru.json
-@@ -133,6 +133,7 @@
- "Account deleted successfully": "Аккаунт успешно удалён",
- "Account ID *": "ID аккаунта *",
- "Account Info": "Информация об аккаунте",
-+ "Account Status": "Статус аккаунта",
- "Account used when authenticating with the SMTP server": "Учетная запись, используемая при аутентификации с SMTP-сервером",
- "acknowledge the related legal risks": "признаю связанные правовые риски",
- "Across all groups": "По всем группам",
-@@ -590,6 +591,7 @@
- "Balance depleted": "Баланс исчерпан",
- "Balance is shown in quota units": "Баланс показан в единицах квоты",
- "Balance queried successfully": "Баланс успешно запрошен",
-+ "Balance Status": "Статус баланса",
- "Balance updated successfully": "Баланс успешно обновлён",
- "Balance updated: {{balance}}": "Баланс обновлён: {{balance}}",
- "Bar Chart": "Столбчатая диаграмма",
-@@ -861,6 +863,7 @@
- "Click for details": "Нажмите для подробностей",
- "Click save when you're done.": "Нажмите «Сохранить», когда закончите.",
- "Click save when you're done.": "Нажмите сохранить, когда закончите.",
-+ "Click Search to load logs": "Нажмите «Поиск», чтобы загрузить логи",
- "Click the button below to bind your Telegram account": "Нажмите кнопку ниже, чтобы привязать ваш аккаунт Telegram",
- "Click to open deployment": "Нажмите, чтобы открыть развертывание",
- "Click to preview audio": "Нажмите для предпросмотра аудио",
-@@ -1215,6 +1218,7 @@
- "Custom model (comma-separated)": "Пользовательская модель (через запятую)",
- "Custom module": "Пользовательский модуль",
- "Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using \"edit_this\" group tokens.": "Пользовательские множители, когда определенные группы пользователей используют определенные группы токенов. Пример: VIP-пользователи получают ставку 0.9x при использовании токенов группы \"edit_this\".",
-+ "Custom navigation item": "Пользовательский пункт навигации",
- "Custom OAuth": "Пользовательский OAuth",
- "Custom OAuth Providers": "Пользовательские OAuth-провайдеры",
- "Custom Seconds": "Пользовательские секунды",
-@@ -1333,6 +1337,7 @@
- "Designed and Developed by": "Разработано и создано",
- "designed for scale": "спроектировано для масштабирования",
- "Designed for the continuous operation of AI models and Agents": "Designed for the continuous operation of AI models and Agents",
-+ "Destination path or URL.": "Путь назначения или URL.",
- "Destroyed": "Уничтожено",
- "Detailed request logs for investigations.": "Подробные журналы запросов для расследований.",
- "Details": "Детали",
-@@ -1403,6 +1408,7 @@
- "Display Options": "Параметры отображения",
- "Display Token Statistics": "Показать статистику токенов",
- "Displayed in": "Отображается в",
-+ "Displayed label in the top navigation.": "Метка, отображаемая в верхней навигации.",
- "Displays the mobile sidebar.": "Отображает мобильную боковую панель.",
- "Do not over-trust this feature. IP may be spoofed. Please use with nginx, CDN and other gateways.": "Не доверяйте этой функции слишком сильно. IP может быть подделан. Используйте с nginx, CDN и другими шлюзами.",
- "Do not repeat check-in; only once per day": "Не повторяйте отметку; только один раз в день",
-@@ -1538,6 +1544,7 @@
- "Enable Discord OAuth": "Включить Discord OAuth",
- "Enable Disk Cache": "Включить дисковый кэш",
- "Enable drawing features": "Включить функции рисования",
-+ "Enable Empty Completion Retry": "Включить повтор пустых завершений",
- "Enable filtering": "Включить фильтрацию",
- "Enable FunctionCall thoughtSignature Fill": "Включить автозаполнение thoughtSignature для FunctionCall",
- "Enable GitHub OAuth": "Включить GitHub OAuth",
-@@ -1807,6 +1814,7 @@
- "Failed to load home page content": "Не удалось загрузить содержимое главной страницы",
- "Failed to load image": "Не удалось загрузить изображение",
- "Failed to load key status": "Не удалось загрузить статус ключей",
-+ "Failed to load log details": "Не удалось загрузить сведения лога",
- "Failed to load logs": "Не удалось загрузить логи",
- "Failed to load Passkey status": "Не удалось загрузить статус Passkey",
- "Failed to load playground groups": "Не удалось загрузить группы Playground",
-@@ -2561,6 +2569,7 @@
- "Maximum tokens including hidden reasoning tokens": "Максимум токенов с учётом скрытых reasoning-токенов",
- "Maximum tokens per response": "Максимум токенов на ответ",
- "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, оба ≤ 2,147,483,647",
-+ "May be caused by concurrent billing or manual adjustment.": "Может быть вызвано параллельным списанием или ручной корректировкой.",
- "May be used for training by upstream provider": "Может использоваться поставщиком для обучения",
- "Media pricing": "Цены для медиа",
- "Median time-to-first-token (TTFT) sampled hourly per group": "Медианная задержка первого токена (TTFT), измеряемая ежечасно по группам",
-@@ -2762,8 +2771,12 @@
- "name@example.com": "name@example.com",
- "Native format": "Собственный формат",
- "Native forwarding": "Нативная пересылка",
-+ "Navigation link": "Ссылка навигации",
-+ "Navigation name": "Название навигации",
- "Need a redemption code?": "Нужен код активации?",
- "Needs API key": "Нужен API-ключ",
-+ "Negative": "Отрицательный",
-+ "Negative Balance": "Отрицательный баланс",
- "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "Вложенный JSON, определяющий правила для каждой группы для добавления (+:), удаления (-:) или добавления используемых групп.",
- "Nested JSON: source group →": "Вложенный JSON: исходная группа →",
- "Network proxy for this channel (supports socks5 protocol)": "Сетевой прокси для этого канала (поддерживает протокол socks5)",
-@@ -3344,6 +3357,7 @@
- "Port": "Порт",
- "Port must be a positive integer": "Порт должен быть положительным целым числом",
- "Position in the stack": "Позиция в стеке",
-+ "Positive Balance": "Положительный баланс",
- "Post Delta": "Дельта после расчёта",
- "PostgreSQL detected": "PostgreSQL обнаружен",
- "PostgreSQL offers advanced reliability and data integrity for production workloads.": "PostgreSQL обеспечивает высокую надёжность и целостность данных для продакшен-нагрузок.",
-@@ -3781,6 +3795,7 @@
- "Retention days": "Дней хранения",
- "Retry": "Повторить попытку",
- "Retry Chain": "Цепочка повторов",
-+ "Retry once when upstream returns an empty Chat or Responses completion before any stream data is sent. Requires Retry Times greater than 0.": "Повторяет запрос один раз, если апстрим вернул пустое завершение Chat или Responses до отправки потоковых данных. Требуется, чтобы Retry Times было больше 0.",
- "Retry policy": "Политика повторов",
- "Retry Suggestion": "Рекомендация по повтору",
- "Retry Times": "Количество повторных попыток",
-@@ -4063,6 +4078,7 @@
- "Set API key access restrictions": "Настройте ограничения доступа API-ключа",
- "Set API key basic information": "Настройте основные сведения API-ключа",
- "Set Field": "Установить поле",
-+ "Set filters and click Search to load usage logs.": "Задайте фильтры и нажмите «Поиск», чтобы загрузить логи использования.",
- "Set filters to customize your dashboard statistics and charts.": "Установите фильтры, чтобы настроить статистику и диаграммы вашей панели управления.",
- "Set filters to narrow down your log search results.": "Установите фильтры, чтобы сузить результаты поиска по журналам.",
- "Set Header": "Установить заголовок",
-@@ -4097,6 +4113,7 @@
- "Show": "Показать",
- "Show All": "Показать все",
- "Show all providers including unbound": "Показать всех провайдеров (включая непривязанные)",
-+ "Show one custom link in the top navigation.": "Показывать одну пользовательскую ссылку в верхней навигации.",
- "Show only bound providers": "Показать только привязанных провайдеров",
- "Show prices in currency instead of quota.": "Показывать цены в валюте вместо квоты.",
- "Show setup guide": "Показать руководство по настройке",
-@@ -4603,6 +4620,7 @@
- "Total earned": "Итого заработано",
- "Total Earned": "Всего заработано",
- "Total GPUs": "Всего GPU",
-+ "Total Granted": "Всего предоставлено",
- "Total invitation revenue": "Общий доход от приглашений",
- "Total Log Size": "Общий размер журналов",
- "Total Quota": "Общая квота",
-@@ -5087,6 +5105,7 @@
- "Your transaction history will appear here": "Ваша история транзакций появится здесь",
- "Your Turnstile secret key": "Секретный ключ Turnstile",
- "Your Turnstile site key": "Ключ сайта Turnstile",
-+ "Zero Balance": "Нулевой баланс",
- "Zero retention": "Без хранения данных",
- "Zhipu": "Zhipu",
- "Zhipu V4": "Zhipu V4",
-diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json
-index 2d8bb7f..8883192 100644
---- a/web/default/src/i18n/locales/vi.json
-+++ b/web/default/src/i18n/locales/vi.json
-@@ -133,6 +133,7 @@
- "Account deleted successfully": "Tài khoản đã được xóa thành công",
- "Account ID *": "ID tài khoản *",
- "Account Info": "Thông tin tài khoản",
-+ "Account Status": "Trạng thái tài khoản",
- "Account used when authenticating with the SMTP server": "Tài khoản được sử dụng khi xác thực với máy chủ SMTP",
- "acknowledge the related legal risks": "thừa nhận các rủi ro pháp lý liên quan",
- "Across all groups": "Trên mọi nhóm",
-@@ -590,6 +591,7 @@
- "Balance depleted": "Đã hết số dư",
- "Balance is shown in quota units": "Số dư được hiển thị theo đơn vị hạn mức",
- "Balance queried successfully": "Truy vấn số dư thành công",
-+ "Balance Status": "Trạng thái số dư",
- "Balance updated successfully": "Đã cập nhật số dư thành công",
- "Balance updated: {{balance}}": "Số dư đã cập nhật: {{balance}}",
- "Bar Chart": "Biểu đồ cột",
-@@ -861,6 +863,7 @@
- "Click for details": "Nhấp để xem chi tiết",
- "Click save when you're done.": "Nhấn lưu khi bạn hoàn tất.",
- "Click save when you're done.": "Nhấn lưu khi bạn hoàn tất.",
-+ "Click Search to load logs": "Nhấp Tìm kiếm để tải nhật ký",
- "Click the button below to bind your Telegram account": "Nhấp vào nút bên dưới để liên kết tài khoản Telegram của bạn",
- "Click to open deployment": "Nhấp để mở triển khai",
- "Click to preview audio": "Nhấp để xem trước âm thanh",
-@@ -1215,6 +1218,7 @@
- "Custom model (comma-separated)": "Mô hình tùy chỉnh (phân tách bằng dấu phẩy)",
- "Custom module": "Module tùy chỉnh",
- "Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using \"edit_this\" group tokens.": "Các hệ số nhân tùy chỉnh khi các nhóm người dùng cụ thể sử dụng các nhóm token cụ thể. Ví dụ: Người dùng VIP được hưởng tỷ lệ 0.9x khi sử dụng các token thuộc nhóm \"edit_this\".",
-+ "Custom navigation item": "Mục điều hướng tùy chỉnh",
- "Custom OAuth": "OAuth tùy chỉnh",
- "Custom OAuth Providers": "Nhà cung cấp OAuth tùy chỉnh",
- "Custom Seconds": "Giây tùy chỉnh",
-@@ -1333,6 +1337,7 @@
- "Designed and Developed by": "Thiết kế và Phát triển bởi",
- "designed for scale": "thiết kế cho quy mô lớn",
- "Designed for the continuous operation of AI models and Agents": "Designed for the continuous operation of AI models and Agents",
-+ "Destination path or URL.": "Đường dẫn hoặc URL đích.",
- "Destroyed": "Đã hủy",
- "Detailed request logs for investigations.": "Nhật ký yêu cầu chi tiết cho các cuộc điều tra.",
- "Details": "Chi tiết",
-@@ -1403,6 +1408,7 @@
- "Display Options": "Tùy chọn hiển thị",
- "Display Token Statistics": "Hiển thị Thống kê Token",
- "Displayed in": "Hiển thị theo",
-+ "Displayed label in the top navigation.": "Nhãn hiển thị trên thanh điều hướng đầu trang.",
- "Displays the mobile sidebar.": "Hiển thị thanh bên di động.",
- "Do not over-trust this feature. IP may be spoofed. Please use with nginx, CDN and other gateways.": "Đừng tin tưởng quá mức vào tính năng này. IP có thể bị giả mạo. Hãy sử dụng cùng với nginx, CDN và các gateway khác.",
- "Do not repeat check-in; only once per day": "Không lặp lại check-in; chỉ một lần mỗi ngày",
-@@ -1538,6 +1544,7 @@
- "Enable Discord OAuth": "Bật Discord OAuth",
- "Enable Disk Cache": "Bật bộ nhớ đệm đĩa",
- "Enable drawing features": "Bật tính năng vẽ",
-+ "Enable Empty Completion Retry": "Bật thử lại khi phản hồi trống",
- "Enable filtering": "Bật lọc",
- "Enable FunctionCall thoughtSignature Fill": "Bật tính năng điền FunctionCall thoughtSignature",
- "Enable GitHub OAuth": "Kích hoạt GitHub OAuth",
-@@ -1807,6 +1814,7 @@
- "Failed to load home page content": "Không thể tải nội dung trang chủ",
- "Failed to load image": "Không thể tải ảnh",
- "Failed to load key status": "Không thể tải trạng thái khóa",
-+ "Failed to load log details": "Không tải được chi tiết nhật ký",
- "Failed to load logs": "Không tải được nhật ký",
- "Failed to load Passkey status": "Không thể tải trạng thái Passkey",
- "Failed to load playground groups": "Tải nhóm playground thất bại",
-@@ -2561,6 +2569,7 @@
- "Maximum tokens including hidden reasoning tokens": "Số token tối đa bao gồm token suy luận ẩn",
- "Maximum tokens per response": "Số token tối đa mỗi phản hồi",
- "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, cả hai đều ≤ 2,147,483,647",
-+ "May be caused by concurrent billing or manual adjustment.": "Có thể do tính phí đồng thời hoặc điều chỉnh thủ công.",
- "May be used for training by upstream provider": "Có thể được nhà cung cấp dùng để huấn luyện",
- "Media pricing": "Giá phương tiện",
- "Median time-to-first-token (TTFT) sampled hourly per group": "Độ trễ token đầu tiên trung vị (TTFT) lấy mẫu mỗi giờ theo nhóm",
-@@ -2762,8 +2771,12 @@
- "name@example.com": "name@example.com",
- "Native format": "Định dạng gốc",
- "Native forwarding": "Chuyển tiếp nguyên bản",
-+ "Navigation link": "Liên kết điều hướng",
-+ "Navigation name": "Tên điều hướng",
- "Need a redemption code?": "Cần mã đổi thưởng?",
- "Needs API key": "Cần khóa API",
-+ "Negative": "Âm",
-+ "Negative Balance": "Số dư âm",
- "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "JSON lồng nhau xác định quy tắc theo nhóm để thêm (+:), xóa (-:), hoặc nối các nhóm có thể sử dụng.",
- "Nested JSON: source group →": "JSON lồng nhau: nhóm nguồn →",
- "Network proxy for this channel (supports socks5 protocol)": "Proxy mạng cho kênh này (hỗ trợ giao thức socks5)",
-@@ -3344,6 +3357,7 @@
- "Port": "Cảng",
- "Port must be a positive integer": "Cổng phải là số nguyên dương",
- "Position in the stack": "Vị trí trong stack",
-+ "Positive Balance": "Số dư dương",
- "Post Delta": "Chênh lệch sau",
- "PostgreSQL detected": "Phát hiện PostgreSQL",
- "PostgreSQL offers advanced reliability and data integrity for production workloads.": "PostgreSQL cung cấp độ tin cậy cao và tính toàn vẹn dữ liệu cho khối lượng công việc production.",
-@@ -3781,6 +3795,7 @@
- "Retention days": "Số ngày lưu giữ",
- "Retry": "Thử lại",
- "Retry Chain": "Chuỗi thử lại",
-+ "Retry once when upstream returns an empty Chat or Responses completion before any stream data is sent. Requires Retry Times greater than 0.": "Thử lại một lần khi upstream trả về phần hoàn thành Chat hoặc Responses trống trước khi gửi bất kỳ dữ liệu stream nào. Yêu cầu Retry Times lớn hơn 0.",
- "Retry policy": "Chính sách retry",
- "Retry Suggestion": "Gợi ý thử lại",
- "Retry Times": "Số lần thử lại",
-@@ -4063,6 +4078,7 @@
- "Set API key access restrictions": "Thiết lập hạn chế truy cập cho khóa API",
- "Set API key basic information": "Thiết lập thông tin cơ bản cho khóa API",
- "Set Field": "Đặt trường",
-+ "Set filters and click Search to load usage logs.": "Thiết lập bộ lọc rồi nhấp Tìm kiếm để tải nhật ký sử dụng.",
- "Set filters to customize your dashboard statistics and charts.": "Đặt bộ lọc để tùy chỉnh số liệu thống kê và biểu đồ trên bảng điều khiển của bạn.",
- "Set filters to narrow down your log search results.": "Đặt bộ lọc để thu hẹp kết quả tìm kiếm nhật ký của bạn.",
- "Set Header": "Đặt tiêu đề",
-@@ -4097,6 +4113,7 @@
- "Show": "Hiển thị",
- "Show All": "Hiển thị tất cả",
- "Show all providers including unbound": "Hiển thị tất cả nhà cung cấp (bao gồm chưa liên kết)",
-+ "Show one custom link in the top navigation.": "Hiển thị một liên kết tùy chỉnh trên thanh điều hướng đầu trang.",
- "Show only bound providers": "Chỉ hiển thị nhà cung cấp đã liên kết",
- "Show prices in currency instead of quota.": "Hiển thị giá bằng tiền tệ thay vì hạn ngạch.",
- "Show setup guide": "Hiển thị hướng dẫn thiết lập",
-@@ -4603,6 +4620,7 @@
- "Total earned": "Tổng thu nhập",
- "Total Earned": "Total income",
- "Total GPUs": "Tổng GPU",
-+ "Total Granted": "Tổng hạn mức đã cấp",
- "Total invitation revenue": "Tổng doanh thu mời",
- "Total Log Size": "Tổng dung lượng nhật ký",
- "Total Quota": "Tổng hạn mức",
-@@ -5087,6 +5105,7 @@
- "Your transaction history will appear here": "Lịch sử giao dịch của bạn sẽ xuất hiện ở đây",
- "Your Turnstile secret key": "Khóa bí mật Turnstile của bạn",
- "Your Turnstile site key": "Khóa site Turnstile của bạn",
-+ "Zero Balance": "Số dư bằng không",
- "Zero retention": "Không lưu dữ liệu",
- "Zhipu": "Zhipu",
- "Zhipu V4": "Zhipu V4",
-diff --git a/web/default/src/i18n/locales/zh.json b/web/default/src/i18n/locales/zh.json
-index 34bedf6..15ba178 100644
---- a/web/default/src/i18n/locales/zh.json
-+++ b/web/default/src/i18n/locales/zh.json
-@@ -133,6 +133,7 @@
- "Account deleted successfully": "账户删除成功",
- "Account ID *": "账户 ID *",
- "Account Info": "账户信息",
-+ "Account Status": "账号状态",
- "Account used when authenticating with the SMTP server": "用于与 SMTP 服务器进行身份验证的账户",
- "acknowledge the related legal risks": "确认相关法律风险",
- "Across all groups": "跨所有分组",
-@@ -590,6 +591,7 @@
- "Balance depleted": "余额已耗尽",
- "Balance is shown in quota units": "余额以额度单位显示",
- "Balance queried successfully": "余额查询成功",
-+ "Balance Status": "余额状态",
- "Balance updated successfully": "余额更新成功",
- "Balance updated: {{balance}}": "余额已更新:{{balance}}",
- "Bar Chart": "柱状图",
-@@ -861,6 +863,7 @@
- "Click for details": "点击查看详情",
- "Click save when you're done.": "完成後點擊保存。",
- "Click save when you're done.": "完成后点击保存。",
-+ "Click Search to load logs": "点击搜索加载日志",
- "Click the button below to bind your Telegram account": "点击下方按钮绑定您的 Telegram 账户",
- "Click to open deployment": "点击打开部署",
- "Click to preview audio": "点击预览音乐",
-@@ -1215,6 +1218,7 @@
- "Custom model (comma-separated)": "自定义模型(逗号分隔)",
- "Custom module": "自定义模块",
- "Custom multipliers when specific user groups use specific token groups. Example: VIP users get 0.9x rate when using \"edit_this\" group tokens.": "当特定用户分组使用特定令牌分组时的自定义乘数。示例:VIP 用户在使用“edit_this”分组令牌时获得 0.9 倍费率。",
-+ "Custom navigation item": "自定义导航项",
- "Custom OAuth": "自定义 OAuth",
- "Custom OAuth Providers": "自定义OAuth提供商",
- "Custom Seconds": "自定义秒数",
-@@ -1333,6 +1337,7 @@
- "Designed and Developed by": "设计与开发",
- "designed for scale": "为规模而设计",
- "Designed for the continuous operation of AI models and Agents": "为 AI 模型与 Agent 的持续运营而设计",
-+ "Destination path or URL.": "目标路径或 URL。",
- "Destroyed": "已销毁",
- "Detailed request logs for investigations.": "用于调查的详细请求日志。",
- "Details": "详情",
-@@ -1403,6 +1408,7 @@
- "Display Options": "显示选项",
- "Display Token Statistics": "显示 Token 统计信息",
- "Displayed in": "显示单位",
-+ "Displayed label in the top navigation.": "顶部导航中显示的名称。",
- "Displays the mobile sidebar.": "显示移动侧边栏。",
- "Do not over-trust this feature. IP may be spoofed. Please use with nginx, CDN and other gateways.": "请勿过度信任此功能,IP 可能被伪造,请配合 nginx 和 cdn 等网关使用",
- "Do not repeat check-in; only once per day": "请勿重复签到;每天仅一次",
-@@ -1538,6 +1544,7 @@
- "Enable Discord OAuth": "启用 Discord OAuth",
- "Enable Disk Cache": "启用磁盘缓存",
- "Enable drawing features": "启用绘制功能",
-+ "Enable Empty Completion Retry": "启用空补全重试",
- "Enable filtering": "启用过滤",
- "Enable FunctionCall thoughtSignature Fill": "启用 FunctionCall 思维签名填充",
- "Enable GitHub OAuth": "启用 GitHub OAuth",
-@@ -1807,6 +1814,7 @@
- "Failed to load home page content": "加载首页内容失败",
- "Failed to load image": "无法加载图像",
- "Failed to load key status": "加载密钥状态失败",
-+ "Failed to load log details": "加载日志详情失败",
- "Failed to load logs": "加载日志失败",
- "Failed to load Passkey status": "加载 Passkey 状态失败",
- "Failed to load playground groups": "加载 Playground 分组失败",
-@@ -2561,6 +2569,7 @@
- "Maximum tokens including hidden reasoning tokens": "最大 token 数(含隐藏的推理 token)",
- "Maximum tokens per response": "单次响应最大 token 数",
- "maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1,两者均 ≤ 2,147,483,647",
-+ "May be caused by concurrent billing or manual adjustment.": "可能由并发扣费或手动调整导致。",
- "May be used for training by upstream provider": "可能被上游提供商用于训练",
- "Media pricing": "媒体定价",
- "Median time-to-first-token (TTFT) sampled hourly per group": "按小时采样的各分组首 token 延迟(TTFT)中位数",
-@@ -2762,8 +2771,12 @@
- "name@example.com": "name@example.com",
- "Native format": "原生格式",
- "Native forwarding": "原生转发",
-+ "Navigation link": "导航链接",
-+ "Navigation name": "导航名称",
- "Need a redemption code?": "需要兑换码?",
- "Needs API key": "需要 API 密钥",
-+ "Negative": "负余额",
-+ "Negative Balance": "负余额",
- "Nested JSON defining per-group rules for adding (+:), removing (-:), or appending usable groups.": "嵌套 JSON,定义按分组添加(+:)、移除(-:)或追加可用分组的规则。",
- "Nested JSON: source group →": "嵌套 JSON:源分组 →",
- "Network proxy for this channel (supports socks5 protocol)": "此渠道的网络代理(支持 socks5 协议)",
-@@ -3344,6 +3357,7 @@
- "Port": "端口",
- "Port must be a positive integer": "端口必须为正整数",
- "Position in the stack": "系统栈定位",
-+ "Positive Balance": "正常余额",
- "Post Delta": "结算差额",
- "PostgreSQL detected": "检测到 PostgreSQL",
- "PostgreSQL offers advanced reliability and data integrity for production workloads.": "PostgreSQL 为生产工作负载提供高级可靠性和数据完整性。",
-@@ -3781,6 +3795,7 @@
- "Retention days": "保留天数",
- "Retry": "重试",
- "Retry Chain": "重试链路",
-+ "Retry once when upstream returns an empty Chat or Responses completion before any stream data is sent. Requires Retry Times greater than 0.": "当上游在发送任何流数据前返回空的 Chat 或 Responses 补全时重试一次。需要将重试次数设置为大于 0。",
- "Retry policy": "重试策略",
- "Retry Suggestion": "重试建议",
- "Retry Times": "重试次数",
-@@ -4063,6 +4078,7 @@
- "Set API key access restrictions": "设置令牌的访问限制",
- "Set API key basic information": "设置令牌的基本信息",
- "Set Field": "设置字段",
-+ "Set filters and click Search to load usage logs.": "设置筛选条件后点击搜索加载使用日志。",
- "Set filters to customize your dashboard statistics and charts.": "设置筛选器以自定义您的仪表板统计数据和图表。",
- "Set filters to narrow down your log search results.": "设置筛选器以缩小日志搜索结果范围。",
- "Set Header": "设请求头",
-@@ -4097,6 +4113,7 @@
- "Show": "显示",
- "Show All": "显示全部",
- "Show all providers including unbound": "显示所有提供商(包括未绑定)",
-+ "Show one custom link in the top navigation.": "在顶部导航中显示一个自定义链接。",
- "Show only bound providers": "仅显示已绑定的提供商",
- "Show prices in currency instead of quota.": "以货币而非配额显示价格。",
- "Show setup guide": "显示设置引导",
-@@ -4603,6 +4620,7 @@
- "Total earned": "累计获得",
- "Total Earned": "总收入",
- "Total GPUs": "总 GPU",
-+ "Total Granted": "总授予额度",
- "Total invitation revenue": "总邀请收入",
- "Total Log Size": "日志总大小",
- "Total Quota": "总额度",
-@@ -5087,6 +5105,7 @@
- "Your transaction history will appear here": "您的交易历史会显示在这里",
- "Your Turnstile secret key": "您的 Turnstile 密钥",
- "Your Turnstile site key": "您的 Turnstile 站点密钥",
-+ "Zero Balance": "零余额",
- "Zero retention": "零数据保留",
- "Zhipu": "智谱",
- "Zhipu V4": "智谱 V4",
-diff --git a/web/default/src/lib/header-nav-modules.ts b/web/default/src/lib/header-nav-modules.ts
-index bee15be..740769e 100644
---- a/web/default/src/lib/header-nav-modules.ts
-+++ b/web/default/src/lib/header-nav-modules.ts
-@@ -21,6 +21,12 @@ export type HeaderNavAccessConfig = {
- requireAuth: boolean
- }
-
-+export type HeaderNavCustomConfig = {
-+ enabled: boolean
-+ title: string
-+ href: string
-+}
-+
- export type HeaderNavModule = 'rankings' | 'pricing'
-
- export type HeaderNavModulesConfig = {
-@@ -30,7 +36,8 @@ export type HeaderNavModulesConfig = {
- rankings: HeaderNavAccessConfig
- docs: boolean
- about: boolean
-- [key: string]: boolean | HeaderNavAccessConfig
-+ custom: HeaderNavCustomConfig
-+ [key: string]: boolean | HeaderNavAccessConfig | HeaderNavCustomConfig
- }
-
- export const HEADER_NAV_DEFAULT: HeaderNavModulesConfig = {
-@@ -46,6 +53,11 @@ export const HEADER_NAV_DEFAULT: HeaderNavModulesConfig = {
- },
- docs: true,
- about: true,
-+ custom: {
-+ enabled: false,
-+ title: '',
-+ href: '',
-+ },
- }
-
- export const HEADER_NAV_MODULE_DEFAULTS: Record<
-@@ -79,6 +91,7 @@ function cloneHeaderNavDefault(): HeaderNavModulesConfig {
- ...HEADER_NAV_DEFAULT,
- pricing: { ...HEADER_NAV_DEFAULT.pricing },
- rankings: { ...HEADER_NAV_DEFAULT.rankings },
-+ custom: { ...HEADER_NAV_DEFAULT.custom },
- }
- }
-
-@@ -124,6 +137,23 @@ export function parseHeaderNavAccess(
- return { ...fallback }
- }
-
-+export function parseHeaderNavCustom(
-+ raw: unknown,
-+ fallback: HeaderNavCustomConfig
-+): HeaderNavCustomConfig {
-+ if (!raw || typeof raw !== 'object') {
-+ return { ...fallback }
-+ }
-+
-+ const record = raw as Record
-+ return {
-+ enabled: parseHeaderNavBoolean(record.enabled, fallback.enabled),
-+ title:
-+ typeof record.title === 'string' ? record.title.trim() : fallback.title,
-+ href: typeof record.href === 'string' ? record.href.trim() : fallback.href,
-+ }
-+}
-+
- export function parseHeaderNavAccessOption(
- raw: unknown,
- fallback: HeaderNavAccessConfig
-@@ -170,6 +200,10 @@ export function parseHeaderNavModules(raw: unknown): HeaderNavModulesConfig {
- result.rankings = parseHeaderNavAccess(value, result.rankings)
- return
- }
-+ if (key === 'custom') {
-+ result.custom = parseHeaderNavCustom(value, result.custom)
-+ return
-+ }
-
- const fallback = result[key]
- if (
-diff --git a/web/default/src/routes/_authenticated/usage-logs/$section.tsx b/web/default/src/routes/_authenticated/usage-logs/$section.tsx
-index e75005d..b378934 100644
---- a/web/default/src/routes/_authenticated/usage-logs/$section.tsx
-+++ b/web/default/src/routes/_authenticated/usage-logs/$section.tsx
-@@ -38,6 +38,7 @@ const logTypeSearchSchema = z
- const usageLogsSearchSchema = z.object({
- page: z.number().optional().catch(1),
- pageSize: z.number().optional().catch(undefined),
-+ searchVersion: z.coerce.number().optional().catch(undefined),
- type: logTypeSearchSchema.optional(),
- filter: z.string().optional().catch(''),
- model: z.string().optional().catch(''),
-diff --git a/web/default/src/routes/_authenticated/users/index.tsx b/web/default/src/routes/_authenticated/users/index.tsx
-index 2b9a29c..e4fed0b 100644
---- a/web/default/src/routes/_authenticated/users/index.tsx
-+++ b/web/default/src/routes/_authenticated/users/index.tsx
-@@ -27,7 +27,7 @@ const usersSearchSchema = z.object({
- pageSize: z.number().optional().catch(undefined),
- filter: z.string().optional().catch(''),
- status: z
-- .array(z.enum(['1', '2']))
-+ .array(z.enum(['-1', '1', '2']))
- .optional()
- .catch([]),
- role: z
-@@ -35,6 +35,10 @@ const usersSearchSchema = z.object({
- .optional()
- .catch([]),
- group: z.string().optional().catch(''),
-+ quota_status: z
-+ .array(z.enum(['negative', 'zero', 'positive']))
-+ .optional()
-+ .catch([]),
- })
-
- export const Route = createFileRoute('/_authenticated/users/')({
diff --git a/web/bun.lock b/web/bun.lock
index ba2b366..2326d41 100644
--- a/web/bun.lock
+++ b/web/bun.lock
@@ -73,6 +73,7 @@
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
+ "@types/jsdom": "^28.0.3",
"@types/node": "^25.9.1",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
@@ -82,6 +83,7 @@
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
+ "jsdom": "^29.1.1",
"knip": "^6.14.2",
"prettier": "catalog:",
"prettier-plugin-tailwindcss": "^0.8.0",
@@ -126,6 +128,14 @@
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
+ "@asamuzakjp/css-color": ["@asamuzakjp/css-color@5.1.11", "", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@csstools/css-calc": "^3.2.0", "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg=="],
+
+ "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@7.1.1", "", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1" } }, "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ=="],
+
+ "@asamuzakjp/generational-cache": ["@asamuzakjp/generational-cache@1.0.1", "", {}, "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg=="],
+
+ "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="],
+
"@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="],
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
@@ -192,8 +202,22 @@
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
+ "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="],
+
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
+ "@csstools/color-helpers": ["@csstools/color-helpers@6.1.0", "", {}, "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg=="],
+
+ "@csstools/css-calc": ["@csstools/css-calc@3.2.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg=="],
+
+ "@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.9", "", { "dependencies": { "@csstools/color-helpers": "^6.1.0", "@csstools/css-calc": "^3.2.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A=="],
+
+ "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="],
+
+ "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.5", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A=="],
+
+ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="],
+
"@date-fns/tz": ["@date-fns/tz@1.5.0", "", {}, "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg=="],
"@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="],
@@ -262,6 +286,8 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="],
+ "@exodus/bytes": ["@exodus/bytes@1.15.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q=="],
+
"@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
@@ -846,6 +872,8 @@
"@types/js-cookie": ["@types/js-cookie@3.0.6", "", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="],
+ "@types/jsdom": ["@types/jsdom@28.0.3", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^8.0.0", "undici-types": "^7.21.0" } }, "sha512-/HQ2uFoetFTXuye8vzIcHw2z6Fwi7Hi/qcgC+RoS9NCyewiqxhVGqlG+ViGB6lkax481R6dmhf1I7lIGlzJStQ=="],
+
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/katex": ["@types/katex@0.16.8", "", {}, "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg=="],
@@ -868,6 +896,8 @@
"@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="],
+ "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
+
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
@@ -994,6 +1024,8 @@
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.33", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw=="],
+ "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
+
"binary-searching": ["binary-searching@2.0.5", "", {}, "sha512-v4N2l3RxL+m4zDxyxz3Ne2aTmiPn8ZUpKFpdPtO+ItW1NcTCXA7JeHG5GMBSvoKSkQZ9ycS+EouDVxYB9ufKWA=="],
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
@@ -1096,6 +1128,8 @@
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+ "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
+
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
@@ -1176,12 +1210,16 @@
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
+ "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="],
+
"date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="],
"dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
+
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
@@ -1244,7 +1282,7 @@
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
- "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+ "entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="],
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
@@ -1486,6 +1524,8 @@
"hono": ["hono@4.12.23", "", {}, "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA=="],
+ "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
+
"html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="],
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
@@ -1570,6 +1610,8 @@
"is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="],
+ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
+
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"is-regexp": ["is-regexp@3.1.0", "", {}, "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA=="],
@@ -1602,6 +1644,8 @@
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
+ "jsdom": ["jsdom@29.1.1", "", { "dependencies": { "@asamuzakjp/css-color": "^5.1.11", "@asamuzakjp/dom-selector": "^7.1.1", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.3", "@exodus/bytes": "^1.15.0", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.3.5", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.1", "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q=="],
+
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
@@ -1684,7 +1728,7 @@
"lottie-web": ["lottie-web@5.13.0", "", {}, "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ=="],
- "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+ "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
"lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="],
@@ -1738,6 +1782,8 @@
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
+ "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
+
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
@@ -1922,7 +1968,7 @@
"parse-svg-path": ["parse-svg-path@0.1.2", "", {}, "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ=="],
- "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
+ "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
@@ -2178,6 +2224,8 @@
"sass-formatter": ["sass-formatter@0.7.9", "", { "dependencies": { "suf-log": "^2.5.3" } }, "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw=="],
+ "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
+
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"screenfull": ["screenfull@5.2.0", "", {}, "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA=="],
@@ -2290,6 +2338,8 @@
"swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="],
+ "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
+
"tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
@@ -2328,6 +2378,8 @@
"tough-cookie": ["tough-cookie@6.0.1", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw=="],
+ "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
+
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
@@ -2360,6 +2412,8 @@
"unbash": ["unbash@3.0.0", "", {}, "sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA=="],
+ "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
+
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
@@ -2430,14 +2484,22 @@
"void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="],
+ "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
+
"walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
+ "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="],
+
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
+ "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="],
+
+ "whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="],
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
@@ -2448,6 +2510,10 @@
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
+ "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
+
+ "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
+
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
@@ -2474,6 +2540,8 @@
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+ "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+
"@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
@@ -2622,6 +2690,10 @@
"geojson-flatten/minimist": ["minimist@1.2.0", "", {}, "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw=="],
+ "hast-util-from-html/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
+
+ "hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
+
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
@@ -2768,6 +2840,10 @@
"express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
+ "hast-util-from-html/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+
+ "hast-util-raw/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+
"mermaid/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"micromark-extension-math/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
diff --git a/web/default/package.json b/web/default/package.json
index 67f9977..430b27e 100644
--- a/web/default/package.json
+++ b/web/default/package.json
@@ -82,6 +82,7 @@
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
+ "@types/jsdom": "^28.0.3",
"@types/node": "^25.9.1",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
@@ -91,6 +92,7 @@
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
+ "jsdom": "^29.1.1",
"knip": "^6.14.2",
"prettier": "catalog:",
"prettier-plugin-tailwindcss": "^0.8.0",
diff --git a/web/default/src/components/layout/components/app-header.tsx b/web/default/src/components/layout/components/app-header.tsx
index 301de9f..75b8230 100644
--- a/web/default/src/components/layout/components/app-header.tsx
+++ b/web/default/src/components/layout/components/app-header.tsx
@@ -130,6 +130,7 @@ export function AppHeader({
void
+ onCloseForToday: () => void
unreadCount: number
activeTab: 'notice' | 'announcements'
onTabChange: (tab: 'notice' | 'announcements') => void
@@ -271,6 +272,7 @@ function AnnouncementsContent({
export function NotificationPopover({
open,
onOpenChange,
+ onCloseForToday,
unreadCount,
activeTab,
onTabChange,
@@ -343,7 +345,10 @@ export function NotificationPopover({
-
+
+
diff --git a/web/default/src/components/ui/markdown.test.ts b/web/default/src/components/ui/markdown.test.ts
new file mode 100644
index 0000000..a32080c
--- /dev/null
+++ b/web/default/src/components/ui/markdown.test.ts
@@ -0,0 +1,92 @@
+/*
+Copyright (C) 2023-2026 MAX-API-Next
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+
+For commercial licensing, please contact https://github.com/MAX-API-Next/MAX-API/issues
+*/
+import assert from 'node:assert/strict'
+import { describe, test } from 'node:test'
+import { JSDOM } from 'jsdom'
+import { renderMarkdownForTest } from './markdown'
+
+const dom = new JSDOM('')
+Object.defineProperty(globalThis, 'window', {
+ configurable: true,
+ value: dom.window,
+})
+
+describe('renderMarkdownForTest', () => {
+ test('renders markdown links without losing the marked parser context', () => {
+ const html = renderMarkdownForTest('[MAX API](https://example.com)')
+
+ assert.match(html, /MAX API<\/a>/)
+ })
+
+ test('falls back to link text for invalid href values', () => {
+ const badHref = `http://example.com/${String.fromCharCode(0xd800)}`
+ const html = renderMarkdownForTest(`[Broken](${badHref})`)
+
+ assert.equal(html, '