diff --git a/messages/en/settings.json b/messages/en/settings.json index 78d1184e5..6654ba5d1 100644 --- a/messages/en/settings.json +++ b/messages/en/settings.json @@ -748,6 +748,15 @@ "chunksCount": "Received {count} chunks ({format})", "truncatedPreview": "Showing first {length} characters, copy to see full content", "truncatedBrief": "Showing first {length} characters, click \"View Details\" for more", + "timeout": { + "label": "Timeout (seconds)", + "desc": "Max wait time for test request (5-120 sec)", + "geminiHint": ", Gemini Thinking models recommend 60+ sec" + }, + "geminiAuthFallback": { + "warning": "Header auth failed, using URL parameter auth", + "desc": "Actual proxy forwarding only uses header auth, may cause request failures" + }, "copyFormat": { "testResult": "Test result", "message": "Message", @@ -1225,6 +1234,15 @@ "desc": "Group tag. Only users whose providerGroup matches can use this provider. Example: set to \"premium\" to serve users with providerGroup=\"premium\" only" } }, + "cacheTtl": { + "label": "Cache TTL Override", + "options": { + "inherit": "No override (follow client)", + "5m": "5 minutes", + "1h": "1 hour" + }, + "desc": "Force prompt cache TTL; only affects requests with cache_control." + }, "context1m": { "label": "1M Context Window", "options": { @@ -1598,7 +1616,6 @@ "leaderboard": "Leaderboard", "title": "Provider Management" }, - "addProvider": "Add server", "filter": { "status": { "all": "Any status", diff --git a/messages/ja/settings.json b/messages/ja/settings.json index 60958b747..1f0ccbd97 100644 --- a/messages/ja/settings.json +++ b/messages/ja/settings.json @@ -680,6 +680,15 @@ "chunksCount": "{count} チャンク受信 ({format})", "truncatedPreview": "先頭 {length} 文字を表示、全文はコピーして確認", "truncatedBrief": "先頭 {length} 文字を表示、全文は「詳細を見る」をクリック", + "timeout": { + "label": "タイムアウト(秒)", + "desc": "テストリクエストの最大待機時間(5〜120秒)", + "geminiHint": "、Gemini Thinkingモデルは60秒以上を推奨" + }, + "geminiAuthFallback": { + "warning": "ヘッダー認証に失敗し、URLパラメータ認証を使用しました", + "desc": "実際のプロキシ転送はヘッダー認証のみを使用するため、リクエストが失敗する可能性があります" + }, "copyFormat": { "testResult": "テスト結果", "message": "メッセージ", @@ -1120,6 +1129,15 @@ "desc": "グループタグ。ユーザーの providerGroup が一致する場合のみ利用可能。例: \"premium\" に設定すると providerGroup=\"premium\" のユーザーのみ対象" } }, + "cacheTtl": { + "label": "キャッシュTTLオーバーライド", + "options": { + "inherit": "オーバーライドしない(クライアントに従う)", + "5m": "5分", + "1h": "1時間" + }, + "desc": "プロンプトキャッシュのTTLを強制設定。cache_controlを含むリクエストにのみ適用されます。" + }, "context1m": { "label": "1M コンテキストウィンドウ", "options": { @@ -1468,7 +1486,6 @@ "leaderboard": "プロバイダーランキング", "title": "服务商管理" }, - "addProvider": "プロバイダーを追加", "filter": { "status": { "all": "すべてのステータス", diff --git a/messages/ru/settings.json b/messages/ru/settings.json index 82c4d1971..dd248698d 100644 --- a/messages/ru/settings.json +++ b/messages/ru/settings.json @@ -680,6 +680,15 @@ "chunksCount": "Получено {count} чанков ({format})", "truncatedPreview": "Показаны первые {length} символов, скопируйте для просмотра полного текста", "truncatedBrief": "Показаны первые {length} символов, нажмите «Подробнее» для полного просмотра", + "timeout": { + "label": "Таймаут (секунды)", + "desc": "Максимальное время ожидания тестового запроса (5-120 сек)", + "geminiHint": ", для моделей Gemini Thinking рекомендуется 60+ сек" + }, + "geminiAuthFallback": { + "warning": "Ошибка аутентификации через заголовок, использована URL-аутентификация", + "desc": "Реальный прокси использует только аутентификацию через заголовок, что может вызвать ошибки" + }, "copyFormat": { "testResult": "Результат теста", "message": "Сообщение", @@ -1120,6 +1129,15 @@ "desc": "Метка группы. Пользователь может использовать провайдера только если его providerGroup совпадает. Пример: значение \"premium\" — только для пользователей с providerGroup=\"premium\"" } }, + "cacheTtl": { + "label": "Переопределение Cache TTL", + "options": { + "inherit": "Не переопределять (следовать клиенту)", + "5m": "5 минут", + "1h": "1 час" + }, + "desc": "Принудительно задать TTL кэша промптов; влияет только на запросы с cache_control." + }, "context1m": { "label": "Контекстное окно 1M", "options": { @@ -1468,7 +1486,6 @@ "leaderboard": "Рейтинг", "title": "Поставщики" }, - "addProvider": "Добавить", "filter": { "status": { "all": "Все статусы", diff --git a/messages/zh-CN/settings.json b/messages/zh-CN/settings.json index 6ced53a21..9f2521247 100644 --- a/messages/zh-CN/settings.json +++ b/messages/zh-CN/settings.json @@ -289,6 +289,15 @@ "chunksCount": "接收 {count} 个数据块 ({format})", "truncatedPreview": "显示前 {length} 字符,完整内容请复制查看", "truncatedBrief": "显示前 {length} 字符,完整内容请点击「查看详情」", + "timeout": { + "label": "超时时间(秒)", + "desc": "测试请求的最大等待时间(5-120 秒)", + "geminiHint": ",Gemini Thinking 模型建议 60 秒以上" + }, + "geminiAuthFallback": { + "warning": "Header 认证失败,使用了 URL 参数认证", + "desc": "实际代理转发仅使用 Header 认证,可能导致请求失败" + }, "copyFormat": { "testResult": "测试结果", "message": "消息", @@ -734,6 +743,15 @@ "desc": "供应商分组标签(支持多个,逗号分隔)。只有用户的 providerGroup 与此值匹配时,该用户才能使用此供应商。留空=对所有用户开放" } }, + "cacheTtl": { + "label": "Cache TTL 覆写", + "options": { + "inherit": "不覆写(跟随客户端)", + "5m": "5 分钟", + "1h": "1 小时" + }, + "desc": "强制设置 prompt cache TTL;仅影响包含 cache_control 的请求。" + }, "context1m": { "label": "1M 上下文窗口", "options": { diff --git a/messages/zh-TW/settings.json b/messages/zh-TW/settings.json index 14a185122..258f023a9 100644 --- a/messages/zh-TW/settings.json +++ b/messages/zh-TW/settings.json @@ -718,6 +718,15 @@ "chunksCount": "已接收 {count} 個區塊({format})", "truncatedPreview": "顯示前 {length} 個字元,複製可查看完整內容", "truncatedBrief": "顯示前 {length} 個字元,點擊「查看詳情」以查看更多", + "timeout": { + "label": "逾時時間(秒)", + "desc": "測試請求的最大等待時間(5-120 秒)", + "geminiHint": ",Gemini Thinking 模型建議 60 秒以上" + }, + "geminiAuthFallback": { + "warning": "Header 認證失敗,使用了 URL 參數認證", + "desc": "實際代理轉發僅使用 Header 認證,可能導致請求失敗" + }, "copyFormat": { "testResult": "測試結果", "message": "訊息", @@ -1120,6 +1129,15 @@ "desc": "分組標籤。僅供 providerGroup 與此值相符的用戶使用。例:設為「premium」表示僅供 providerGroup=\"premium\" 的用戶使用" } }, + "cacheTtl": { + "label": "Cache TTL 覆寫", + "options": { + "inherit": "不覆寫(跟隨客戶端)", + "5m": "5 分鐘", + "1h": "1 小時" + }, + "desc": "強制設定 prompt cache TTL;僅影響包含 cache_control 的請求。" + }, "context1m": { "label": "1M 上下文視窗", "options": { @@ -1474,7 +1492,6 @@ "leaderboard": "供應商排行榜", "title": "服务商管理" }, - "addProvider": "新增服務商", "filter": { "status": { "all": "所有狀態", diff --git a/src/app/[locale]/dashboard/_components/user/user-management-table.tsx b/src/app/[locale]/dashboard/_components/user/user-management-table.tsx index c351c2163..50e1c932c 100644 --- a/src/app/[locale]/dashboard/_components/user/user-management-table.tsx +++ b/src/app/[locale]/dashboard/_components/user/user-management-table.tsx @@ -262,7 +262,11 @@ export function UserManagementTable({ useEffect(() => { if (!scrollResetKey) return; parentRef.current?.scrollTo({ top: 0 }); - rowVirtualizer.measure(); + // Defer measurement to next frame to ensure DOM has updated + const rafId = requestAnimationFrame(() => { + rowVirtualizer.measure(); + }); + return () => cancelAnimationFrame(rafId); }, [scrollResetKey, rowVirtualizer]); const quickRenewTranslations = useMemo(() => { diff --git a/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx b/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx index 981f4887b..d5ab9a530 100644 --- a/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx +++ b/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx @@ -132,14 +132,14 @@ export function AddRuleDialog() { {t("errorRules.add")} - -
- + + + {t("errorRules.dialog.addTitle")} {t("errorRules.dialog.addDescription")} -
+
- + - +
- +
- 测试请求的最大等待时间(5-120 秒) - {apiFormat === "gemini" && ",Gemini Thinking 模型建议 60 秒以上"} + {t("timeout.desc")} + {apiFormat === "gemini" && t("timeout.geminiHint")}
diff --git a/src/app/[locale]/settings/providers/_components/forms/provider-form.tsx b/src/app/[locale]/settings/providers/_components/forms/provider-form.tsx index 5e7d55dbb..d241912cf 100644 --- a/src/app/[locale]/settings/providers/_components/forms/provider-form.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/provider-form.tsx @@ -4,6 +4,7 @@ import { useTranslations } from "next-intl"; import { useEffect, useRef, useState, useTransition } from "react"; import { toast } from "sonner"; import { addProvider, editProvider, removeProvider } from "@/actions/providers"; +import { getDistinctProviderGroupsAction } from "@/actions/request-filters"; import { AlertDialog, AlertDialogAction, @@ -18,7 +19,7 @@ import { import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { @@ -95,6 +96,7 @@ export function ProviderForm({ .filter(Boolean) : [] ); + const [groupSuggestions, setGroupSuggestions] = useState([]); const [limit5hUsd, setLimit5hUsd] = useState(sourceProvider?.limit5hUsd ?? null); const [limitDailyUsd, setLimitDailyUsd] = useState( sourceProvider?.limitDailyUsd ?? null @@ -231,6 +233,19 @@ export function ProviderForm({ return () => clearTimeout(timer); }, []); + // Load existing provider groups as suggestions + useEffect(() => { + getDistinctProviderGroupsAction() + .then((res) => { + if (res.ok && res.data) { + setGroupSuggestions(res.data); + } + }) + .catch((err) => { + console.error("Failed to load provider groups:", err); + }); + }, []); + // 折叠区域切换函数 const toggleSection = (key: SectionKey) => { setOpenSections((prev) => ({ ...prev, [key]: !prev[key] })); @@ -501,385 +516,175 @@ export function ProviderForm({ }; return ( -
- + <> + {isEdit ? t("title.edit") : t("title.create")} - -
- - setName(e.target.value)} - placeholder={t("name.placeholder")} - disabled={isPending} - required - /> -
+ +
+
+ + setName(e.target.value)} + placeholder={t("name.placeholder")} + disabled={isPending} + required + /> +
- {/* 移除描述字段 */} - -
- - setUrl(e.target.value)} - placeholder={t("url.placeholder")} - disabled={isPending} - required - /> - {/* URL 预览组件 - 实时显示端点拼接结果 */} - {url.trim() && } -
+ {/* 移除描述字段 */} -
- - setKey(e.target.value)} - placeholder={isEdit ? t("key.leaveEmptyDesc") : t("key.placeholder")} - disabled={isPending} - required={!isEdit} - /> - {isEdit && provider ? ( -
- {t("key.currentKey", { key: provider.maskedKey })} -
- ) : null} -
+
+ + setUrl(e.target.value)} + placeholder={t("url.placeholder")} + disabled={isPending} + required + /> + {/* URL 预览组件 - 实时显示端点拼接结果 */} + {url.trim() && } +
-
- - setWebsiteUrl(e.target.value)} - placeholder={t("websiteUrl.placeholder")} - disabled={isPending} - /> -
{t("websiteUrl.desc")}
-
+
+ + setKey(e.target.value)} + placeholder={isEdit ? t("key.leaveEmptyDesc") : t("key.placeholder")} + disabled={isPending} + required={!isEdit} + /> + {isEdit && provider ? ( +
+ {t("key.currentKey", { key: provider.maskedKey })} +
+ ) : null} +
- {/* 展开/折叠全部按钮 */} -
- - -
+
+ + setWebsiteUrl(e.target.value)} + placeholder={t("websiteUrl.placeholder")} + disabled={isPending} + /> +
{t("websiteUrl.desc")}
+
- {/* Codex 支持:供应商类型和模型重定向 */} - toggleSection("routing")}> - - - - -
-
- - -

- {t("sections.routing.providerTypeDesc")} - {!enableMultiProviderTypes && ( - - {t("sections.routing.providerTypeDisabledNote")} - - )} -

-
+ {t("buttons.expandAll")} + + +
-
-
-
- -

- {t("sections.routing.preserveClientIp.desc")} -

-
- toggleSection("routing")}> + +
- -
- - -
- - {/* joinClaudePool 开关 - 仅非 Claude 供应商显示 */} - {providerType !== "claude" && - (() => { - // 检查是否有重定向到 Claude 模型的映射 - const hasClaudeRedirects = Object.values(modelRedirects).some((target) => - target.startsWith("claude-") - ); - - if (!hasClaudeRedirects) return null; - - return ( -
-
-
- -

- {t("sections.routing.joinClaudePool.desc")} -

-
- -
-

- {t("sections.routing.joinClaudePool.help")} -

-
- ); - })()} - - {/* 模型白名单配置 */} -
-
- {t("sections.routing.modelWhitelist.title")} -
-

- {t("sections.routing.modelWhitelist.desc")} -

-
- -
- - - - - {allowedModels.length > 0 && ( -
- {allowedModels.slice(0, 5).map((model) => ( - - {model} - - ))} - {allowedModels.length > 5 && ( - - {t("sections.routing.modelWhitelist.moreModels", { - count: allowedModels.length - 5, - })} - - )} -
- )} - -

- {allowedModels.length === 0 ? ( - - {t("sections.routing.modelWhitelist.allowAll")} - - ) : ( - - {t("sections.routing.modelWhitelist.selectedOnly", { - count: allowedModels.length, - })} - - )} -

-
- - {/* 路由配置 - 优先级、权重、成本 */} + + {(() => { + const parts = []; + if (allowedModels.length > 0) + parts.push( + t("sections.routing.summary.models", { + count: allowedModels.length, + }) + ); + if (Object.keys(modelRedirects).length > 0) + parts.push( + t("sections.routing.summary.redirects", { + count: Object.keys(modelRedirects).length, + }) + ); + return parts.length > 0 ? parts.join(", ") : t("sections.routing.summary.none"); + })()} + + + +
-
- {t("sections.routing.scheduleParams.title")} -
-
-
- - setPriority(parseInt(e.target.value, 10) || 0)} - placeholder={t("sections.routing.scheduleParams.priority.placeholder")} - disabled={isPending} - min="0" - step="1" - /> -

- {t("sections.routing.scheduleParams.priority.desc")} -

-
-
- - setWeight(parseInt(e.target.value, 10) || 1)} - placeholder={t("sections.routing.scheduleParams.weight.placeholder")} - disabled={isPending} - min="1" - step="1" - /> -

- {t("sections.routing.scheduleParams.weight.desc")} -

-
-
- - { - const value = e.target.value; - if (value === "") { - setCostMultiplier(1.0); - return; - } - const num = parseFloat(value); - setCostMultiplier(Number.isNaN(num) ? 1.0 : num); - }} - onFocus={(e) => e.target.select()} - placeholder={t("sections.routing.scheduleParams.costMultiplier.placeholder")} - disabled={isPending} - min="0" - step="0.0001" - /> -

- {t("sections.routing.scheduleParams.costMultiplier.desc")} -

-
+
+ + +

+ {t("sections.routing.providerTypeDesc")} + {!enableMultiProviderTypes && ( + + {t("sections.routing.providerTypeDisabledNote")} + + )} +

+
+
- - + /> +
+ + {/* joinClaudePool 开关 - 仅非 Claude 供应商显示 */} + {providerType !== "claude" && + (() => { + // 检查是否有重定向到 Claude 模型的映射 + const hasClaudeRedirects = Object.values(modelRedirects).some((target) => + target.startsWith("claude-") + ); + + if (!hasClaudeRedirects) return null; + + return ( +
+
+
+ +

+ {t("sections.routing.joinClaudePool.desc")} +

+
+ +
+

+ {t("sections.routing.joinClaudePool.help")} +

+
+ ); + })()} + + {/* 模型白名单配置 */} +
+
+ {t("sections.routing.modelWhitelist.title")} +
+

+ {t("sections.routing.modelWhitelist.desc")} +

+
+ +
+ + + + + {allowedModels.length > 0 && ( +
+ {allowedModels.slice(0, 5).map((model) => ( + + {model} + + ))} + {allowedModels.length > 5 && ( + + {t("sections.routing.modelWhitelist.moreModels", { + count: allowedModels.length - 5, + })} + + )} +
+ )} +

- 强制设置 prompt cache TTL;仅影响包含 cache_control 的请求。 + {allowedModels.length === 0 ? ( + + {t("sections.routing.modelWhitelist.allowAll")} + + ) : ( + + {t("sections.routing.modelWhitelist.selectedOnly", { + count: allowedModels.length, + })} + + )}

- {/* 1M Context Window 配置 - 仅 Anthropic 类型供应商显示 */} - {(providerType === "claude" || providerType === "claude-auth") && ( + {/* 路由配置 - 优先级、权重、成本 */} +
+
+ {t("sections.routing.scheduleParams.title")} +
+
+
+ + setPriority(parseInt(e.target.value, 10) || 0)} + placeholder={t("sections.routing.scheduleParams.priority.placeholder")} + disabled={isPending} + min="0" + step="1" + /> +

+ {t("sections.routing.scheduleParams.priority.desc")} +

+
+
+ + setWeight(parseInt(e.target.value, 10) || 1)} + placeholder={t("sections.routing.scheduleParams.weight.placeholder")} + disabled={isPending} + min="1" + step="1" + /> +

+ {t("sections.routing.scheduleParams.weight.desc")} +

+
+
+ + { + const value = e.target.value; + if (value === "") { + setCostMultiplier(1.0); + return; + } + const num = parseFloat(value); + setCostMultiplier(Number.isNaN(num) ? 1.0 : num); + }} + onFocus={(e) => e.target.select()} + placeholder={t( + "sections.routing.scheduleParams.costMultiplier.placeholder" + )} + disabled={isPending} + min="0" + step="0.0001" + /> +

+ {t("sections.routing.scheduleParams.costMultiplier.desc")} +

+
+
- +

- {t("sections.routing.context1m.desc")} + {t("sections.routing.cacheTtl.desc")}

- )} -
-
- - - {/* 限流配置 */} - toggleSection("rateLimit")}> - - - - -
-
-
- - setLimit5hUsd(validateNumericField(e.target.value))} - placeholder={t("sections.rateLimit.limit5h.placeholder")} - disabled={isPending} - min="0" - step="0.01" - /> -
-
- - setLimitDailyUsd(validateNumericField(e.target.value))} - placeholder={t("sections.rateLimit.limitDaily.placeholder")} - disabled={isPending} - min="0" - step="0.01" - /> + {/* 1M Context Window 配置 - 仅 Anthropic 类型供应商显示 */} + {(providerType === "claude" || providerType === "claude-auth") && ( +
+ + +

+ {t("sections.routing.context1m.desc")} +

+
+ )}
+ + -
-
- - -

- {dailyResetMode === "fixed" - ? t("sections.rateLimit.dailyResetMode.desc.fixed") - : t("sections.rateLimit.dailyResetMode.desc.rolling")} -

+ {/* 限流配置 */} + toggleSection("rateLimit")} + > + + + + +
+
-
- )} -
+
+ + setLimitDailyUsd(validateNumericField(e.target.value))} + placeholder={t("sections.rateLimit.limitDaily.placeholder")} + disabled={isPending} + min="0" + step="0.01" + /> +
+
-
-
- - setLimitWeeklyUsd(validateNumericField(e.target.value))} - placeholder={t("sections.rateLimit.limitWeekly.placeholder")} - disabled={isPending} - min="0" - step="0.01" - /> +
+
+ + +

+ {dailyResetMode === "fixed" + ? t("sections.rateLimit.dailyResetMode.desc.fixed") + : t("sections.rateLimit.dailyResetMode.desc.rolling")} +

+
+ {dailyResetMode === "fixed" && ( +
+ + setDailyResetTime(e.target.value || "00:00")} + placeholder="00:00" + disabled={isPending} + step="60" + /> +
+ )} +
+ +
+
+ + setLimitWeeklyUsd(validateNumericField(e.target.value))} + placeholder={t("sections.rateLimit.limitWeekly.placeholder")} + disabled={isPending} + min="0" + step="0.01" + /> +
+
+ +
+
+ + setLimitMonthlyUsd(validateNumericField(e.target.value))} + placeholder={t("sections.rateLimit.limitMonthly.placeholder")} + disabled={isPending} + min="0" + step="0.01" + /> +
+
+ + + setLimitConcurrentSessions(validateNumericField(e.target.value)) + } + placeholder={t("sections.rateLimit.limitConcurrent.placeholder")} + disabled={isPending} + min="0" + step="1" + /> +
+ + -
-
- - setLimitMonthlyUsd(validateNumericField(e.target.value))} - placeholder={t("sections.rateLimit.limitMonthly.placeholder")} - disabled={isPending} - min="0" - step="0.01" + {/* 熔断器配置 */} + toggleSection("circuitBreaker")} + > + + + + +
+
+

+ {t("sections.circuitBreaker.desc")} +

+
+
+
+ + { + const val = e.target.value; + setFailureThreshold(val === "" ? undefined : parseInt(val, 10)); + }} + placeholder={t("sections.circuitBreaker.failureThreshold.placeholder")} + disabled={isPending} + min="0" + step="1" + /> +

+ {t("sections.circuitBreaker.failureThreshold.desc")} +

+
+
+ + { + const val = e.target.value; + setOpenDurationMinutes(val === "" ? undefined : parseInt(val, 10)); + }} + placeholder={t("sections.circuitBreaker.openDuration.placeholder")} + disabled={isPending} + min="1" + max="1440" + step="1" + /> +

+ {t("sections.circuitBreaker.openDuration.desc")} +

+
+
+ + { + const val = e.target.value; + setHalfOpenSuccessThreshold(val === "" ? undefined : parseInt(val, 10)); + }} + placeholder={t("sections.circuitBreaker.successThreshold.placeholder")} + disabled={isPending} + min="1" + max="10" + step="1" + /> +

+ {t("sections.circuitBreaker.successThreshold.desc")} +

+
+
+ + { + const val = e.target.value; + setMaxRetryAttempts(val === "" ? null : parseInt(val, 10)); + }} + placeholder={t("sections.circuitBreaker.maxRetryAttempts.placeholder")} + disabled={isPending} + min="1" + max="10" + step="1" + /> +

+ {t("sections.circuitBreaker.maxRetryAttempts.desc")} +

+
-
- - - - {/* 熔断器配置 */} - toggleSection("circuitBreaker")} - > - - - - -
-
-

{t("sections.circuitBreaker.desc")}

+ + + + {/* 超时配置 */} + toggleSection("timeout")}> + + + + +
+
+

{t("sections.timeout.desc")}

+
+
+
+ + { + const val = e.target.value; + setFirstByteTimeoutStreamingSeconds( + val === "" ? undefined : parseInt(val, 10) + ); + }} + placeholder={t("sections.timeout.streamingFirstByte.placeholder")} + disabled={isPending} + min="0" + max="180" + step="1" + className="border-orange-200 focus:border-orange-500 focus:ring-orange-500" + /> +

+ {t("sections.timeout.streamingFirstByte.desc")} +

+
+
+ + { + const val = e.target.value; + setStreamingIdleTimeoutSeconds(val === "" ? undefined : parseInt(val, 10)); + }} + placeholder={t("sections.timeout.streamingIdle.placeholder")} + disabled={isPending} + min="0" + max="600" + step="1" + className="border-orange-200 focus:border-orange-500 focus:ring-orange-500" + /> +

+ {t("sections.timeout.streamingIdle.desc")} +

+
+
+ + { + const val = e.target.value; + setRequestTimeoutNonStreamingSeconds( + val === "" ? undefined : parseInt(val, 10) + ); + }} + placeholder={t("sections.timeout.nonStreamingTotal.placeholder")} + disabled={isPending} + min="0" + max="1800" + step="1" + /> +

+ {t("sections.timeout.nonStreamingTotal.desc")} +

+
+
+

+ {t("sections.timeout.disableHint")} +

-
-
- - { - const val = e.target.value; - setFailureThreshold(val === "" ? undefined : parseInt(val, 10)); - }} - placeholder={t("sections.circuitBreaker.failureThreshold.placeholder")} - disabled={isPending} - min="0" - step="1" + + + + {/* 代理配置 */} + toggleSection("proxy")}> + + + + +
+
+

{t("sections.proxy.desc")}

+
+ + {/* 代理地址输入 */}
-
+ + {/* 降级策略开关 */}
- - { - const val = e.target.value; - setHalfOpenSuccessThreshold(val === "" ? undefined : parseInt(val, 10)); - }} - placeholder={t("sections.circuitBreaker.successThreshold.placeholder")} - disabled={isPending} - min="1" - max="10" - step="1" - /> -

- {t("sections.circuitBreaker.successThreshold.desc")} -

+
+
+ +

+ {t("sections.proxy.fallback.desc")} +

+
+ +
+ + {/* 测试连接按钮 */}
- - { - const val = e.target.value; - setMaxRetryAttempts(val === "" ? null : parseInt(val, 10)); - }} - placeholder={t("sections.circuitBreaker.maxRetryAttempts.placeholder")} - disabled={isPending} - min="1" - max="10" - step="1" + + -

- {t("sections.circuitBreaker.maxRetryAttempts.desc")} -

+

{t("sections.proxy.test.desc")}

-
- - + + + + {/* API 测试 */} + toggleSection("apiTest")}> + + + + +
+
+

{t("sections.apiTest.desc")}

+
- {/* 超时配置 */} - toggleSection("timeout")}> - - - - -
-
-

{t("sections.timeout.desc")}

-
-
- - { - const val = e.target.value; - setFirstByteTimeoutStreamingSeconds( - val === "" ? undefined : parseInt(val, 10) - ); - }} - placeholder={t("sections.timeout.streamingFirstByte.placeholder")} - disabled={isPending} - min="0" - max="180" - step="1" - className="border-orange-200 focus:border-orange-500 focus:ring-orange-500" + -

- {t("sections.timeout.streamingFirstByte.desc")} -

-
- - { - const val = e.target.value; - setStreamingIdleTimeoutSeconds(val === "" ? undefined : parseInt(val, 10)); - }} - placeholder={t("sections.timeout.streamingIdle.placeholder")} - disabled={isPending} - min="0" - max="600" - step="1" - className="border-orange-200 focus:border-orange-500 focus:ring-orange-500" +
+ + + + {/* MCP 透传配置 */} + toggleSection("mcpPassthrough")} + > + + + + +
+

- {t("sections.timeout.streamingIdle.desc")} + {t("sections.mcpPassthrough.desc")}

+
-
-
-

- {t("sections.timeout.disableHint")} -

-
- - - - {/* 代理配置 */} - toggleSection("proxy")}> - - - - -
-
-

{t("sections.proxy.desc")}

-
- {/* 代理地址输入 */} -
- - setProxyUrl(e.target.value)} - placeholder={t("sections.proxy.url.placeholder")} - disabled={isPending} - /> -

- {t("sections.proxy.url.formats")}{" "} - http://、 - https://、 - socks4://、 - socks5:// -

-
- - {/* 降级策略开关 */} -
-
-
-
-
- - {/* 测试连接按钮 */} -
- - -

{t("sections.proxy.test.desc")}

-
-
- - - - {/* API 测试 */} - toggleSection("apiTest")}> - - - - -
-
-

{t("sections.apiTest.desc")}

-
- -
- -
-
-
-
- - {/* MCP 透传配置 */} - toggleSection("mcpPassthrough")} - > - - - - -
-
-

{t("sections.mcpPassthrough.desc")}

+ )}
+ + -
- - -

{t("sections.mcpPassthrough.hint")}

-
+ {t("failureThresholdConfirmDialog.confirm")} + + + + +
- {/* MCP 透传 URL 配置 */} - {mcpPassthroughType !== "none" && ( -
- - setMcpPassthroughUrl(e.target.value)} - placeholder={t("sections.mcpPassthrough.urlPlaceholder")} - disabled={isPending} - /> -

- {t("sections.mcpPassthrough.urlDesc")} -

- {!mcpPassthroughUrl && url && ( -

- {t("sections.mcpPassthrough.urlAuto", { - url: extractBaseUrl(url), + + {isEdit ? ( +

+ + + + + + + {t("deleteDialog.title")} + + {t("deleteDialog.description", { + name: provider?.name ?? "", })} -

- )} -
- )} -
-
-
- - {/* failureThreshold 特殊值确认对话框 */} - - - - {t("failureThresholdConfirmDialog.title")} - -
- {failureThreshold === 0 ? ( -

- {t("failureThresholdConfirmDialog.descriptionDisabledPrefix")} - {t("failureThresholdConfirmDialog.descriptionDisabledValue")} - {t("failureThresholdConfirmDialog.descriptionDisabledMiddle")} - - {t("failureThresholdConfirmDialog.descriptionDisabledAction")} - - {t("failureThresholdConfirmDialog.descriptionDisabledSuffix")} -

- ) : ( -

- {t("failureThresholdConfirmDialog.descriptionHighValuePrefix")} - {failureThreshold} - {t("failureThresholdConfirmDialog.descriptionHighValueSuffix")} -

- )} -

{t("failureThresholdConfirmDialog.confirmQuestion")}

-
-
-
- - {t("failureThresholdConfirmDialog.cancel")} - { - setShowFailureThresholdConfirm(false); - performSubmit(); - }} - > - {t("failureThresholdConfirmDialog.confirm")} - - -
-
- - {isEdit ? ( -
- - - - - - - {t("deleteDialog.title")} - - {t("deleteDialog.description", { - name: provider?.name ?? "", - })} - - - - {t("deleteDialog.cancel")} - { - if (!provider) return; - startTransition(async () => { - try { - const res = await removeProvider(provider.id); - if (!res.ok) { - toast.error(res.error || t("errors.deleteFailed")); - return; + + + + {t("deleteDialog.cancel")} + { + if (!provider) return; + startTransition(async () => { + try { + const res = await removeProvider(provider.id); + if (!res.ok) { + toast.error(res.error || t("errors.deleteFailed")); + return; + } + onSuccess?.(); + } catch (e) { + console.error(t("errors.deleteFailed"), e); + toast.error(t("errors.deleteFailed")); } - onSuccess?.(); - } catch (e) { - console.error(t("errors.deleteFailed"), e); - toast.error(t("errors.deleteFailed")); - } - }); - }} - > - {t("deleteDialog.confirm")} - - - - - - -
- ) : ( -
- -
- )} + }); + }} + > + {t("deleteDialog.confirm")} + + + + + + +
+ ) : ( +
+ +
+ )} + -
+ ); } diff --git a/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx b/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx index 30aa6a0fd..8fb666d16 100644 --- a/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx +++ b/src/app/[locale]/settings/providers/_components/provider-rich-list-item.tsx @@ -607,7 +607,7 @@ export function ProviderRichListItem({ {/* 编辑 Dialog */} - + - + - -
- + + + {t("sensitiveWords.dialog.addTitle")} {t("sensitiveWords.dialog.addDescription")} -
+
- +