From 9c1fa04e19bfa017ad060c0fa0bb9903c515304e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E9=9B=A8=E6=99=A8?= Date: Sat, 23 May 2026 01:24:47 +0800 Subject: [PATCH 1/5] feat: add Langfuse trace content notice on Logs and Keys pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Langfuse is enabled AND trace_content (full statistics) is enabled, display a warning banner on the Logs and Keys pages: '本站使用Langfuse记录用户提示词等数据,请注意安全。' - Add langfuse_trace_content field to /api/status public endpoint - Create LangfuseTraceNotice reusable component - Add notice to Usage Logs page - Add notice to API Keys page - Add i18n translations for en/zh/fr/ja/ru/vi --- controller/misc.go | 2 ++ .../src/components/langfuse-trace-notice.tsx | 22 +++++++++++++++++++ web/default/src/features/keys/index.tsx | 6 ++++- web/default/src/features/usage-logs/index.tsx | 2 ++ .../i18n/locales/_reports/_sync-report.json | 22 +++++++++---------- .../locales/_reports/fr.untranslated.json | 1 + .../locales/_reports/ja.untranslated.json | 2 ++ .../locales/_reports/ru.untranslated.json | 2 ++ .../locales/_reports/vi.untranslated.json | 1 + web/default/src/i18n/locales/en.json | 15 ++++++++++++- web/default/src/i18n/locales/fr.json | 17 +++++++++++++- web/default/src/i18n/locales/ja.json | 17 +++++++++++++- web/default/src/i18n/locales/ru.json | 17 +++++++++++++- web/default/src/i18n/locales/vi.json | 17 +++++++++++++- web/default/src/i18n/locales/zh.json | 2 +- 15 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 web/default/src/components/langfuse-trace-notice.tsx diff --git a/controller/misc.go b/controller/misc.go index a91efec7bce..d4ff81eb356 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -14,6 +14,7 @@ import ( "github.com/QuantumNous/new-api/oauth" "github.com/QuantumNous/new-api/setting" "github.com/QuantumNous/new-api/setting/console_setting" + "github.com/QuantumNous/new-api/setting/langfuse_setting" "github.com/QuantumNous/new-api/setting/operation_setting" "github.com/QuantumNous/new-api/setting/system_setting" @@ -119,6 +120,7 @@ func GetStatus(c *gin.Context) { "user_agreement_enabled": legalSetting.UserAgreement != "", "privacy_policy_enabled": legalSetting.PrivacyPolicy != "", "checkin_enabled": operation_setting.GetCheckinSetting().Enabled, + "langfuse_trace_content": langfuse_setting.GetLangfuseSetting().Enabled && langfuse_setting.GetLangfuseSetting().TraceContent, } // 根据启用状态注入可选内容 diff --git a/web/default/src/components/langfuse-trace-notice.tsx b/web/default/src/components/langfuse-trace-notice.tsx new file mode 100644 index 00000000000..12419175538 --- /dev/null +++ b/web/default/src/components/langfuse-trace-notice.tsx @@ -0,0 +1,22 @@ +import { ShieldAlert } from 'lucide-react' +import { useTranslation } from 'react-i18next' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { useStatus } from '@/hooks/use-status' + +export function LangfuseTraceNotice() { + const { t } = useTranslation() + const { status } = useStatus() + + if (!status?.langfuse_trace_content) return null + + return ( + + + + {t( + 'This site uses Langfuse to record user prompts and other data. Please be aware of security risks.' + )} + + + ) +} diff --git a/web/default/src/features/keys/index.tsx b/web/default/src/features/keys/index.tsx index c7bb51fcaca..ea09cb11edb 100644 --- a/web/default/src/features/keys/index.tsx +++ b/web/default/src/features/keys/index.tsx @@ -1,4 +1,5 @@ import { useTranslation } from 'react-i18next' +import { LangfuseTraceNotice } from '@/components/langfuse-trace-notice' import { SectionPageLayout } from '@/components/layout' import { ApiKeysDialogs } from './components/api-keys-dialogs' import { ApiKeysProvider } from './components/api-keys-provider' @@ -14,7 +15,10 @@ export function ApiKeys() { {t('Manage your API keys for accessing the service')} - +
+ + +
diff --git a/web/default/src/features/usage-logs/index.tsx b/web/default/src/features/usage-logs/index.tsx index c27c29f3354..0e094f813e1 100644 --- a/web/default/src/features/usage-logs/index.tsx +++ b/web/default/src/features/usage-logs/index.tsx @@ -1,6 +1,7 @@ import { useCallback, useMemo } from 'react' import { getRouteApi, useNavigate } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' +import { LangfuseTraceNotice } from '@/components/langfuse-trace-notice' import { useSidebarConfig } from '@/hooks/use-sidebar-config' import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' import { SectionPageLayout } from '@/components/layout' @@ -106,6 +107,7 @@ function UsageLogsContent() {
+ {showTaskSwitcher && ( diff --git a/web/default/src/i18n/locales/_reports/_sync-report.json b/web/default/src/i18n/locales/_reports/_sync-report.json index ab28efa1383..a9482bb317b 100644 --- a/web/default/src/i18n/locales/_reports/_sync-report.json +++ b/web/default/src/i18n/locales/_reports/_sync-report.json @@ -1,41 +1,41 @@ { - "base": "en.json", + "base": "zh.json", "locales": { "en": { "file": "en.json", - "missingCount": 0, + "missingCount": 12, "extrasCount": 0, "untranslatedCount": 0 }, "fr": { "file": "fr.json", - "missingCount": 0, + "missingCount": 14, "extrasCount": 0, - "untranslatedCount": 9 + "untranslatedCount": 10 }, "ja": { "file": "ja.json", - "missingCount": 0, + "missingCount": 14, "extrasCount": 0, - "untranslatedCount": 123 + "untranslatedCount": 125 }, "ru": { "file": "ru.json", - "missingCount": 0, + "missingCount": 14, "extrasCount": 0, - "untranslatedCount": 127 + "untranslatedCount": 129 }, "vi": { "file": "vi.json", - "missingCount": 0, + "missingCount": 14, "extrasCount": 0, - "untranslatedCount": 9 + "untranslatedCount": 10 }, "zh": { "file": "zh.json", "missingCount": 0, "extrasCount": 0, - "untranslatedCount": 97 + "untranslatedCount": 0 } } } diff --git a/web/default/src/i18n/locales/_reports/fr.untranslated.json b/web/default/src/i18n/locales/_reports/fr.untranslated.json index a686891329a..557f333a214 100644 --- a/web/default/src/i18n/locales/_reports/fr.untranslated.json +++ b/web/default/src/i18n/locales/_reports/fr.untranslated.json @@ -1,6 +1,7 @@ { "A text banner displayed at the top of all pages. Leave empty to disable.": "A text banner displayed at the top of all pages. Leave empty to disable.", "Add animation effects to the banner background.": "Add animation effects to the banner background.", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Choose a preset effect and customize colors and speed.": "Choose a preset effect and customize colors and speed.", "Comma-separated color values for the gradient.": "Comma-separated color values for the gradient.", "Configure the banner background style.": "Configure the banner background style.", diff --git a/web/default/src/i18n/locales/_reports/ja.untranslated.json b/web/default/src/i18n/locales/_reports/ja.untranslated.json index da8aeb8516f..5339c826cae 100644 --- a/web/default/src/i18n/locales/_reports/ja.untranslated.json +++ b/web/default/src/i18n/locales/_reports/ja.untranslated.json @@ -17,6 +17,7 @@ "Anthropic": "Anthropic", "API URL": "API URL", "API2GPT": "API2GPT", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *", "Background Type": "Background Type", "Banner Content": "Banner Content", @@ -72,6 +73,7 @@ "https://worker.example.workers.dev": "https://worker.example.workers.dev", "https://your-server.example.com": "https://your-server.example.com", "https://yourdomain.com": "https://yourdomain.com", + "Initial Subscription Plan": "Initial Subscription Plan", "Intensity": "Intensity", "Jimeng": "Jimeng", "JustSong": "JustSong", diff --git a/web/default/src/i18n/locales/_reports/ru.untranslated.json b/web/default/src/i18n/locales/_reports/ru.untranslated.json index 3de5a3ac7ba..40cd6b7de2e 100644 --- a/web/default/src/i18n/locales/_reports/ru.untranslated.json +++ b/web/default/src/i18n/locales/_reports/ru.untranslated.json @@ -20,6 +20,7 @@ "Animation Type": "Animation Type", "Anthropic": "Anthropic", "API2GPT": "API2GPT", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *", "Background Type": "Background Type", "Baidu V2": "Baidu V2", @@ -76,6 +77,7 @@ "https://worker.example.workers.dev": "https://worker.example.workers.dev", "https://your-server.example.com": "https://your-server.example.com", "https://yourdomain.com": "https://yourdomain.com", + "Initial Subscription Plan": "Initial Subscription Plan", "Intensity": "Intensity", "Jimeng": "Jimeng", "JustSong": "JustSong", diff --git a/web/default/src/i18n/locales/_reports/vi.untranslated.json b/web/default/src/i18n/locales/_reports/vi.untranslated.json index a686891329a..557f333a214 100644 --- a/web/default/src/i18n/locales/_reports/vi.untranslated.json +++ b/web/default/src/i18n/locales/_reports/vi.untranslated.json @@ -1,6 +1,7 @@ { "A text banner displayed at the top of all pages. Leave empty to disable.": "A text banner displayed at the top of all pages. Leave empty to disable.", "Add animation effects to the banner background.": "Add animation effects to the banner background.", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Choose a preset effect and customize colors and speed.": "Choose a preset effect and customize colors and speed.", "Comma-separated color values for the gradient.": "Comma-separated color values for the gradient.", "Configure the banner background style.": "Configure the banner background style.", diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json index ca8be34712d..45b8b19709c 100644 --- a/web/default/src/i18n/locales/en.json +++ b/web/default/src/i18n/locales/en.json @@ -741,6 +741,7 @@ "Configuration for Stripe payment integration": "Configuration for Stripe payment integration", "Configuration required": "Configuration required", "Configure": "Configure", + "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", "Configure a Creem product for user recharge options.": "Configure a Creem product for user recharge options.", "Configure a custom ratio for when users use a specific token group.": "Configure a custom ratio for when users use a specific token group.", "Configure a group that users can select when creating API keys.": "Configure a group that users can select when creating API keys.", @@ -1250,6 +1251,14 @@ "Embeddings": "Embeddings", "Empty value will be saved as {}.": "Empty value will be saved as {}.", "Enable": "Enable", + "Enable Langfuse Tracing": "启用 Langfuse 追踪", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", + "Langfuse Host": "Langfuse 地址", + "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", + "Langfuse Tracing": "Langfuse 追踪", + "Public Key": "公钥", + "Secret Key": "Secret Key", + "Trace Request/Response Content": "追踪请求/响应内容", "Enable 2FA": "Enable 2FA", "Enable All": "Enable All", "Enable check-in feature": "Enable check-in feature", @@ -1881,6 +1890,7 @@ "Interface Language": "Interface Language", "Interface Style": "Interface Style", "Interface style updated. Reloading...": "Interface style updated. Reloading...", + "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", "Internal Notes": "Internal Notes", "Internal notes (not shown to users)": "Internal notes (not shown to users)", "Internal Server Error!": "Internal Server Error!", @@ -3160,7 +3170,6 @@ "seconds": "seconds", "Secret env (JSON object)": "Secret env (JSON object)", "Secret environment variables (JSON)": "Secret environment variables (JSON)", - "Secret Key": "Secret Key", "Secure & Reliable": "Secure & Reliable", "Security": "Security", "Security Check": "Security Check", @@ -3233,6 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Selected conflicts were overwritten successfully.", "Self-Use Mode": "Self-Use Mode", "Send": "Send", + "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", + "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", "Send code": "Send code", "Send email alerts when a user falls below this quota": "Send email alerts when a user falls below this quota", "Sending...": "Sending...", @@ -3357,6 +3368,7 @@ "Statistical quota": "Statistical quota", "Statistical tokens": "Statistical tokens", "Statistics reset": "Statistics reset", + "Statistics": "统计", "Status": "Status", "Status & Sync": "Status & Sync", "Status Code": "Status Code", @@ -3555,6 +3567,7 @@ "This project must be used in compliance with the": "This project must be used in compliance with the", "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.", "This site currently has {{count}} models enabled": "This site currently has {{count}} models enabled", + "This site uses Langfuse to record user prompts and other data. Please be aware of security risks.": "This site uses Langfuse to record user prompts and other data. Please be aware of security risks.", "this token group": "this token group", "this user group": "this user group", "This user has no bindings": "This user has no bindings", diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json index 28377422d36..5f159350338 100644 --- a/web/default/src/i18n/locales/fr.json +++ b/web/default/src/i18n/locales/fr.json @@ -400,6 +400,7 @@ "Auto-discovers endpoints from the provider": "Découvre automatiquement les points de terminaison du fournisseur", "Auto-fill when one field exists and another is missing": "Remplissage automatique si un champ existe et l'autre est manquant", "Auto-retry status codes": "Codes de statut de nouvelle tentative auto", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Automatically disable channel on repeated failures": "Désactiver automatiquement le canal en cas d'échecs répétés", "Automatically disable channels exceeding this response time": "Désactiver automatiquement les canaux dépassant ce temps de réponse", "Automatically disable channels when tests fail": "Désactiver automatiquement les canaux lorsque les tests échouent", @@ -740,6 +741,7 @@ "Configuration for Stripe payment integration": "Configuration pour l'intégration de paiement Stripe", "Configuration required": "Configuration requise", "Configure": "Configurer", + "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", "Configure a Creem product for user recharge options.": "Configurez un produit Creem pour les options de recharge utilisateur.", "Configure a custom ratio for when users use a specific token group.": "Configurer un ratio personnalisé lorsque les utilisateurs utilisent un groupe de jetons spécifique.", "Configure a group that users can select when creating API keys.": "Configurer un groupe que les utilisateurs peuvent sélectionner lors de la création de clés API.", @@ -1249,6 +1251,14 @@ "Embeddings": "Embeddings", "Empty value will be saved as {}.": "Une valeur vide sera enregistrée comme {}.", "Enable": "Activer", + "Enable Langfuse Tracing": "启用 Langfuse 追踪", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", + "Langfuse Host": "Langfuse 地址", + "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", + "Langfuse Tracing": "Langfuse 追踪", + "Public Key": "公钥", + "Secret Key": "Clé secrète", + "Trace Request/Response Content": "追踪请求/响应内容", "Enable 2FA": "Activer 2FA", "Enable All": "Tout activer", "Enable check-in feature": "Activer la fonction de connexion", @@ -1864,6 +1874,7 @@ "Initialize": "Initialiser", "Initialize system": "Initialiser le système", "Initializing…": "Initialisation…", + "Initial Subscription Plan": "Initial Subscription Plan", "Inpaint": "Inpainting", "Input": "Entrée", "Input mode": "Mode d'entrée", @@ -1879,6 +1890,7 @@ "Interface Language": "Langue de l'interface", "Interface Style": "Style de l'interface", "Interface style updated. Reloading...": "Style de l'interface mis ? jour. Rechargement...", + "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", "Internal Notes": "Notes internes", "Internal notes (not shown to users)": "Notes internes (non visibles par les utilisateurs)", "Internal Server Error!": "Erreur interne du serveur !", @@ -3158,7 +3170,6 @@ "seconds": "secondes", "Secret env (JSON object)": "Environnement secret (objet JSON)", "Secret environment variables (JSON)": "Variables d'environnement secrètes (JSON)", - "Secret Key": "Clé secrète", "Secure & Reliable": "Sécurisé et fiable", "Security": "Sécurité", "Security Check": "Vérification de sécurité", @@ -3231,6 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Les conflits sélectionnés ont été écrasés avec succès.", "Self-Use Mode": "Mode d'utilisation personnelle", "Send": "Envoyer", + "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", + "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", "Send code": "Envoyer le code", "Send email alerts when a user falls below this quota": "Envoyer des alertes par e-mail lorsqu'un utilisateur descend en dessous de ce quota", "Sending...": "Envoi en cours...", @@ -3355,6 +3368,7 @@ "Statistical quota": "Quota statistique", "Statistical tokens": "Jetons statistiques", "Statistics reset": "Statistiques réinitialisées", + "Statistics": "统计", "Status": "Statut", "Status & Sync": "Statut et synchronisation", "Status Code": "Code de statut", @@ -3553,6 +3567,7 @@ "This project must be used in compliance with the": "Ce projet doit être utilisé conformément aux", "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Cet enregistrement provient d’une instance avant la mise à niveau et n’inclut pas d’audits. Mettez à jour l’instance pour enregistrer l’IP du serveur, l’IP de callback, le moyen de paiement et la version du système.", "This site currently has {{count}} models enabled": "Ce site compte actuellement {{count}} modèles activés", + "This site uses Langfuse to record user prompts and other data. Please be aware of security risks.": "Ce site utilise Langfuse pour enregistrer les invites des utilisateurs et d'autres données. Veuillez faire attention à la sécurité.", "this token group": "ce groupe de jetons", "this user group": "ce groupe d'utilisateurs", "This user has no bindings": "Cet utilisateur n'a aucune liaison", diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json index c807f1d06df..b1001683b20 100644 --- a/web/default/src/i18n/locales/ja.json +++ b/web/default/src/i18n/locales/ja.json @@ -400,6 +400,7 @@ "Auto-discovers endpoints from the provider": "プロバイダーからエンドポイントを自動検出します", "Auto-fill when one field exists and another is missing": "一方のフィールドがあり他方が欠けている場合に自動補完", "Auto-retry status codes": "自動リトライするステータスコード", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Automatically disable channel on repeated failures": "繰り返しの失敗でチャンネルを自動的に無効にする", "Automatically disable channels exceeding this response time": "この応答時間を超えるチャネルを自動的に無効にする", "Automatically disable channels when tests fail": "テストが失敗したときにチャネルを自動的に無効にする", @@ -740,6 +741,7 @@ "Configuration for Stripe payment integration": "Stripe決済連携のための設定", "Configuration required": "設定が必要です", "Configure": "設定", + "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", "Configure a Creem product for user recharge options.": "ユーザー チャージオプション用の Creem 製品を設定。", "Configure a custom ratio for when users use a specific token group.": "ユーザーが特定のトークングループを使用する際のカスタム倍率を設定します。", "Configure a group that users can select when creating API keys.": "ユーザーがAPIキー作成時に選択できるグループを設定します。", @@ -1249,6 +1251,14 @@ "Embeddings": "埋め込み", "Empty value will be saved as {}.": "空の値は {} として保存されます。", "Enable": "有効にする", + "Enable Langfuse Tracing": "启用 Langfuse 追踪", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", + "Langfuse Host": "Langfuse 地址", + "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", + "Langfuse Tracing": "Langfuse 追踪", + "Public Key": "公钥", + "Secret Key": "シークレットキー", + "Trace Request/Response Content": "追踪请求/响应内容", "Enable 2FA": "2FA を有効にする", "Enable All": "すべて有効にする", "Enable check-in feature": "チェックイン機能を有効にする", @@ -1864,6 +1874,7 @@ "Initialize": "初期化", "Initialize system": "システム初期化", "Initializing…": "初期化中…", + "Initial Subscription Plan": "Initial Subscription Plan", "Inpaint": "インペイント", "Input": "入力", "Input mode": "入力モード", @@ -1879,6 +1890,7 @@ "Interface Language": "インターフェース言語", "Interface Style": "????????????", "Interface style updated. Reloading...": "??????????????????????????????...", + "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", "Internal Notes": "内部メモ", "Internal notes (not shown to users)": ":内部メモ(ユーザーには表示されません)", "Internal Server Error!": "内部サーバーエラー!", @@ -3158,7 +3170,6 @@ "seconds": "秒", "Secret env (JSON object)": "シークレット env (JSON オブジェクト)", "Secret environment variables (JSON)": "シークレット環境変数(JSON)", - "Secret Key": "シークレットキー", "Secure & Reliable": "セキュア&信頼性", "Security": "セキュリティ", "Security Check": "セキュリティチェック", @@ -3231,6 +3242,8 @@ "Selected conflicts were overwritten successfully.": "選択した競合が正常に上書きされました。", "Self-Use Mode": "セルフユースモード", "Send": "送信", + "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", + "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", "Send code": "コードを送信", "Send email alerts when a user falls below this quota": "ユーザーがこのクォータを下回ったときにメールアラートを送信", "Sending...": "送信中...", @@ -3355,6 +3368,7 @@ "Statistical quota": "統計クォータ", "Statistical tokens": "統計トークン", "Statistics reset": "統計をリセットしました", + "Statistics": "统计", "Status": "ステータス", "Status & Sync": "ステータスと同期", "Status Code": "ステータスコード", @@ -3553,6 +3567,7 @@ "This project must be used in compliance with the": "このプロジェクトは、以下を遵守して使用する必要があります", "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "古いバージョンのインスタンスがこの記録を書き込み、監査情報がありません。最新に更新し、サーバーIP・コールバックIP・支払方法・OSバージョンの記録を有効にしてください。", "This site currently has {{count}} models enabled": "このサイトでは現在 {{count}} 個のモデルが有効です", + "This site uses Langfuse to record user prompts and other data. Please be aware of security risks.": "このサイトはLangfuseを使用してユーザーのプロンプトなどのデータを記録しています。セキュリティにご注意ください。", "this token group": "このトークングループ", "this user group": "このユーザーグループ", "This user has no bindings": "このユーザーには連携がありません", diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json index 8fc42d4346c..94373770af9 100644 --- a/web/default/src/i18n/locales/ru.json +++ b/web/default/src/i18n/locales/ru.json @@ -400,6 +400,7 @@ "Auto-discovers endpoints from the provider": "Автоматически обнаруживает конечные точки от провайдера", "Auto-fill when one field exists and another is missing": "Автозаполнение, когда одно поле есть, а другое отсутствует", "Auto-retry status codes": "Коды авто-повтора", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Automatically disable channel on repeated failures": "Автоматически отключать канал при повторных неудачах", "Automatically disable channels exceeding this response time": "Автоматически отключать каналы, превышающие это время ответа", "Automatically disable channels when tests fail": "Автоматически отключать каналы при сбое тестов", @@ -740,6 +741,7 @@ "Configuration for Stripe payment integration": "Конфигурация для интеграции платежей Stripe", "Configuration required": "Требуется настройка", "Configure": "Настройка", + "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", "Configure a Creem product for user recharge options.": "Настройте продукт Creem для опций пополнения пользователя.", "Configure a custom ratio for when users use a specific token group.": "Настроить пользовательский коэффициент при использовании определённой группы токенов.", "Configure a group that users can select when creating API keys.": "Настройте группу, которую пользователи могут выбрать при создании ключей API.", @@ -1249,6 +1251,14 @@ "Embeddings": "Встраивания", "Empty value will be saved as {}.": "Пустое значение будет сохранено как {}.", "Enable": "Включить", + "Enable Langfuse Tracing": "启用 Langfuse 追踪", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", + "Langfuse Host": "Langfuse 地址", + "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", + "Langfuse Tracing": "Langfuse 追踪", + "Public Key": "公钥", + "Secret Key": "Секретный ключ", + "Trace Request/Response Content": "追踪请求/响应内容", "Enable 2FA": "Включить 2FA", "Enable All": "Включить все", "Enable check-in feature": "Включить функцию прибытия", @@ -1864,6 +1874,7 @@ "Initialize": "Инициализировать", "Initialize system": "Инициализация системы", "Initializing…": "Инициализация…", + "Initial Subscription Plan": "Initial Subscription Plan", "Inpaint": "Инпейнтинг", "Input": "Ввод", "Input mode": "Режим ввода", @@ -1879,6 +1890,7 @@ "Interface Language": "Язык интерфейса", "Interface Style": "????? ??????????", "Interface style updated. Reloading...": "????? ?????????? ????????. ????????????...", + "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", "Internal Notes": "Внутренние заметки", "Internal notes (not shown to users)": "Внутренние заметки (не показываются пользователям)", "Internal Server Error!": "Внутренняя ошибка сервера!", @@ -3158,7 +3170,6 @@ "seconds": "секунды", "Secret env (JSON object)": "Секретные переменные окружения (объект JSON)", "Secret environment variables (JSON)": "Секретные переменные окружения (JSON)", - "Secret Key": "Секретный ключ", "Secure & Reliable": "Безопасно и надежно", "Security": "Безопасность", "Security Check": "Проверка безопасности", @@ -3231,6 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Выбранные конфликты успешно перезаписаны.", "Self-Use Mode": "Режим самоиспользования", "Send": "Отправить", + "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", + "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", "Send code": "Отправить код", "Send email alerts when a user falls below this quota": "Отправлять оповещения по электронной почте, когда пользователь опускается ниже этой квоты", "Sending...": "Отправка...", @@ -3355,6 +3368,7 @@ "Statistical quota": "Статистическая квота", "Statistical tokens": "Статистические токены", "Statistics reset": "Статистика сброшена", + "Statistics": "统计", "Status": "Статус", "Status & Sync": "Статус и синхронизация", "Status Code": "Код статуса", @@ -3553,6 +3567,7 @@ "This project must be used in compliance with the": "Этот проект должен использоваться в соответствии с", "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Запись создана экземпляром до обновления и не содержит сведений аудита. Обновите экземпляр, чтобы фиксировать IP сервера, IP callback, способ оплаты и версию ОС.", "This site currently has {{count}} models enabled": "На этом сайте сейчас включено моделей: {{count}}", + "This site uses Langfuse to record user prompts and other data. Please be aware of security risks.": "Этот сайт использует Langfuse для записи пользовательских подсказок и других данных. Обратите внимание на безопасность.", "this token group": "эта группа токенов", "this user group": "эта группа пользователей", "This user has no bindings": "У этого пользователя нет привязок", diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json index c2b8096c46b..8ee486007d7 100644 --- a/web/default/src/i18n/locales/vi.json +++ b/web/default/src/i18n/locales/vi.json @@ -400,6 +400,7 @@ "Auto-discovers endpoints from the provider": "Tự động khám phá các điểm cuối từ nhà cung cấp", "Auto-fill when one field exists and another is missing": "Tự động điền khi một trường có giá trị và trường khác thiếu", "Auto-retry status codes": "Mã trạng thái tự thử lại", + "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Automatically disable channel on repeated failures": "Tự động vô hiệu hóa kênh khi xảy ra lỗi lặp lại", "Automatically disable channels exceeding this response time": "Tự động vô hiệu hóa các kênh vượt quá thời gian phản hồi này", "Automatically disable channels when tests fail": "Tự động vô hiệu hóa các kênh khi kiểm thử thất bại", @@ -740,6 +741,7 @@ "Configuration for Stripe payment integration": "Cấu hình cho tích hợp thanh toán Stripe", "Configuration required": "Cần cấu hình", "Configure": "Cấu hình", + "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", "Configure a Creem product for user recharge options.": "Cấu hình một sản phẩm Creem cho các tùy chọn nạp tiền người dùng.", "Configure a custom ratio for when users use a specific token group.": "Cấu hình tỷ lệ tùy chỉnh khi người dùng sử dụng nhóm token cụ thể.", "Configure a group that users can select when creating API keys.": "Cấu hình một nhóm mà người dùng có thể chọn khi tạo khóa API.", @@ -1249,6 +1251,14 @@ "Embeddings": "Embeddings", "Empty value will be saved as {}.": "Giá trị trống sẽ được lưu thành {}.", "Enable": "Bật", + "Enable Langfuse Tracing": "启用 Langfuse 追踪", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", + "Langfuse Host": "Langfuse 地址", + "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", + "Langfuse Tracing": "Langfuse 追踪", + "Public Key": "公钥", + "Secret Key": "Khóa bí mật", + "Trace Request/Response Content": "追踪请求/响应内容", "Enable 2FA": "Bật 2FA", "Enable All": "Bật tất cả", "Enable check-in feature": "Bật tính năng điểm danh", @@ -1864,6 +1874,7 @@ "Initialize": "Khởi tạo", "Initialize system": "Khởi tạo hệ thống", "Initializing…": "Đang khởi tạo…", + "Initial Subscription Plan": "Initial Subscription Plan", "Inpaint": "Inpaint", "Input": "Đầu vào", "Input mode": "Chế độ nhập", @@ -1879,6 +1890,7 @@ "Interface Language": "Ngôn ngữ giao diện", "Interface Style": "Ki?u giao di?n", "Interface style updated. Reloading...": "?? c?p nh?t ki?u giao di?n. ?ang t?i l?i...", + "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", "Internal Notes": "Ghi chú nội bộ", "Internal notes (not shown to users)": "Ghi chú nội bộ (không hiển thị cho người dùng)", "Internal Server Error!": "Lỗi máy chủ nội bộ!", @@ -3158,7 +3170,6 @@ "seconds": "giây", "Secret env (JSON object)": "Biến môi trường bí mật (đối tượng JSON)", "Secret environment variables (JSON)": "Biến môi trường bí mật (JSON)", - "Secret Key": "Khóa bí mật", "Secure & Reliable": "An toàn & Đáng tin cậy", "Security": "Bảo mật", "Security Check": "Kiểm tra bảo mật", @@ -3231,6 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Các xung đột được chọn đã được ghi đè thành công.", "Self-Use Mode": "Chế độ tự sử dụng", "Send": "Gửi", + "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", + "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", "Send code": "Gửi mã", "Send email alerts when a user falls below this quota": "Gửi cảnh báo email khi người dùng xuống dưới hạn mức này", "Sending...": "Đang gửi...", @@ -3355,6 +3368,7 @@ "Statistical quota": "Chỉ tiêu thống kê", "Statistical tokens": "Mã thông báo thống kê", "Statistics reset": "Đã đặt lại thống kê", + "Statistics": "统计", "Status": "Trạng thái", "Status & Sync": "Trạng thái & Đồng bộ", "Status Code": "Mã trạng thái", @@ -3553,6 +3567,7 @@ "This project must be used in compliance with the": "Dự án này phải được sử dụng tuân thủ theo", "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Bản ghi này do bản cũ tạo và thiếu thông tin audit. Nâng cấp bản cài để lưu IP máy chủ, IP callback, hình thức thanh toán và phiên bản hệ thống.", "This site currently has {{count}} models enabled": "Trang này hiện đã bật {{count}} mô hình", + "This site uses Langfuse to record user prompts and other data. Please be aware of security risks.": "Trang web này sử dụng Langfuse để ghi lại các lời nhắc của người dùng và dữ liệu khác. Vui lòng lưu ý về bảo mật.", "this token group": "nhóm token này", "this user group": "nhóm người dùng này", "This user has no bindings": "Người dùng này không có liên kết nào", diff --git a/web/default/src/i18n/locales/zh.json b/web/default/src/i18n/locales/zh.json index 192c9141995..456ed4d5955 100644 --- a/web/default/src/i18n/locales/zh.json +++ b/web/default/src/i18n/locales/zh.json @@ -3170,7 +3170,6 @@ "seconds": "秒", "Secret env (JSON object)": "密钥环境 (JSON 对象)", "Secret environment variables (JSON)": "密钥环境变量 (JSON)", - "Secret Key": "密钥", "Secure & Reliable": "安全可靠", "Security": "安全", "Security Check": "安全验证", @@ -3568,6 +3567,7 @@ "This project must be used in compliance with the": "此项目的使用必须遵守", "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "该记录由旧版本实例写入,缺少审计信息,建议将实例升级至最新版本以便记录服务器 IP、回调 IP、支付方式与系统版本等审计字段。", "This site currently has {{count}} models enabled": "本站当前已启用模型,总计 {{count}} 个", + "This site uses Langfuse to record user prompts and other data. Please be aware of security risks.": "本站使用Langfuse记录用户提示词等数据,请注意安全。", "this token group": "此令牌分组", "this user group": "此用户分组", "This user has no bindings": "该用户无任何绑定", From 2fb64e8ec08e9875b3d0515167c2f742ad001cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E9=9B=A8=E6=99=A8?= Date: Sat, 23 May 2026 01:26:43 +0800 Subject: [PATCH 2/5] fix: resolve theme redirect loop and use theme-aware paths - Expand MapFrontendPath with complete classic<->default path mappings - Move path mapping logic from router to common/frontend_theme.go - Fix infinite redirect loop in classic theme UserProvider - Fix default theme auth redirect to only redirect for classic - Fix _authenticated route to redirect to /console for classic theme - Replace hardcoded /console/xxx paths with GetThemeAwarePath() in: - epay/stripe/waffo payment controllers - telegram bind controller - topup controllers - Fix hardcoded /console/xxx links in default frontend components: - channels-columns.tsx, channel-test-dialog.tsx, message-error.tsx - Remove duplicate setFrontendTheme from classic PageLayout --- common/frontend_theme.go | 122 ++++++++++++++++++ controller/subscription_payment_epay.go | 14 +- controller/subscription_payment_stripe.go | 8 +- controller/telegram.go | 2 +- controller/topup.go | 2 +- controller/topup_stripe.go | 8 +- controller/topup_waffo.go | 2 +- controller/topup_waffo_pancake.go | 6 +- router/web-router.go | 38 +----- .../src/components/layout/PageLayout.jsx | 13 -- web/classic/src/context/User/index.jsx | 9 +- .../features/auth/hooks/use-auth-redirect.ts | 5 +- .../channels/components/channels-columns.tsx | 2 +- .../dialogs/channel-test-dialog.tsx | 2 +- .../playground/components/message-error.tsx | 2 +- .../src/routes/_authenticated/route.tsx | 7 +- 16 files changed, 163 insertions(+), 79 deletions(-) diff --git a/common/frontend_theme.go b/common/frontend_theme.go index af1220b8007..eabfea58536 100644 --- a/common/frontend_theme.go +++ b/common/frontend_theme.go @@ -1,6 +1,7 @@ package common import ( + "net/url" "strings" "github.com/gin-gonic/gin" @@ -25,3 +26,124 @@ func SetFrontendThemeCookie(c *gin.Context, theme string) { } c.SetCookie(FrontendThemeCookieName, theme, FrontendThemeCookieMaxAge, "/", "", false, false) } + +var classicToDefaultMap = map[string]string{ + "/console": "/dashboard", + "/console/personal": "/profile", + "/console/channel": "/channels", + "/console/token": "/keys", + "/console/log": "/usage-logs", + "/console/setting": "/system-settings", + "/console/topup": "/wallet", + "/console/redemption": "/redemption-codes", + "/console/user": "/users", + "/console/midjourney": "/usage-logs", + "/console/task": "/usage-logs", + "/console/models": "/models", + "/console/deployment": "/models", + "/console/subscription": "/subscriptions", + "/console/playground": "/playground", + "/console/chat": "/playground", +} + +var defaultToClassicMap = map[string]string{ + "/dashboard": "/console", + "/profile": "/console/personal", + "/channels": "/console/channel", + "/keys": "/console/token", + "/models": "/console/models", + "/usage-logs": "/console/log", + "/system-settings": "/console/setting", + "/playground": "/console/playground", + "/wallet": "/console/topup", + "/subscriptions": "/console/subscription", + "/redemption-codes": "/console/redemption", + "/users": "/console/user", + "/availability": "/console", +} + +var classicToDefaultPrefixes = []struct{ prefix, replacement string }{ + {"/console/chat/", "/playground"}, + {"/console/setting/", "/system-settings/"}, + {"/console/log/", "/usage-logs/"}, + {"/console/channel/", "/channels/"}, + {"/console/token/", "/keys/"}, + {"/console/models/", "/models/"}, + {"/console/topup/", "/wallet/"}, +} + +var defaultToClassicPrefixes = []struct{ prefix, replacement string }{ + {"/dashboard/", "/console"}, + {"/system-settings/", "/console/setting/"}, + {"/usage-logs/", "/console/log/"}, + {"/channels/", "/console/channel/"}, + {"/keys/", "/console/token/"}, + {"/models/", "/console/models/"}, +} + +func normalizeMapPath(path string) string { + if path == "" { + return "/" + } + unescapedPath, err := url.PathUnescape(path) + if err == nil && unescapedPath != "" { + path = unescapedPath + } + path = strings.TrimSuffix(path, "/") + if path == "" { + path = "/" + } + return path +} + +func MapFrontendPath(theme string, path string) string { + normalizedPath := normalizeMapPath(path) + + if theme == "classic" { + if mapped, ok := defaultToClassicMap[normalizedPath]; ok { + return mapped + } + for _, p := range defaultToClassicPrefixes { + if strings.HasPrefix(normalizedPath, p.prefix) { + return p.replacement + normalizedPath[len(p.prefix):] + } + } + } + + if theme == "default" { + if mapped, ok := classicToDefaultMap[normalizedPath]; ok { + return mapped + } + for _, p := range classicToDefaultPrefixes { + if strings.HasPrefix(normalizedPath, p.prefix) { + return p.replacement + normalizedPath[len(p.prefix):] + } + } + } + + return "" +} + +func GetThemeAwarePath(c *gin.Context, classicPath string) string { + theme := GetTheme() + themeCookie, err := c.Cookie(FrontendThemeCookieName) + if err == nil { + normalized := NormalizeFrontendTheme(themeCookie) + if normalized != "" { + theme = normalized + } + } + if theme == "default" { + path := classicPath + query := "" + if idx := strings.Index(classicPath, "?"); idx != -1 { + path = classicPath[:idx] + query = classicPath[idx:] + } + mapped := MapFrontendPath("default", path) + if mapped != "" { + return mapped + query + } + } + return classicPath +} diff --git a/controller/subscription_payment_epay.go b/controller/subscription_payment_epay.go index 2567654ff47..2154070ef9e 100644 --- a/controller/subscription_payment_epay.go +++ b/controller/subscription_payment_epay.go @@ -173,7 +173,7 @@ func SubscriptionEpayReturn(c *gin.Context) { if c.Request.Method == "POST" { // POST 请求:从 POST body 解析参数 if err := c.Request.ParseForm(); err != nil { - c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail") + c.Redirect(http.StatusFound, system_setting.ServerAddress+common.GetThemeAwarePath(c, "/console/topup?pay=fail")) return } params = lo.Reduce(lo.Keys(c.Request.PostForm), func(r map[string]string, t string, i int) map[string]string { @@ -189,29 +189,29 @@ func SubscriptionEpayReturn(c *gin.Context) { } if len(params) == 0 { - c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail") + c.Redirect(http.StatusFound, system_setting.ServerAddress+common.GetThemeAwarePath(c, "/console/topup?pay=fail")) return } client := GetEpayClient() if client == nil { - c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail") + c.Redirect(http.StatusFound, system_setting.ServerAddress+common.GetThemeAwarePath(c, "/console/topup?pay=fail")) return } verifyInfo, err := client.Verify(params) if err != nil || !verifyInfo.VerifyStatus { - c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail") + c.Redirect(http.StatusFound, system_setting.ServerAddress+common.GetThemeAwarePath(c, "/console/topup?pay=fail")) return } if verifyInfo.TradeStatus == epay.StatusTradeSuccess { LockOrder(verifyInfo.ServiceTradeNo) defer UnlockOrder(verifyInfo.ServiceTradeNo) if err := model.CompleteSubscriptionOrder(verifyInfo.ServiceTradeNo, common.GetJsonString(verifyInfo), model.PaymentProviderEpay, verifyInfo.Type); err != nil { - c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=fail") + c.Redirect(http.StatusFound, system_setting.ServerAddress+common.GetThemeAwarePath(c, "/console/topup?pay=fail")) return } - c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=success") + c.Redirect(http.StatusFound, system_setting.ServerAddress+common.GetThemeAwarePath(c, "/console/topup?pay=success")) return } - c.Redirect(http.StatusFound, system_setting.ServerAddress+"/console/topup?pay=pending") + c.Redirect(http.StatusFound, system_setting.ServerAddress+common.GetThemeAwarePath(c, "/console/topup?pay=pending")) } diff --git a/controller/subscription_payment_stripe.go b/controller/subscription_payment_stripe.go index a5ce4685b4c..02fa417f620 100644 --- a/controller/subscription_payment_stripe.go +++ b/controller/subscription_payment_stripe.go @@ -76,7 +76,7 @@ func SubscriptionRequestStripePay(c *gin.Context) { reference := fmt.Sprintf("sub-stripe-ref-%d-%d-%s", user.Id, time.Now().UnixMilli(), randstr.String(4)) referenceId := "sub_ref_" + common.Sha1([]byte(reference)) - payLink, err := genStripeSubscriptionLink(referenceId, user.StripeCustomer, user.Email, plan.StripePriceId) + payLink, err := genStripeSubscriptionLink(c, referenceId, user.StripeCustomer, user.Email, plan.StripePriceId) if err != nil { logger.LogError(c.Request.Context(), fmt.Sprintf("Stripe 订阅支付链接创建失败 trade_no=%s plan_id=%d error=%q", referenceId, plan.Id, err.Error())) c.JSON(http.StatusOK, gin.H{"message": "error", "data": "拉起支付失败"}) @@ -106,13 +106,13 @@ func SubscriptionRequestStripePay(c *gin.Context) { }) } -func genStripeSubscriptionLink(referenceId string, customerId string, email string, priceId string) (string, error) { +func genStripeSubscriptionLink(c *gin.Context, referenceId string, customerId string, email string, priceId string) (string, error) { stripe.Key = setting.StripeApiSecret params := &stripe.CheckoutSessionParams{ ClientReferenceID: stripe.String(referenceId), - SuccessURL: stripe.String(system_setting.ServerAddress + "/console/topup"), - CancelURL: stripe.String(system_setting.ServerAddress + "/console/topup"), + SuccessURL: stripe.String(system_setting.ServerAddress + common.GetThemeAwarePath(c, "/console/topup")), + CancelURL: stripe.String(system_setting.ServerAddress + common.GetThemeAwarePath(c, "/console/topup")), LineItems: []*stripe.CheckoutSessionLineItemParams{ { Price: stripe.String(priceId), diff --git a/controller/telegram.go b/controller/telegram.go index f16cdd66c54..19d1860cd44 100644 --- a/controller/telegram.go +++ b/controller/telegram.go @@ -66,7 +66,7 @@ func TelegramBind(c *gin.Context) { return } - c.Redirect(302, "/console/personal") + c.Redirect(302, common.GetThemeAwarePath(c, "/console/personal")) } func TelegramLogin(c *gin.Context) { diff --git a/controller/topup.go b/controller/topup.go index a6445b40d68..592fedcf333 100644 --- a/controller/topup.go +++ b/controller/topup.go @@ -207,7 +207,7 @@ func RequestEpay(c *gin.Context) { } callBackAddress := service.GetCallbackAddress() - returnUrl, _ := url.Parse(system_setting.ServerAddress + "/console/log") + returnUrl, _ := url.Parse(system_setting.ServerAddress + common.GetThemeAwarePath(c, "/console/log")) notifyUrl, _ := url.Parse(callBackAddress + "/api/user/epay/notify") tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix()) tradeNo = fmt.Sprintf("USR%dNO%s", id, tradeNo) diff --git a/controller/topup_stripe.go b/controller/topup_stripe.go index ceee8ecdd66..99e514322eb 100644 --- a/controller/topup_stripe.go +++ b/controller/topup_stripe.go @@ -93,7 +93,7 @@ func (*StripeAdaptor) RequestPay(c *gin.Context, req *StripePayRequest) { reference := fmt.Sprintf("new-api-ref-%d-%d-%s", user.Id, time.Now().UnixMilli(), randstr.String(4)) referenceId := "ref_" + common.Sha1([]byte(reference)) - payLink, err := genStripeLink(referenceId, user.StripeCustomer, user.Email, req.Amount, req.SuccessURL, req.CancelURL) + payLink, err := genStripeLink(c, referenceId, user.StripeCustomer, user.Email, req.Amount, req.SuccessURL, req.CancelURL) if err != nil { logger.LogError(c.Request.Context(), fmt.Sprintf("Stripe 创建 Checkout Session 失败 user_id=%d trade_no=%s amount=%d error=%q", id, referenceId, req.Amount, err.Error())) c.JSON(http.StatusOK, gin.H{"message": "error", "data": "拉起支付失败"}) @@ -339,7 +339,7 @@ func sessionExpired(ctx context.Context, event stripe.Event) { // - cancelURL: custom URL to redirect when payment is canceled (empty for default) // // Returns the checkout session URL or an error if the session creation fails. -func genStripeLink(referenceId string, customerId string, email string, amount int64, successURL string, cancelURL string) (string, error) { +func genStripeLink(c *gin.Context, referenceId string, customerId string, email string, amount int64, successURL string, cancelURL string) (string, error) { if !strings.HasPrefix(setting.StripeApiSecret, "sk_") && !strings.HasPrefix(setting.StripeApiSecret, "rk_") { return "", fmt.Errorf("无效的Stripe API密钥") } @@ -348,10 +348,10 @@ func genStripeLink(referenceId string, customerId string, email string, amount i // Use custom URLs if provided, otherwise use defaults if successURL == "" { - successURL = system_setting.ServerAddress + "/console/log" + successURL = system_setting.ServerAddress + common.GetThemeAwarePath(c, "/console/log") } if cancelURL == "" { - cancelURL = system_setting.ServerAddress + "/console/topup" + cancelURL = system_setting.ServerAddress + common.GetThemeAwarePath(c, "/console/topup") } params := &stripe.CheckoutSessionParams{ diff --git a/controller/topup_waffo.go b/controller/topup_waffo.go index 1885c1ded9e..4cf4aaef09e 100644 --- a/controller/topup_waffo.go +++ b/controller/topup_waffo.go @@ -237,7 +237,7 @@ func RequestWaffoPay(c *gin.Context) { if setting.WaffoNotifyUrl != "" { notifyUrl = setting.WaffoNotifyUrl } - returnUrl := system_setting.ServerAddress + "/console/topup?show_history=true" + returnUrl := system_setting.ServerAddress + common.GetThemeAwarePath(c, "/console/topup?show_history=true") if setting.WaffoReturnUrl != "" { returnUrl = setting.WaffoReturnUrl } diff --git a/controller/topup_waffo_pancake.go b/controller/topup_waffo_pancake.go index 09f1516304b..1837071c243 100644 --- a/controller/topup_waffo_pancake.go +++ b/controller/topup_waffo_pancake.go @@ -103,11 +103,11 @@ func getWaffoPancakeBuyerEmail(user *model.User) string { return "" } -func getWaffoPancakeReturnURL() string { +func getWaffoPancakeReturnURL(c *gin.Context) string { if strings.TrimSpace(setting.WaffoPancakeReturnURL) != "" { return setting.WaffoPancakeReturnURL } - return strings.TrimRight(system_setting.ServerAddress, "/") + "/console/topup?show_history=true" + return strings.TrimRight(system_setting.ServerAddress, "/") + common.GetThemeAwarePath(c, "/console/topup?show_history=true") } func RequestWaffoPancakePay(c *gin.Context) { @@ -186,7 +186,7 @@ func RequestWaffoPancakePay(c *gin.Context) { TaxCategory: "saas", }, BuyerEmail: getWaffoPancakeBuyerEmail(user), - SuccessURL: getWaffoPancakeReturnURL(), + SuccessURL: getWaffoPancakeReturnURL(c), ExpiresInSeconds: &expiresInSeconds, }) if err != nil { diff --git a/router/web-router.go b/router/web-router.go index 03f3f2e3699..17027316f77 100644 --- a/router/web-router.go +++ b/router/web-router.go @@ -3,7 +3,6 @@ package router import ( "embed" "net/http" - "net/url" "strings" "github.com/QuantumNous/new-api/common" @@ -39,7 +38,7 @@ func SetWebRouter(router *gin.Engine, assets ThemeAssets) { } theme := resolveFrontendTheme(c) - if redirectPath := mapFrontendPath(theme, c.Request.URL.Path); redirectPath != "" && redirectPath != c.Request.URL.Path { + if redirectPath := common.MapFrontendPath(theme, c.Request.URL.Path); redirectPath != "" && redirectPath != c.Request.URL.Path { if c.Request.URL.RawQuery != "" { redirectPath = redirectPath + "?" + c.Request.URL.RawQuery } @@ -102,41 +101,6 @@ func resolveFrontendTheme(c *gin.Context) string { return common.GetTheme() } -func mapFrontendPath(theme string, path string) string { - normalizedPath := path - if normalizedPath == "" { - normalizedPath = "/" - } - unescapedPath, err := url.PathUnescape(normalizedPath) - if err == nil && unescapedPath != "" { - normalizedPath = unescapedPath - } - normalizedPath = strings.TrimSuffix(normalizedPath, "/") - if normalizedPath == "" { - normalizedPath = "/" - } - - if theme == "classic" { - switch normalizedPath { - case "/dashboard": - return "/console" - case "/profile": - return "/console/personal" - } - } - - if theme == "default" { - switch normalizedPath { - case "/console": - return "/dashboard" - case "/console/personal": - return "/profile" - } - } - - return "" -} - func selectFrontendFS(theme string, defaultFS, classicFS static.ServeFileSystem) static.ServeFileSystem { if theme == "classic" { return classicFS diff --git a/web/classic/src/components/layout/PageLayout.jsx b/web/classic/src/components/layout/PageLayout.jsx index 971497503c9..b50ff961583 100644 --- a/web/classic/src/components/layout/PageLayout.jsx +++ b/web/classic/src/components/layout/PageLayout.jsx @@ -39,17 +39,7 @@ import { UserContext } from '../../context/User'; import { StatusContext } from '../../context/Status'; import { useLocation } from 'react-router-dom'; import { normalizeLanguage } from '../../i18n/language'; -const FRONTEND_THEME_COOKIE_NAME = 'frontend_theme'; -const FRONTEND_THEME_COOKIE_MAX_AGE = 60 * 60 * 24 * 365; -const normalizeFrontendTheme = (value) => { - return value === 'classic' ? 'classic' : 'default'; -}; - -const setFrontendTheme = (theme) => { - if (typeof document === 'undefined') return; - document.cookie = `${FRONTEND_THEME_COOKIE_NAME}=${theme}; path=/; max-age=${FRONTEND_THEME_COOKIE_MAX_AGE}`; -}; const { Sider, Content, Header } = Layout; const PageLayout = () => { @@ -135,9 +125,6 @@ const PageLayout = () => { try { const settings = JSON.parse(userState.user.setting); preferredLang = normalizeLanguage(settings.language); - if (settings.frontend_theme) { - setFrontendTheme(normalizeFrontendTheme(settings.frontend_theme)); - } } catch (e) { // Ignore parse errors } diff --git a/web/classic/src/context/User/index.jsx b/web/classic/src/context/User/index.jsx index 04511648e9c..1dc5d2f49e8 100644 --- a/web/classic/src/context/User/index.jsx +++ b/web/classic/src/context/User/index.jsx @@ -29,6 +29,8 @@ const normalizeFrontendTheme = (value) => { return value === 'classic' ? 'classic' : 'default'; }; +const themeRedirectAttempted = { current: false }; + const setFrontendTheme = (theme) => { if (typeof document === 'undefined') return; document.cookie = `${FRONTEND_THEME_COOKIE_NAME}=${theme}; path=/; max-age=${FRONTEND_THEME_COOKIE_MAX_AGE}`; @@ -56,7 +58,12 @@ export const UserProvider = ({ children }) => { localStorage.setItem('i18nextLng', normalizedLanguage); } if (settings.frontend_theme) { - setFrontendTheme(normalizeFrontendTheme(settings.frontend_theme)); + const normalizedTheme = normalizeFrontendTheme(settings.frontend_theme); + setFrontendTheme(normalizedTheme); + if (normalizedTheme === 'default' && !themeRedirectAttempted.current) { + themeRedirectAttempted.current = true; + window.location.replace('/dashboard'); + } } } catch (e) { // Ignore parse errors diff --git a/web/default/src/features/auth/hooks/use-auth-redirect.ts b/web/default/src/features/auth/hooks/use-auth-redirect.ts index 49fc8f69ebf..8a2f546f046 100644 --- a/web/default/src/features/auth/hooks/use-auth-redirect.ts +++ b/web/default/src/features/auth/hooks/use-auth-redirect.ts @@ -3,7 +3,6 @@ import i18n from 'i18next' import { useAuthStore } from '@/stores/auth-store' import { getSelf } from '@/lib/api' import { - getFrontendThemeSettingsPath, normalizeFrontendTheme, setFrontendTheme, } from '@/lib/frontend-theme' @@ -90,8 +89,8 @@ export function useAuthRedirect() { const savedTheme = getSavedFrontendTheme(user) if (savedTheme) { setFrontendTheme(savedTheme) - if (!redirectTo) { - window.location.assign(getFrontendThemeSettingsPath(savedTheme)) + if (savedTheme === 'classic') { + window.location.assign('/console') return } } diff --git a/web/default/src/features/channels/components/channels-columns.tsx b/web/default/src/features/channels/components/channels-columns.tsx index 047ecf63c65..6a0444c04ef 100644 --- a/web/default/src/features/channels/components/channels-columns.tsx +++ b/web/default/src/features/channels/components/channels-columns.tsx @@ -661,7 +661,7 @@ export function useChannelsColumns(): ColumnDef[] { onClick={(e) => { e.stopPropagation() if (!deploymentId) return - const targetUrl = `/console/deployment?deployment_id=${deploymentId}` + const targetUrl = `/models/deployments?deployment_id=${deploymentId}` window.open(targetUrl, '_blank', 'noopener') }} > diff --git a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx index 9d2990f22de..0166182fb9d 100644 --- a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx @@ -352,7 +352,7 @@ export function ChannelTestDialog({ size='sm' className='w-fit' onClick={() => - window.open('/console/setting?tab=ratio', '_blank') + window.open('/system-settings?tab=ratio', '_blank') } > diff --git a/web/default/src/features/playground/components/message-error.tsx b/web/default/src/features/playground/components/message-error.tsx index 51804607ba7..173e0ccf8ac 100644 --- a/web/default/src/features/playground/components/message-error.tsx +++ b/web/default/src/features/playground/components/message-error.tsx @@ -39,7 +39,7 @@ export function MessageError({ message, className = '' }: MessageErrorProps) { variant='outline' size='sm' onClick={() => - window.open('/console/setting?tab=ratio', '_blank') + window.open('/system-settings?tab=ratio', '_blank') } > diff --git a/web/default/src/routes/_authenticated/route.tsx b/web/default/src/routes/_authenticated/route.tsx index fe95ab8c7d9..12dfe6d44a4 100644 --- a/web/default/src/routes/_authenticated/route.tsx +++ b/web/default/src/routes/_authenticated/route.tsx @@ -30,7 +30,12 @@ export const Route = createFileRoute('/_authenticated')({ try { const parsed = JSON.parse(setting) as { frontend_theme?: string } if (parsed.frontend_theme) { - setFrontendTheme(normalizeFrontendTheme(parsed.frontend_theme)) + const normalizedTheme = normalizeFrontendTheme(parsed.frontend_theme) + setFrontendTheme(normalizedTheme) + if (normalizedTheme === 'classic') { + window.location.replace('/console') + return + } } } catch { /* empty */ From 1952ed0f34739bf3bbd4155650f52e39e0717583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E9=9B=A8=E6=99=A8?= Date: Sat, 23 May 2026 01:44:04 +0800 Subject: [PATCH 3/5] fix: address PR review issues - Fix /console/deployment mapping to /models/deployments (not /models) - Fix missing trailing slash in prefix replacements: - /dashboard/ -> /console/ (was /console, causing /consolesomething) - /console/chat/ -> /playground/ (was /playground, causing /playgroundxxx) - Fix en.json: replace Chinese translations with English - Fix fr/ja/ru/vi.json: replace Chinese translations with proper translations --- common/frontend_theme.go | 6 ++--- .../i18n/locales/_reports/_sync-report.json | 14 +++++------ web/default/src/i18n/locales/en.json | 24 +++++++++---------- web/default/src/i18n/locales/fr.json | 24 +++++++++---------- web/default/src/i18n/locales/ja.json | 24 +++++++++---------- web/default/src/i18n/locales/ru.json | 24 +++++++++---------- web/default/src/i18n/locales/vi.json | 24 +++++++++---------- 7 files changed, 70 insertions(+), 70 deletions(-) diff --git a/common/frontend_theme.go b/common/frontend_theme.go index eabfea58536..dcbf93114c3 100644 --- a/common/frontend_theme.go +++ b/common/frontend_theme.go @@ -40,7 +40,7 @@ var classicToDefaultMap = map[string]string{ "/console/midjourney": "/usage-logs", "/console/task": "/usage-logs", "/console/models": "/models", - "/console/deployment": "/models", + "/console/deployment": "/models/deployments", "/console/subscription": "/subscriptions", "/console/playground": "/playground", "/console/chat": "/playground", @@ -63,7 +63,7 @@ var defaultToClassicMap = map[string]string{ } var classicToDefaultPrefixes = []struct{ prefix, replacement string }{ - {"/console/chat/", "/playground"}, + {"/console/chat/", "/playground/"}, {"/console/setting/", "/system-settings/"}, {"/console/log/", "/usage-logs/"}, {"/console/channel/", "/channels/"}, @@ -73,7 +73,7 @@ var classicToDefaultPrefixes = []struct{ prefix, replacement string }{ } var defaultToClassicPrefixes = []struct{ prefix, replacement string }{ - {"/dashboard/", "/console"}, + {"/dashboard/", "/console/"}, {"/system-settings/", "/console/setting/"}, {"/usage-logs/", "/console/log/"}, {"/channels/", "/console/channel/"}, diff --git a/web/default/src/i18n/locales/_reports/_sync-report.json b/web/default/src/i18n/locales/_reports/_sync-report.json index a9482bb317b..c766dc48d8d 100644 --- a/web/default/src/i18n/locales/_reports/_sync-report.json +++ b/web/default/src/i18n/locales/_reports/_sync-report.json @@ -1,33 +1,33 @@ { - "base": "zh.json", + "base": "en.json", "locales": { "en": { "file": "en.json", - "missingCount": 12, + "missingCount": 0, "extrasCount": 0, "untranslatedCount": 0 }, "fr": { "file": "fr.json", - "missingCount": 14, + "missingCount": 0, "extrasCount": 0, "untranslatedCount": 10 }, "ja": { "file": "ja.json", - "missingCount": 14, + "missingCount": 0, "extrasCount": 0, "untranslatedCount": 125 }, "ru": { "file": "ru.json", - "missingCount": 14, + "missingCount": 0, "extrasCount": 0, "untranslatedCount": 129 }, "vi": { "file": "vi.json", - "missingCount": 14, + "missingCount": 0, "extrasCount": 0, "untranslatedCount": 10 }, @@ -35,7 +35,7 @@ "file": "zh.json", "missingCount": 0, "extrasCount": 0, - "untranslatedCount": 0 + "untranslatedCount": 97 } } } diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json index 45b8b19709c..db86fde5dab 100644 --- a/web/default/src/i18n/locales/en.json +++ b/web/default/src/i18n/locales/en.json @@ -741,7 +741,7 @@ "Configuration for Stripe payment integration": "Configuration for Stripe payment integration", "Configuration required": "Configuration required", "Configure": "Configure", - "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", + "Configure Langfuse LLM observability": "Configure Langfuse LLM observability", "Configure a Creem product for user recharge options.": "Configure a Creem product for user recharge options.", "Configure a custom ratio for when users use a specific token group.": "Configure a custom ratio for when users use a specific token group.", "Configure a group that users can select when creating API keys.": "Configure a group that users can select when creating API keys.", @@ -1251,14 +1251,14 @@ "Embeddings": "Embeddings", "Empty value will be saved as {}.": "Empty value will be saved as {}.", "Enable": "Enable", - "Enable Langfuse Tracing": "启用 Langfuse 追踪", - "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", - "Langfuse Host": "Langfuse 地址", - "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", - "Langfuse Tracing": "Langfuse 追踪", - "Public Key": "公钥", + "Enable Langfuse Tracing": "Enable Langfuse Tracing", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.", + "Langfuse Host": "Langfuse Host", + "Langfuse server URL (cloud or self-hosted)": "Langfuse server URL (cloud or self-hosted)", + "Langfuse Tracing": "Langfuse Tracing", + "Public Key": "Public Key", "Secret Key": "Secret Key", - "Trace Request/Response Content": "追踪请求/响应内容", + "Trace Request/Response Content": "Trace Request/Response Content", "Enable 2FA": "Enable 2FA", "Enable All": "Enable All", "Enable check-in feature": "Enable check-in feature", @@ -1890,7 +1890,7 @@ "Interface Language": "Interface Language", "Interface Style": "Interface Style", "Interface style updated. Reloading...": "Interface style updated. Reloading...", - "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", + "Interface style updated. Redirecting...": "Interface style updated. Redirecting...", "Internal Notes": "Internal Notes", "Internal notes (not shown to users)": "Internal notes (not shown to users)", "Internal Server Error!": "Internal Server Error!", @@ -3242,8 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Selected conflicts were overwritten successfully.", "Self-Use Mode": "Self-Use Mode", "Send": "Send", - "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", - "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", + "Send LLM usage traces to Langfuse for observability and analytics": "Send LLM usage traces to Langfuse for observability and analytics", + "Send request traces to Langfuse for LLM observability": "Send request traces to Langfuse for LLM observability", "Send code": "Send code", "Send email alerts when a user falls below this quota": "Send email alerts when a user falls below this quota", "Sending...": "Sending...", @@ -3368,7 +3368,7 @@ "Statistical quota": "Statistical quota", "Statistical tokens": "Statistical tokens", "Statistics reset": "Statistics reset", - "Statistics": "统计", + "Statistics": "Statistics", "Status": "Status", "Status & Sync": "Status & Sync", "Status Code": "Status Code", diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json index 5f159350338..e87b7e4ec09 100644 --- a/web/default/src/i18n/locales/fr.json +++ b/web/default/src/i18n/locales/fr.json @@ -741,7 +741,7 @@ "Configuration for Stripe payment integration": "Configuration pour l'intégration de paiement Stripe", "Configuration required": "Configuration requise", "Configure": "Configurer", - "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", + "Configure Langfuse LLM observability": "Configurer l'observabilité LLM avec Langfuse", "Configure a Creem product for user recharge options.": "Configurez un produit Creem pour les options de recharge utilisateur.", "Configure a custom ratio for when users use a specific token group.": "Configurer un ratio personnalisé lorsque les utilisateurs utilisent un groupe de jetons spécifique.", "Configure a group that users can select when creating API keys.": "Configurer un groupe que les utilisateurs peuvent sélectionner lors de la création de clés API.", @@ -1251,14 +1251,14 @@ "Embeddings": "Embeddings", "Empty value will be saved as {}.": "Une valeur vide sera enregistrée comme {}.", "Enable": "Activer", - "Enable Langfuse Tracing": "启用 Langfuse 追踪", - "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", - "Langfuse Host": "Langfuse 地址", - "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", - "Langfuse Tracing": "Langfuse 追踪", - "Public Key": "公钥", + "Enable Langfuse Tracing": "Activer le traçage Langfuse", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "Inclure le contenu complet des prompts et des complétions dans les traces. La désactivation envoie uniquement les comptes de tokens et les métadonnées.", + "Langfuse Host": "Hôte Langfuse", + "Langfuse server URL (cloud or self-hosted)": "URL du serveur Langfuse (cloud ou auto-hébergé)", + "Langfuse Tracing": "Traçage Langfuse", + "Public Key": "Clé publique", "Secret Key": "Clé secrète", - "Trace Request/Response Content": "追踪请求/响应内容", + "Trace Request/Response Content": "Tracer le contenu des requêtes/réponses", "Enable 2FA": "Activer 2FA", "Enable All": "Tout activer", "Enable check-in feature": "Activer la fonction de connexion", @@ -1890,7 +1890,7 @@ "Interface Language": "Langue de l'interface", "Interface Style": "Style de l'interface", "Interface style updated. Reloading...": "Style de l'interface mis ? jour. Rechargement...", - "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", + "Interface style updated. Redirecting...": "Style d'interface mis à jour. Redirection en cours...", "Internal Notes": "Notes internes", "Internal notes (not shown to users)": "Notes internes (non visibles par les utilisateurs)", "Internal Server Error!": "Erreur interne du serveur !", @@ -3242,8 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Les conflits sélectionnés ont été écrasés avec succès.", "Self-Use Mode": "Mode d'utilisation personnelle", "Send": "Envoyer", - "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", - "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", + "Send LLM usage traces to Langfuse for observability and analytics": "Envoyer les traces d'utilisation LLM à Langfuse pour l'observabilité et l'analyse", + "Send request traces to Langfuse for LLM observability": "Envoyer les traces de requêtes à Langfuse pour l'observabilité LLM", "Send code": "Envoyer le code", "Send email alerts when a user falls below this quota": "Envoyer des alertes par e-mail lorsqu'un utilisateur descend en dessous de ce quota", "Sending...": "Envoi en cours...", @@ -3368,7 +3368,7 @@ "Statistical quota": "Quota statistique", "Statistical tokens": "Jetons statistiques", "Statistics reset": "Statistiques réinitialisées", - "Statistics": "统计", + "Statistics": "Statistiques", "Status": "Statut", "Status & Sync": "Statut et synchronisation", "Status Code": "Code de statut", diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json index b1001683b20..5afaa6f04ac 100644 --- a/web/default/src/i18n/locales/ja.json +++ b/web/default/src/i18n/locales/ja.json @@ -741,7 +741,7 @@ "Configuration for Stripe payment integration": "Stripe決済連携のための設定", "Configuration required": "設定が必要です", "Configure": "設定", - "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", + "Configure Langfuse LLM observability": "Langfuse LLM オブザーバビリティを設定", "Configure a Creem product for user recharge options.": "ユーザー チャージオプション用の Creem 製品を設定。", "Configure a custom ratio for when users use a specific token group.": "ユーザーが特定のトークングループを使用する際のカスタム倍率を設定します。", "Configure a group that users can select when creating API keys.": "ユーザーがAPIキー作成時に選択できるグループを設定します。", @@ -1251,14 +1251,14 @@ "Embeddings": "埋め込み", "Empty value will be saved as {}.": "空の値は {} として保存されます。", "Enable": "有効にする", - "Enable Langfuse Tracing": "启用 Langfuse 追踪", - "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", - "Langfuse Host": "Langfuse 地址", - "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", - "Langfuse Tracing": "Langfuse 追踪", - "Public Key": "公钥", + "Enable Langfuse Tracing": "Langfuse トレースを有効にする", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "トレースにプロンプトと補完の完全な内容を含めます。無効にすると、トークン数とメタデータのみが送信されます。", + "Langfuse Host": "Langfuse ホスト", + "Langfuse server URL (cloud or self-hosted)": "Langfuse サーバー URL(クラウドまたはセルフホスト)", + "Langfuse Tracing": "Langfuse トレース", + "Public Key": "公開鍵", "Secret Key": "シークレットキー", - "Trace Request/Response Content": "追踪请求/响应内容", + "Trace Request/Response Content": "リクエスト/レスポンス内容をトレース", "Enable 2FA": "2FA を有効にする", "Enable All": "すべて有効にする", "Enable check-in feature": "チェックイン機能を有効にする", @@ -1890,7 +1890,7 @@ "Interface Language": "インターフェース言語", "Interface Style": "????????????", "Interface style updated. Reloading...": "??????????????????????????????...", - "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", + "Interface style updated. Redirecting...": "インターフェーススタイルが更新されました。リダイレクト中...", "Internal Notes": "内部メモ", "Internal notes (not shown to users)": ":内部メモ(ユーザーには表示されません)", "Internal Server Error!": "内部サーバーエラー!", @@ -3242,8 +3242,8 @@ "Selected conflicts were overwritten successfully.": "選択した競合が正常に上書きされました。", "Self-Use Mode": "セルフユースモード", "Send": "送信", - "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", - "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", + "Send LLM usage traces to Langfuse for observability and analytics": "LLM 使用トレースを Langfuse に送信してオブザーバビリティと分析を行う", + "Send request traces to Langfuse for LLM observability": "LLM オブザーバビリティのためにリクエストトレースを Langfuse に送信する", "Send code": "コードを送信", "Send email alerts when a user falls below this quota": "ユーザーがこのクォータを下回ったときにメールアラートを送信", "Sending...": "送信中...", @@ -3368,7 +3368,7 @@ "Statistical quota": "統計クォータ", "Statistical tokens": "統計トークン", "Statistics reset": "統計をリセットしました", - "Statistics": "统计", + "Statistics": "統計", "Status": "ステータス", "Status & Sync": "ステータスと同期", "Status Code": "ステータスコード", diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json index 94373770af9..d2dc06e6709 100644 --- a/web/default/src/i18n/locales/ru.json +++ b/web/default/src/i18n/locales/ru.json @@ -741,7 +741,7 @@ "Configuration for Stripe payment integration": "Конфигурация для интеграции платежей Stripe", "Configuration required": "Требуется настройка", "Configure": "Настройка", - "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", + "Configure Langfuse LLM observability": "Настроить наблюдаемость LLM с Langfuse", "Configure a Creem product for user recharge options.": "Настройте продукт Creem для опций пополнения пользователя.", "Configure a custom ratio for when users use a specific token group.": "Настроить пользовательский коэффициент при использовании определённой группы токенов.", "Configure a group that users can select when creating API keys.": "Настройте группу, которую пользователи могут выбрать при создании ключей API.", @@ -1251,14 +1251,14 @@ "Embeddings": "Встраивания", "Empty value will be saved as {}.": "Пустое значение будет сохранено как {}.", "Enable": "Включить", - "Enable Langfuse Tracing": "启用 Langfuse 追踪", - "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", - "Langfuse Host": "Langfuse 地址", - "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", - "Langfuse Tracing": "Langfuse 追踪", - "Public Key": "公钥", + "Enable Langfuse Tracing": "Включить трассировку Langfuse", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "Включать полное содержимое промптов и дополнений в трассировки. При отключении отправляются только количества токенов и метаданные.", + "Langfuse Host": "Хост Langfuse", + "Langfuse server URL (cloud or self-hosted)": "URL-адрес сервера Langfuse (облачный или собственный)", + "Langfuse Tracing": "Трассировка Langfuse", + "Public Key": "Открытый ключ", "Secret Key": "Секретный ключ", - "Trace Request/Response Content": "追踪请求/响应内容", + "Trace Request/Response Content": "Трассировать содержимое запросов/ответов", "Enable 2FA": "Включить 2FA", "Enable All": "Включить все", "Enable check-in feature": "Включить функцию прибытия", @@ -1890,7 +1890,7 @@ "Interface Language": "Язык интерфейса", "Interface Style": "????? ??????????", "Interface style updated. Reloading...": "????? ?????????? ????????. ????????????...", - "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", + "Interface style updated. Redirecting...": "Стиль интерфейса обновлён. Перенаправление...", "Internal Notes": "Внутренние заметки", "Internal notes (not shown to users)": "Внутренние заметки (не показываются пользователям)", "Internal Server Error!": "Внутренняя ошибка сервера!", @@ -3242,8 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Выбранные конфликты успешно перезаписаны.", "Self-Use Mode": "Режим самоиспользования", "Send": "Отправить", - "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", - "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", + "Send LLM usage traces to Langfuse for observability and analytics": "Отправлять трассировки использования LLM в Langfuse для наблюдаемости и аналитики", + "Send request traces to Langfuse for LLM observability": "Отправлять трассировки запросов в Langfuse для наблюдаемости LLM", "Send code": "Отправить код", "Send email alerts when a user falls below this quota": "Отправлять оповещения по электронной почте, когда пользователь опускается ниже этой квоты", "Sending...": "Отправка...", @@ -3368,7 +3368,7 @@ "Statistical quota": "Статистическая квота", "Statistical tokens": "Статистические токены", "Statistics reset": "Статистика сброшена", - "Statistics": "统计", + "Statistics": "Статистика", "Status": "Статус", "Status & Sync": "Статус и синхронизация", "Status Code": "Код статуса", diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json index 8ee486007d7..7c51c1bafe1 100644 --- a/web/default/src/i18n/locales/vi.json +++ b/web/default/src/i18n/locales/vi.json @@ -741,7 +741,7 @@ "Configuration for Stripe payment integration": "Cấu hình cho tích hợp thanh toán Stripe", "Configuration required": "Cần cấu hình", "Configure": "Cấu hình", - "Configure Langfuse LLM observability": "配置 Langfuse LLM 可观测性", + "Configure Langfuse LLM observability": "Cấu hình khả năng quan sát LLM bằng Langfuse", "Configure a Creem product for user recharge options.": "Cấu hình một sản phẩm Creem cho các tùy chọn nạp tiền người dùng.", "Configure a custom ratio for when users use a specific token group.": "Cấu hình tỷ lệ tùy chỉnh khi người dùng sử dụng nhóm token cụ thể.", "Configure a group that users can select when creating API keys.": "Cấu hình một nhóm mà người dùng có thể chọn khi tạo khóa API.", @@ -1251,14 +1251,14 @@ "Embeddings": "Embeddings", "Empty value will be saved as {}.": "Giá trị trống sẽ được lưu thành {}.", "Enable": "Bật", - "Enable Langfuse Tracing": "启用 Langfuse 追踪", - "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "在追踪中包含完整的提示词和补全内容。禁用时仅发送 Token 数量和元数据。", - "Langfuse Host": "Langfuse 地址", - "Langfuse server URL (cloud or self-hosted)": "Langfuse 服务器地址(云端或自托管)", - "Langfuse Tracing": "Langfuse 追踪", - "Public Key": "公钥", + "Enable Langfuse Tracing": "Bật theo dõi Langfuse", + "Include full prompt and completion content in traces. Disabling sends only token counts and metadata.": "Bao gồm nội dung đầy đủ của lời nhắc và phần bổ sung trong theo dõi. Tắt sẽ chỉ gửi số lượng token và siêu dữ liệu.", + "Langfuse Host": "Máy chủ Langfuse", + "Langfuse server URL (cloud or self-hosted)": "URL máy chủ Langfuse (đám mây hoặc tự lưu trữ)", + "Langfuse Tracing": "Theo dõi Langfuse", + "Public Key": "Khóa công khai", "Secret Key": "Khóa bí mật", - "Trace Request/Response Content": "追踪请求/响应内容", + "Trace Request/Response Content": "Theo dõi nội dung yêu cầu/phản hồi", "Enable 2FA": "Bật 2FA", "Enable All": "Bật tất cả", "Enable check-in feature": "Bật tính năng điểm danh", @@ -1890,7 +1890,7 @@ "Interface Language": "Ngôn ngữ giao diện", "Interface Style": "Ki?u giao di?n", "Interface style updated. Reloading...": "?? c?p nh?t ki?u giao di?n. ?ang t?i l?i...", - "Interface style updated. Redirecting...": "界面风格已更新,正在跳转...", + "Interface style updated. Redirecting...": "Kiểu giao diện đã được cập nhật. Đang chuyển hướng...", "Internal Notes": "Ghi chú nội bộ", "Internal notes (not shown to users)": "Ghi chú nội bộ (không hiển thị cho người dùng)", "Internal Server Error!": "Lỗi máy chủ nội bộ!", @@ -3242,8 +3242,8 @@ "Selected conflicts were overwritten successfully.": "Các xung đột được chọn đã được ghi đè thành công.", "Self-Use Mode": "Chế độ tự sử dụng", "Send": "Gửi", - "Send LLM usage traces to Langfuse for observability and analytics": "将 LLM 使用追踪发送到 Langfuse 进行可观测性分析", - "Send request traces to Langfuse for LLM observability": "将请求追踪发送到 Langfuse 进行 LLM 可观测性监控", + "Send LLM usage traces to Langfuse for observability and analytics": "Gửi theo dõi sử dụng LLM đến Langfuse để quan sát và phân tích", + "Send request traces to Langfuse for LLM observability": "Gửi theo dõi yêu cầu đến Langfuse để quan sát LLM", "Send code": "Gửi mã", "Send email alerts when a user falls below this quota": "Gửi cảnh báo email khi người dùng xuống dưới hạn mức này", "Sending...": "Đang gửi...", @@ -3368,7 +3368,7 @@ "Statistical quota": "Chỉ tiêu thống kê", "Statistical tokens": "Mã thông báo thống kê", "Statistics reset": "Đã đặt lại thống kê", - "Statistics": "统计", + "Statistics": "Thống kê", "Status": "Trạng thái", "Status & Sync": "Trạng thái & Đồng bộ", "Status Code": "Mã trạng thái", From d1203efa7f52f1bc9b6f20b3730de710a5cd86ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E9=9B=A8=E6=99=A8?= Date: Sat, 23 May 2026 01:53:10 +0800 Subject: [PATCH 4/5] fix: address remaining PR review issues - Fix classic UserProvider: use useRef instead of module-scoped mutable - Fix theme redirect conflict: add startThemeNavigation() API to UserContext so PreferencesSettings can signal pending navigation - Fix use-auth-redirect.ts: use replace() instead of assign() for classic theme redirect - Fix window.open security: add 'noopener' to channel-test-dialog and message-error - Fix controller/misc.go: use local variable for GetLangfuseSetting() - Fix fr/ja/ru/vi.json: translate 'Automatically bind this subscription plan' and 'Initial Subscription Plan' entries --- .gitignore | 5 ++++- controller/misc.go | 3 ++- .../personal/cards/PreferencesSettings.jsx | 3 ++- web/classic/src/context/User/index.jsx | 18 +++++++++++------- .../features/auth/hooks/use-auth-redirect.ts | 2 +- .../components/dialogs/channel-test-dialog.tsx | 2 +- .../playground/components/message-error.tsx | 2 +- .../i18n/locales/_reports/_sync-report.json | 8 ++++---- .../i18n/locales/_reports/fr.untranslated.json | 1 - .../i18n/locales/_reports/ja.untranslated.json | 2 -- .../i18n/locales/_reports/ru.untranslated.json | 2 -- .../i18n/locales/_reports/vi.untranslated.json | 1 - web/default/src/i18n/locales/fr.json | 4 ++-- web/default/src/i18n/locales/ja.json | 4 ++-- web/default/src/i18n/locales/ru.json | 4 ++-- web/default/src/i18n/locales/vi.json | 4 ++-- 16 files changed, 34 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 00ab1253369..0efb6886dff 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,7 @@ token_estimator_test.go skills-lock.json .trae/ -private/ \ No newline at end of file +private/ + +fixdoc.md +hero-rep.html \ No newline at end of file diff --git a/controller/misc.go b/controller/misc.go index d4ff81eb356..27743840ef7 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -120,8 +120,9 @@ func GetStatus(c *gin.Context) { "user_agreement_enabled": legalSetting.UserAgreement != "", "privacy_policy_enabled": legalSetting.PrivacyPolicy != "", "checkin_enabled": operation_setting.GetCheckinSetting().Enabled, - "langfuse_trace_content": langfuse_setting.GetLangfuseSetting().Enabled && langfuse_setting.GetLangfuseSetting().TraceContent, } + langfuseCfg := langfuse_setting.GetLangfuseSetting() + data["langfuse_trace_content"] = langfuseCfg.Enabled && langfuseCfg.TraceContent // 根据启用状态注入可选内容 if cs.ApiInfoEnabled { diff --git a/web/classic/src/components/settings/personal/cards/PreferencesSettings.jsx b/web/classic/src/components/settings/personal/cards/PreferencesSettings.jsx index 8f18844c86f..2a8bec2c155 100644 --- a/web/classic/src/components/settings/personal/cards/PreferencesSettings.jsx +++ b/web/classic/src/components/settings/personal/cards/PreferencesSettings.jsx @@ -84,7 +84,7 @@ const updateFrontendThemePreference = async (theme, userId) => { const PreferencesSettings = ({ t }) => { const { i18n } = useTranslation(); - const [userState, userDispatch] = useContext(UserContext); + const [userState, userDispatch, startThemeNavigation] = useContext(UserContext); const [currentLanguage, setCurrentLanguage] = useState( normalizeLanguage(i18n.language) || 'zh-CN', ); @@ -198,6 +198,7 @@ const PreferencesSettings = ({ t }) => { setFrontendTheme(theme); setCurrentFrontendTheme(theme); showSuccess(t('界面风格已切换,正在跳转')); + startThemeNavigation(); setTimeout(() => { window.location.assign(getFrontendThemeSettingsPath(theme)); }, 300); diff --git a/web/classic/src/context/User/index.jsx b/web/classic/src/context/User/index.jsx index 1dc5d2f49e8..47c056c87cc 100644 --- a/web/classic/src/context/User/index.jsx +++ b/web/classic/src/context/User/index.jsx @@ -17,7 +17,7 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import React, { useEffect } from 'react'; +import React, { useEffect, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { reducer, initialState } from './reducer'; import { normalizeLanguage } from '../../i18n/language'; @@ -29,8 +29,6 @@ const normalizeFrontendTheme = (value) => { return value === 'classic' ? 'classic' : 'default'; }; -const themeRedirectAttempted = { current: false }; - const setFrontendTheme = (theme) => { if (typeof document === 'undefined') return; document.cookie = `${FRONTEND_THEME_COOKIE_NAME}=${theme}; path=/; max-age=${FRONTEND_THEME_COOKIE_MAX_AGE}`; @@ -39,13 +37,19 @@ const setFrontendTheme = (theme) => { export const UserContext = React.createContext({ state: initialState, dispatch: () => null, + startThemeNavigation: () => {}, }); export const UserProvider = ({ children }) => { const [state, dispatch] = React.useReducer(reducer, initialState); const { i18n } = useTranslation(); + const themeRedirectAttemptedRef = useRef(false); + const themeNavigationPendingRef = useRef(false); + + const startThemeNavigation = useCallback(() => { + themeNavigationPendingRef.current = true; + }, []); - // Sync language preference when user data is loaded useEffect(() => { if (state.user?.setting) { try { @@ -60,8 +64,8 @@ export const UserProvider = ({ children }) => { if (settings.frontend_theme) { const normalizedTheme = normalizeFrontendTheme(settings.frontend_theme); setFrontendTheme(normalizedTheme); - if (normalizedTheme === 'default' && !themeRedirectAttempted.current) { - themeRedirectAttempted.current = true; + if (normalizedTheme === 'default' && !themeRedirectAttemptedRef.current && !themeNavigationPendingRef.current) { + themeRedirectAttemptedRef.current = true; window.location.replace('/dashboard'); } } @@ -72,7 +76,7 @@ export const UserProvider = ({ children }) => { }, [state.user?.setting, i18n]); return ( - + {children} ); diff --git a/web/default/src/features/auth/hooks/use-auth-redirect.ts b/web/default/src/features/auth/hooks/use-auth-redirect.ts index 8a2f546f046..843d424fef5 100644 --- a/web/default/src/features/auth/hooks/use-auth-redirect.ts +++ b/web/default/src/features/auth/hooks/use-auth-redirect.ts @@ -90,7 +90,7 @@ export function useAuthRedirect() { if (savedTheme) { setFrontendTheme(savedTheme) if (savedTheme === 'classic') { - window.location.assign('/console') + window.location.replace('/console') return } } diff --git a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx index 0166182fb9d..a7d47ce6ca1 100644 --- a/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/channel-test-dialog.tsx @@ -352,7 +352,7 @@ export function ChannelTestDialog({ size='sm' className='w-fit' onClick={() => - window.open('/system-settings?tab=ratio', '_blank') + window.open('/system-settings?tab=ratio', '_blank', 'noopener') } > diff --git a/web/default/src/features/playground/components/message-error.tsx b/web/default/src/features/playground/components/message-error.tsx index 173e0ccf8ac..9b9307f1e9d 100644 --- a/web/default/src/features/playground/components/message-error.tsx +++ b/web/default/src/features/playground/components/message-error.tsx @@ -39,7 +39,7 @@ export function MessageError({ message, className = '' }: MessageErrorProps) { variant='outline' size='sm' onClick={() => - window.open('/system-settings?tab=ratio', '_blank') + window.open('/system-settings?tab=ratio', '_blank', 'noopener') } > diff --git a/web/default/src/i18n/locales/_reports/_sync-report.json b/web/default/src/i18n/locales/_reports/_sync-report.json index c766dc48d8d..ab28efa1383 100644 --- a/web/default/src/i18n/locales/_reports/_sync-report.json +++ b/web/default/src/i18n/locales/_reports/_sync-report.json @@ -11,25 +11,25 @@ "file": "fr.json", "missingCount": 0, "extrasCount": 0, - "untranslatedCount": 10 + "untranslatedCount": 9 }, "ja": { "file": "ja.json", "missingCount": 0, "extrasCount": 0, - "untranslatedCount": 125 + "untranslatedCount": 123 }, "ru": { "file": "ru.json", "missingCount": 0, "extrasCount": 0, - "untranslatedCount": 129 + "untranslatedCount": 127 }, "vi": { "file": "vi.json", "missingCount": 0, "extrasCount": 0, - "untranslatedCount": 10 + "untranslatedCount": 9 }, "zh": { "file": "zh.json", diff --git a/web/default/src/i18n/locales/_reports/fr.untranslated.json b/web/default/src/i18n/locales/_reports/fr.untranslated.json index 557f333a214..a686891329a 100644 --- a/web/default/src/i18n/locales/_reports/fr.untranslated.json +++ b/web/default/src/i18n/locales/_reports/fr.untranslated.json @@ -1,7 +1,6 @@ { "A text banner displayed at the top of all pages. Leave empty to disable.": "A text banner displayed at the top of all pages. Leave empty to disable.", "Add animation effects to the banner background.": "Add animation effects to the banner background.", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Choose a preset effect and customize colors and speed.": "Choose a preset effect and customize colors and speed.", "Comma-separated color values for the gradient.": "Comma-separated color values for the gradient.", "Configure the banner background style.": "Configure the banner background style.", diff --git a/web/default/src/i18n/locales/_reports/ja.untranslated.json b/web/default/src/i18n/locales/_reports/ja.untranslated.json index 5339c826cae..da8aeb8516f 100644 --- a/web/default/src/i18n/locales/_reports/ja.untranslated.json +++ b/web/default/src/i18n/locales/_reports/ja.untranslated.json @@ -17,7 +17,6 @@ "Anthropic": "Anthropic", "API URL": "API URL", "API2GPT": "API2GPT", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *", "Background Type": "Background Type", "Banner Content": "Banner Content", @@ -73,7 +72,6 @@ "https://worker.example.workers.dev": "https://worker.example.workers.dev", "https://your-server.example.com": "https://your-server.example.com", "https://yourdomain.com": "https://yourdomain.com", - "Initial Subscription Plan": "Initial Subscription Plan", "Intensity": "Intensity", "Jimeng": "Jimeng", "JustSong": "JustSong", diff --git a/web/default/src/i18n/locales/_reports/ru.untranslated.json b/web/default/src/i18n/locales/_reports/ru.untranslated.json index 40cd6b7de2e..3de5a3ac7ba 100644 --- a/web/default/src/i18n/locales/_reports/ru.untranslated.json +++ b/web/default/src/i18n/locales/_reports/ru.untranslated.json @@ -20,7 +20,6 @@ "Animation Type": "Animation Type", "Anthropic": "Anthropic", "API2GPT": "API2GPT", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *", "Background Type": "Background Type", "Baidu V2": "Baidu V2", @@ -77,7 +76,6 @@ "https://worker.example.workers.dev": "https://worker.example.workers.dev", "https://your-server.example.com": "https://your-server.example.com", "https://yourdomain.com": "https://yourdomain.com", - "Initial Subscription Plan": "Initial Subscription Plan", "Intensity": "Intensity", "Jimeng": "Jimeng", "JustSong": "JustSong", diff --git a/web/default/src/i18n/locales/_reports/vi.untranslated.json b/web/default/src/i18n/locales/_reports/vi.untranslated.json index 557f333a214..a686891329a 100644 --- a/web/default/src/i18n/locales/_reports/vi.untranslated.json +++ b/web/default/src/i18n/locales/_reports/vi.untranslated.json @@ -1,7 +1,6 @@ { "A text banner displayed at the top of all pages. Leave empty to disable.": "A text banner displayed at the top of all pages. Leave empty to disable.", "Add animation effects to the banner background.": "Add animation effects to the banner background.", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", "Choose a preset effect and customize colors and speed.": "Choose a preset effect and customize colors and speed.", "Comma-separated color values for the gradient.": "Comma-separated color values for the gradient.", "Configure the banner background style.": "Configure the banner background style.", diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json index e87b7e4ec09..436fd8614da 100644 --- a/web/default/src/i18n/locales/fr.json +++ b/web/default/src/i18n/locales/fr.json @@ -400,7 +400,7 @@ "Auto-discovers endpoints from the provider": "Découvre automatiquement les points de terminaison du fournisseur", "Auto-fill when one field exists and another is missing": "Remplissage automatique si un champ existe et l'autre est manquant", "Auto-retry status codes": "Codes de statut de nouvelle tentative auto", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", + "Automatically bind this subscription plan to new users upon registration": "Lier automatiquement ce plan d'abonnement aux nouveaux utilisateurs lors de l'inscription", "Automatically disable channel on repeated failures": "Désactiver automatiquement le canal en cas d'échecs répétés", "Automatically disable channels exceeding this response time": "Désactiver automatiquement les canaux dépassant ce temps de réponse", "Automatically disable channels when tests fail": "Désactiver automatiquement les canaux lorsque les tests échouent", @@ -1874,7 +1874,7 @@ "Initialize": "Initialiser", "Initialize system": "Initialiser le système", "Initializing…": "Initialisation…", - "Initial Subscription Plan": "Initial Subscription Plan", + "Initial Subscription Plan": "Plan d'abonnement initial", "Inpaint": "Inpainting", "Input": "Entrée", "Input mode": "Mode d'entrée", diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json index 5afaa6f04ac..e2b912db86f 100644 --- a/web/default/src/i18n/locales/ja.json +++ b/web/default/src/i18n/locales/ja.json @@ -400,7 +400,7 @@ "Auto-discovers endpoints from the provider": "プロバイダーからエンドポイントを自動検出します", "Auto-fill when one field exists and another is missing": "一方のフィールドがあり他方が欠けている場合に自動補完", "Auto-retry status codes": "自動リトライするステータスコード", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", + "Automatically bind this subscription plan to new users upon registration": "新規ユーザー登録時にこのサブスクリプションプランを自動的にバインドする", "Automatically disable channel on repeated failures": "繰り返しの失敗でチャンネルを自動的に無効にする", "Automatically disable channels exceeding this response time": "この応答時間を超えるチャネルを自動的に無効にする", "Automatically disable channels when tests fail": "テストが失敗したときにチャネルを自動的に無効にする", @@ -1874,7 +1874,7 @@ "Initialize": "初期化", "Initialize system": "システム初期化", "Initializing…": "初期化中…", - "Initial Subscription Plan": "Initial Subscription Plan", + "Initial Subscription Plan": "初期サブスクリプションプラン", "Inpaint": "インペイント", "Input": "入力", "Input mode": "入力モード", diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json index d2dc06e6709..09caf495c35 100644 --- a/web/default/src/i18n/locales/ru.json +++ b/web/default/src/i18n/locales/ru.json @@ -400,7 +400,7 @@ "Auto-discovers endpoints from the provider": "Автоматически обнаруживает конечные точки от провайдера", "Auto-fill when one field exists and another is missing": "Автозаполнение, когда одно поле есть, а другое отсутствует", "Auto-retry status codes": "Коды авто-повтора", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", + "Automatically bind this subscription plan to new users upon registration": "Автоматически привязывать этот план подписки новым пользователям при регистрации", "Automatically disable channel on repeated failures": "Автоматически отключать канал при повторных неудачах", "Automatically disable channels exceeding this response time": "Автоматически отключать каналы, превышающие это время ответа", "Automatically disable channels when tests fail": "Автоматически отключать каналы при сбое тестов", @@ -1874,7 +1874,7 @@ "Initialize": "Инициализировать", "Initialize system": "Инициализация системы", "Initializing…": "Инициализация…", - "Initial Subscription Plan": "Initial Subscription Plan", + "Initial Subscription Plan": "Начальный план подписки", "Inpaint": "Инпейнтинг", "Input": "Ввод", "Input mode": "Режим ввода", diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json index 7c51c1bafe1..94a9f0dca51 100644 --- a/web/default/src/i18n/locales/vi.json +++ b/web/default/src/i18n/locales/vi.json @@ -400,7 +400,7 @@ "Auto-discovers endpoints from the provider": "Tự động khám phá các điểm cuối từ nhà cung cấp", "Auto-fill when one field exists and another is missing": "Tự động điền khi một trường có giá trị và trường khác thiếu", "Auto-retry status codes": "Mã trạng thái tự thử lại", - "Automatically bind this subscription plan to new users upon registration": "Automatically bind this subscription plan to new users upon registration", + "Automatically bind this subscription plan to new users upon registration": "Tự động liên kết gói đăng ký này với người dùng mới khi đăng ký", "Automatically disable channel on repeated failures": "Tự động vô hiệu hóa kênh khi xảy ra lỗi lặp lại", "Automatically disable channels exceeding this response time": "Tự động vô hiệu hóa các kênh vượt quá thời gian phản hồi này", "Automatically disable channels when tests fail": "Tự động vô hiệu hóa các kênh khi kiểm thử thất bại", @@ -1874,7 +1874,7 @@ "Initialize": "Khởi tạo", "Initialize system": "Khởi tạo hệ thống", "Initializing…": "Đang khởi tạo…", - "Initial Subscription Plan": "Initial Subscription Plan", + "Initial Subscription Plan": "Gói đăng ký ban đầu", "Inpaint": "Inpaint", "Input": "Đầu vào", "Input mode": "Chế độ nhập", From cfc6f763798eea463cf2b50fcf2f6485d31302f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E9=9B=A8=E6=99=A8?= Date: Sat, 23 May 2026 01:58:51 +0800 Subject: [PATCH 5/5] fix: add noopener,noreferrer to all window.open calls to prevent tab-napping - Fix OAuth window.open calls in default theme (oauth.ts): GitHub, Discord, OIDC, LinuxDO - Fix OAuth window.open in classic theme (api.js): redirectToOAuthUrl - Fix payment window.open calls: use-waffo-payment, use-payment, use-creem-payment, subscription-purchase-dialog - Fix utility window.open calls: audio-preview-dialog, cc-switch-dialog, view-details-dialog, ionet-deployment-settings-section - Fix classic theme window.open calls: topup, SubscriptionPlansCard, CCSwitchModal, AudioPreviewModal, ContentModal, ViewLogsModal, ModelTestModal, MessageContent, MarkdownRenderer, useTokensData, SettingModelDeployment, Home --- .../components/common/markdown/MarkdownRenderer.jsx | 2 +- .../src/components/playground/MessageContent.jsx | 2 +- .../table/channels/modals/ModelTestModal.jsx | 2 +- .../table/model-deployments/modals/ViewLogsModal.jsx | 2 +- .../table/task-logs/modals/AudioPreviewModal.jsx | 2 +- .../components/table/task-logs/modals/ContentModal.jsx | 2 +- .../components/table/tokens/modals/CCSwitchModal.jsx | 2 +- .../src/components/topup/SubscriptionPlansCard.jsx | 4 ++-- web/classic/src/components/topup/index.jsx | 10 +++++----- web/classic/src/helpers/api.js | 2 +- web/classic/src/hooks/tokens/useTokensData.jsx | 2 +- web/classic/src/pages/Home/index.jsx | 3 ++- .../src/pages/Setting/Model/SettingModelDeployment.jsx | 2 +- .../keys/components/dialogs/cc-switch-dialog.tsx | 2 +- .../models/components/dialogs/view-details-dialog.tsx | 2 +- .../dialogs/subscription-purchase-dialog.tsx | 4 ++-- .../integrations/ionet-deployment-settings-section.tsx | 2 +- .../components/dialogs/audio-preview-dialog.tsx | 2 +- .../src/features/wallet/hooks/use-creem-payment.ts | 2 +- web/default/src/features/wallet/hooks/use-payment.ts | 2 +- .../src/features/wallet/hooks/use-waffo-payment.ts | 2 +- web/default/src/lib/oauth.ts | 8 ++++---- 22 files changed, 32 insertions(+), 31 deletions(-) diff --git a/web/classic/src/components/common/markdown/MarkdownRenderer.jsx b/web/classic/src/components/common/markdown/MarkdownRenderer.jsx index 6a71c695f84..bb61efe3834 100644 --- a/web/classic/src/components/common/markdown/MarkdownRenderer.jsx +++ b/web/classic/src/components/common/markdown/MarkdownRenderer.jsx @@ -66,7 +66,7 @@ export function Mermaid(props) { const text = new XMLSerializer().serializeToString(svg); const blob = new Blob([text], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); - window.open(url, '_blank'); + window.open(url, '_blank', 'noopener,noreferrer'); } if (hasError) { diff --git a/web/classic/src/components/playground/MessageContent.jsx b/web/classic/src/components/playground/MessageContent.jsx index 94f494bb31f..6d103fab2f7 100644 --- a/web/classic/src/components/playground/MessageContent.jsx +++ b/web/classic/src/components/playground/MessageContent.jsx @@ -93,7 +93,7 @@ const MessageContent = ({ theme='light' type='warning' icon={} - onClick={() => window.open('/console/setting?tab=ratio', '_blank')} + onClick={() => window.open('/console/setting?tab=ratio', '_blank', 'noopener,noreferrer')} > {t('前往设置')} diff --git a/web/classic/src/components/table/channels/modals/ModelTestModal.jsx b/web/classic/src/components/table/channels/modals/ModelTestModal.jsx index e7f57453229..f46439422f9 100644 --- a/web/classic/src/components/table/channels/modals/ModelTestModal.jsx +++ b/web/classic/src/components/table/channels/modals/ModelTestModal.jsx @@ -199,7 +199,7 @@ const ModelTestModal = ({ theme='light' type='warning' icon={} - onClick={() => window.open('/console/setting?tab=ratio', '_blank')} + onClick={() => window.open('/console/setting?tab=ratio', '_blank', 'noopener,noreferrer')} style={{ width: 'fit-content' }} > {t('前往设置')} diff --git a/web/classic/src/components/table/model-deployments/modals/ViewLogsModal.jsx b/web/classic/src/components/table/model-deployments/modals/ViewLogsModal.jsx index 3d0446aeab9..e5041401fe8 100644 --- a/web/classic/src/components/table/model-deployments/modals/ViewLogsModal.jsx +++ b/web/classic/src/components/table/model-deployments/modals/ViewLogsModal.jsx @@ -573,7 +573,7 @@ const ViewLogsModal = ({ visible, onCancel, deployment, t }) => { size='small' theme='borderless' onClick={() => - window.open(containerDetails.public_url, '_blank') + window.open(containerDetails.public_url, '_blank', 'noopener,noreferrer') } /> diff --git a/web/classic/src/components/table/task-logs/modals/AudioPreviewModal.jsx b/web/classic/src/components/table/task-logs/modals/AudioPreviewModal.jsx index 0b2cada1988..afd260a5462 100644 --- a/web/classic/src/components/table/task-logs/modals/AudioPreviewModal.jsx +++ b/web/classic/src/components/table/task-logs/modals/AudioPreviewModal.jsx @@ -119,7 +119,7 @@ const AudioClipCard = ({ clip }) => { diff --git a/web/classic/src/components/table/task-logs/modals/ContentModal.jsx b/web/classic/src/components/table/task-logs/modals/ContentModal.jsx index 3527fd96d61..88a5289111b 100644 --- a/web/classic/src/components/table/task-logs/modals/ContentModal.jsx +++ b/web/classic/src/components/table/task-logs/modals/ContentModal.jsx @@ -55,7 +55,7 @@ const ContentModal = ({ }; const handleOpenInNewTab = () => { - window.open(modalContent, '_blank'); + window.open(modalContent, '_blank', 'noopener,noreferrer'); }; const renderVideoContent = () => { diff --git a/web/classic/src/components/table/tokens/modals/CCSwitchModal.jsx b/web/classic/src/components/table/tokens/modals/CCSwitchModal.jsx index 6cf817330a5..69bb737a868 100644 --- a/web/classic/src/components/table/tokens/modals/CCSwitchModal.jsx +++ b/web/classic/src/components/table/tokens/modals/CCSwitchModal.jsx @@ -117,7 +117,7 @@ export default function CCSwitchModal({ return; } const url = buildCCSwitchURL(app, name, models, 'sk-' + tokenKey); - window.open(url, '_blank'); + window.open(url, '_blank', 'noopener,noreferrer'); onClose(); }; diff --git a/web/classic/src/components/topup/SubscriptionPlansCard.jsx b/web/classic/src/components/topup/SubscriptionPlansCard.jsx index 9c50828372b..8e91861b8df 100644 --- a/web/classic/src/components/topup/SubscriptionPlansCard.jsx +++ b/web/classic/src/components/topup/SubscriptionPlansCard.jsx @@ -124,7 +124,7 @@ const SubscriptionPlansCard = ({ plan_id: selectedPlan.plan.id, }); if (res.data?.message === 'success') { - window.open(res.data.data?.pay_link, '_blank'); + window.open(res.data.data?.pay_link, '_blank', 'noopener,noreferrer'); showSuccess(t('已打开支付页面')); closeBuy(); } else { @@ -152,7 +152,7 @@ const SubscriptionPlansCard = ({ plan_id: selectedPlan.plan.id, }); if (res.data?.message === 'success') { - window.open(res.data.data?.checkout_url, '_blank'); + window.open(res.data.data?.checkout_url, '_blank', 'noopener,noreferrer'); showSuccess(t('已打开支付页面')); closeBuy(); } else { diff --git a/web/classic/src/components/topup/index.jsx b/web/classic/src/components/topup/index.jsx index 1c23ca928c1..df2c590c082 100644 --- a/web/classic/src/components/topup/index.jsx +++ b/web/classic/src/components/topup/index.jsx @@ -188,7 +188,7 @@ const TopUp = () => { showError(t('超级管理员未设置充值链接!')); return; } - window.open(topUpLink, '_blank'); + window.open(topUpLink, '_blank', 'noopener,noreferrer'); }; const preTopUp = async (payment) => { @@ -294,7 +294,7 @@ const TopUp = () => { if (message === 'success') { if (payWay === 'stripe') { // Stripe 支付回调处理 - window.open(data.pay_link, '_blank'); + window.open(data.pay_link, '_blank', 'noopener,noreferrer'); } else { // 普通支付表单提交 let params = data; @@ -397,7 +397,7 @@ const TopUp = () => { if (res !== undefined) { const { message, data } = res.data; if (message === 'success' && data?.payment_url) { - window.open(data.payment_url, '_blank'); + window.open(data.payment_url, '_blank', 'noopener,noreferrer'); } else { showError(data || t('支付请求失败')); } @@ -455,7 +455,7 @@ const TopUp = () => { if (message === 'success') { const checkoutUrl = data?.checkout_url || ''; if (checkoutUrl) { - window.open(checkoutUrl, '_blank'); + window.open(checkoutUrl, '_blank', 'noopener,noreferrer'); } else { showError(t('支付请求失败')); } @@ -503,7 +503,7 @@ const TopUp = () => { const processCreemCallback = (data) => { // 与 Stripe 保持一致的实现方式 - window.open(data.checkout_url, '_blank'); + window.open(data.checkout_url, '_blank', 'noopener,noreferrer'); }; const getUserQuota = async () => { diff --git a/web/classic/src/helpers/api.js b/web/classic/src/helpers/api.js index 88122a564cf..87c680d5dda 100644 --- a/web/classic/src/helpers/api.js +++ b/web/classic/src/helpers/api.js @@ -42,7 +42,7 @@ function redirectToOAuthUrl(url, options = {}) { const targetUrl = typeof url === 'string' ? url : url.toString(); if (openInNewTab) { - window.open(targetUrl, '_blank'); + window.open(targetUrl, '_blank', 'noopener,noreferrer'); return; } diff --git a/web/classic/src/hooks/tokens/useTokensData.jsx b/web/classic/src/hooks/tokens/useTokensData.jsx index abee82b3977..55bdd411f0b 100644 --- a/web/classic/src/hooks/tokens/useTokensData.jsx +++ b/web/classic/src/hooks/tokens/useTokensData.jsx @@ -257,7 +257,7 @@ export const useTokensData = (openFluentNotification, openCCSwitchModal) => { url = url.replaceAll('{key}', `sk-${fullKey}`); } - window.open(url, '_blank'); + window.open(url, '_blank', 'noopener,noreferrer'); }; // Manage token function (delete, enable, disable) diff --git a/web/classic/src/pages/Home/index.jsx b/web/classic/src/pages/Home/index.jsx index c153c1b3da9..81c7d677f1a 100644 --- a/web/classic/src/pages/Home/index.jsx +++ b/web/classic/src/pages/Home/index.jsx @@ -233,6 +233,7 @@ const Home = () => { window.open( 'https://github.com/QuantumNous/new-api', '_blank', + 'noopener,noreferrer', ) } > @@ -244,7 +245,7 @@ const Home = () => { size={isMobile ? 'default' : 'large'} className='flex items-center !rounded-3xl px-6 py-2' icon={} - onClick={() => window.open(docsLink, '_blank')} + onClick={() => window.open(docsLink, '_blank', 'noopener,noreferrer')} > {t('文档')} diff --git a/web/classic/src/pages/Setting/Model/SettingModelDeployment.jsx b/web/classic/src/pages/Setting/Model/SettingModelDeployment.jsx index fdfbb448e36..3858e215d5b 100644 --- a/web/classic/src/pages/Setting/Model/SettingModelDeployment.jsx +++ b/web/classic/src/pages/Setting/Model/SettingModelDeployment.jsx @@ -310,7 +310,7 @@ export default function SettingModelDeployment(props) { theme='solid' style={{ width: '100%' }} onClick={() => - window.open('https://ai.io.net/ai/api-keys', '_blank') + window.open('https://ai.io.net/ai/api-keys', '_blank', 'noopener,noreferrer') } > {t('前往 io.net API Keys')} diff --git a/web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx b/web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx index f99beafd29d..eab2e99a609 100644 --- a/web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx +++ b/web/default/src/features/keys/components/dialogs/cc-switch-dialog.tsx @@ -128,7 +128,7 @@ export function CCSwitchDialog(props: Props) { ? props.tokenKey : `sk-${props.tokenKey}` const url = buildCCSwitchURL(app, name, models, key) - window.open(url, '_blank') + window.open(url, '_blank', 'noopener,noreferrer') props.onOpenChange(false) } diff --git a/web/default/src/features/models/components/dialogs/view-details-dialog.tsx b/web/default/src/features/models/components/dialogs/view-details-dialog.tsx index ae2a9d33344..27966c46a06 100644 --- a/web/default/src/features/models/components/dialogs/view-details-dialog.tsx +++ b/web/default/src/features/models/components/dialogs/view-details-dialog.tsx @@ -224,7 +224,7 @@ export function ViewDetailsDialog({