Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions messages/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@
"create": "Add Provider",
"edit": "Edit Provider"
},
"dialogDescription": "Configure provider details and advanced settings.",
"url": {
"label": "API Address *",
"placeholder": "e.g. https://open.bigmodel.cn/api/anthropic"
Expand Down Expand Up @@ -1470,6 +1471,7 @@
"errors": {
"invalidUrl": "Please enter a valid API address",
"invalidWebsiteUrl": "Please enter a valid provider website URL",
"groupTagTooLong": "Provider group tags are too long (max {max} chars total)",
"addFailed": "Failed to add provider",
"updateFailed": "Failed to update provider",
"deleteFailed": "Failed to delete provider"
Expand Down
2 changes: 2 additions & 0 deletions messages/ja/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,7 @@
"create": "プロバイダーを追加",
"edit": "プロバイダーを編集"
},
"dialogDescription": "プロバイダーの詳細と高度な設定を構成します。",
"url": {
"label": "API アドレス *",
"placeholder": "例: https://open.bigmodel.cn/api/anthropic"
Expand Down Expand Up @@ -1340,6 +1341,7 @@
"errors": {
"invalidUrl": "有効な API アドレスを入力してください",
"invalidWebsiteUrl": "有効な公式サイト URL を入力してください",
"groupTagTooLong": "プロバイダーグループが長すぎます(合計{max}文字まで)",
"addFailed": "プロバイダーの追加に失敗しました",
"updateFailed": "プロバイダーの更新に失敗しました",
"deleteFailed": "プロバイダーの削除に失敗しました"
Expand Down
2 changes: 2 additions & 0 deletions messages/ru/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,7 @@
"create": "Добавить провайдера",
"edit": "Редактировать провайдера"
},
"dialogDescription": "Настройте детали провайдера и расширенные параметры.",
"url": {
"label": "Адрес API *",
"placeholder": "например: https://open.bigmodel.cn/api/anthropic"
Expand Down Expand Up @@ -1340,6 +1341,7 @@
"errors": {
"invalidUrl": "Введите корректный адрес API",
"invalidWebsiteUrl": "Введите корректный адрес сайта провайдера",
"groupTagTooLong": "Список групп провайдера слишком длинный (макс. {max} символов всего)",
"addFailed": "Не удалось добавить провайдера",
"updateFailed": "Не удалось обновить провайдера",
"deleteFailed": "Не удалось удалить провайдера"
Expand Down
2 changes: 2 additions & 0 deletions messages/zh-CN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@
"create": "新增服务商",
"edit": "编辑服务商"
},
"dialogDescription": "配置供应商信息及高级设置。",
"url": {
"label": "API 地址 *",
"placeholder": "例如: https://open.bigmodel.cn/api/anthropic"
Expand Down Expand Up @@ -979,6 +980,7 @@
"errors": {
"invalidUrl": "请输入有效的 API 地址",
"invalidWebsiteUrl": "请输入有效的供应商官网地址",
"groupTagTooLong": "分组标签总长度不能超过 {max} 个字符",
"addFailed": "添加服务商失败",
"updateFailed": "更新服务商失败",
"deleteFailed": "删除服务商失败"
Expand Down
2 changes: 2 additions & 0 deletions messages/zh-TW/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,7 @@
"create": "新增供應商",
"edit": "編輯供應商"
},
"dialogDescription": "設定供應商資訊與進階設定。",
"url": {
"label": "API 位址 *",
"placeholder": "例如:https://open.bigmodel.cn/api/anthropic"
Expand Down Expand Up @@ -1346,6 +1347,7 @@
"errors": {
"invalidUrl": "請輸入有效的 API 位址",
"invalidWebsiteUrl": "請輸入有效的供應商官網",
"groupTagTooLong": "分組標籤總長度不能超過 {max} 個字元",
"addFailed": "新增供應商失敗",
"updateFailed": "更新供應商失敗",
"deleteFailed": "刪除供應商失敗"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,7 @@ export function UserManagementTable({

useEffect(() => {
if (!scrollResetKey) return;
parentRef.current?.scrollTo({ top: 0 });
// Defer measurement to next frame to ensure DOM has updated
const rafId = requestAnimationFrame(() => {
rowVirtualizer.measure();
});
return () => cancelAnimationFrame(rafId);
rowVirtualizer.scrollToOffset(0);
}, [scrollResetKey, rowVirtualizer]);

const quickRenewTranslations = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,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 { DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Expand All @@ -41,6 +41,8 @@ import { ApiTestButton } from "./api-test-button";
import { ProxyTestButton } from "./proxy-test-button";
import { UrlPreview } from "./url-preview";

const GROUP_TAG_MAX_TOTAL_LENGTH = 50;

type Mode = "create" | "edit";

interface ProviderFormProps {
Expand Down Expand Up @@ -295,6 +297,14 @@ export function ProviderForm({
return;
}

// group_tag 在 DB/schema 中限制为 varchar(50),并且后端按整串校验 max(50)
// 这里限制逗号拼接后的总长度,避免“UI 看似可选多标签,但保存必失败”的体验
const serializedGroupTag = groupTag.join(",");
if (serializedGroupTag.length > GROUP_TAG_MAX_TOTAL_LENGTH) {
toast.error(t("errors.groupTagTooLong", { max: GROUP_TAG_MAX_TOTAL_LENGTH }));
return;
}
Comment on lines +300 to +306
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This validation for the total length of groupTag is redundant. The handleGroupTagChange function, which is called on every change to the tags, already performs this validation and prevents the state from being updated if the length is invalid. Removing this duplicated check from handleSubmit will make the code cleaner and rely on a single source of truth for this validation.


// 检查 failureThreshold 是否为特殊值(0 或大于 100)
const threshold = failureThreshold ?? 5;
if (threshold === 0 || threshold > 100) {
Expand All @@ -306,6 +316,15 @@ export function ProviderForm({
performSubmit();
};

const handleGroupTagChange = (nextTags: string[]) => {
const serialized = nextTags.join(",");
if (serialized.length > GROUP_TAG_MAX_TOTAL_LENGTH) {
toast.error(t("errors.groupTagTooLong", { max: GROUP_TAG_MAX_TOTAL_LENGTH }));
return;
}
setGroupTag(nextTags);
};

// 实际提交逻辑
const performSubmit = () => {
// 处理模型重定向(空对象转为 null)
Expand Down Expand Up @@ -519,6 +538,7 @@ export function ProviderForm({
<>
<DialogHeader className="flex-shrink-0">
<DialogTitle>{isEdit ? t("title.edit") : t("title.create")}</DialogTitle>
<DialogDescription className="sr-only">{t("dialogDescription")}</DialogDescription>
</DialogHeader>

<form onSubmit={handleSubmit} className="flex flex-col flex-1 min-h-0">
Expand Down Expand Up @@ -692,16 +712,16 @@ export function ProviderForm({
<TagInput
id={isEdit ? "edit-group" : "group"}
value={groupTag}
onChange={setGroupTag}
onChange={handleGroupTagChange}
placeholder={t("sections.routing.scheduleParams.group.placeholder")}
disabled={isPending}
maxTagLength={50}
maxTagLength={GROUP_TAG_MAX_TOTAL_LENGTH}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the provider-form.tsx file and relevant constants/components
find . -name "provider-form.tsx" -type f | head -5

Repository: ding113/claude-code-hub

Length of output: 140


🏁 Script executed:

# Search for GROUP_TAG_MAX_TOTAL_LENGTH constant definition
rg -n "GROUP_TAG_MAX_TOTAL_LENGTH" --type=ts --type=tsx -B 2 -A 2

Repository: ding113/claude-code-hub

Length of output: 94


🏁 Script executed:

# Find TagInput component
fd -name "*tag-input*" -type f

Repository: ding113/claude-code-hub

Length of output: 297


🏁 Script executed:

# Search for GROUP_TAG_MAX_TOTAL_LENGTH constant definition
rg "GROUP_TAG_MAX_TOTAL_LENGTH" -B 2 -A 2

Repository: ding113/claude-code-hub

Length of output: 3269


🏁 Script executed:

# Find TagInput component
fd "tag-input" -type f

Repository: ding113/claude-code-hub

Length of output: 237


🏁 Script executed:

# Check the provider-form.tsx file for imports and relevant code around line 718
head -750 ./src/app/[locale]/settings/providers/_components/forms/provider-form.tsx | tail -100

Repository: ding113/claude-code-hub

Length of output: 5234


🏁 Script executed:

# Find TagInput component
find . -name "*" -type f | xargs grep -l "export.*TagInput" | head -10

Repository: ding113/claude-code-hub

Length of output: 137


🏁 Script executed:

# Search for TagInput prop definition or component
rg "maxTagLength" -B 5 -A 5 --max-count=20

Repository: ding113/claude-code-hub

Length of output: 17009


🏁 Script executed:

# Look for TagInput component more broadly
rg "component.*TagInput|export.*function.*TagInput|const.*TagInput.*=" -A 20

Repository: ding113/claude-code-hub

Length of output: 5866


🏁 Script executed:

# Get the complete TagInput component implementation
cat ./src/components/ui/tag-input.tsx | head -200

Repository: ding113/claude-code-hub

Length of output: 6074


🏁 Script executed:

# Check how handleGroupTagChange is implemented in context
rg -B 10 "const handleGroupTagChange" src/app/[locale]/settings/providers/_components/forms/provider-form.tsx

Repository: ding113/claude-code-hub

Length of output: 318


纠正 maxTagLength 属性的语义用途。

maxTagLength 属性在 TagInput 组件中用于验证单个标签的长度(见 validateBaseTagif (tag.length > maxTagLength)),但此处被设置为 GROUP_TAG_MAX_TOTAL_LENGTH(用于限制所有标签逗号拼接后的总长度)。

这会导致语义混乱:maxTagLength={50} 表示每个单个标签可长达 50 字符,而不是所有标签的总长度为 50。如果 GROUP_TAG_MAX_TOTAL_LENGTH 在未来被修改(例如改为 200),则单个标签验证和总长度验证的含义将完全不符。

虽然 handleGroupTagChange 作为安全网能防止问题发生,但建议为 maxTagLength 设置更合理的值(如 25-30),使其在语义上与 GROUP_TAG_MAX_TOTAL_LENGTH 的限制条件相适配。

🤖 Prompt for AI Agents
In src/app/[locale]/settings/providers/_components/forms/provider-form.tsx
around line 718, maxTagLength is incorrectly set to GROUP_TAG_MAX_TOTAL_LENGTH
(which represents the total length of all tags combined) but TagInput uses
maxTagLength to validate each individual tag; change the prop to a sensible
per-tag limit (e.g. 25–30) by introducing or using a per-tag constant like
GROUP_SINGLE_TAG_MAX_LENGTH (or a literal such as 30) and keep
GROUP_TAG_MAX_TOTAL_LENGTH for the combined-length checks in
handleGroupTagChange so single-tag validation semantics and total-length
validation remain consistent.

suggestions={groupSuggestions}
onInvalidTag={(_tag, reason) => {
const messages: Record<string, string> = {
empty: tUI("emptyTag"),
duplicate: tUI("duplicateTag"),
too_long: tUI("tooLong", { max: 50 }),
too_long: tUI("tooLong", { max: GROUP_TAG_MAX_TOTAL_LENGTH }),
invalid_format: tUI("invalidFormat"),
max_tags: tUI("maxTags"),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export function UrlPreview({ baseUrl, providerType }: UrlPreviewProps) {
}

try {
return previewProxyUrls(baseUrl, providerType);
const result = previewProxyUrls(baseUrl, providerType);
return Object.keys(result).length > 0 ? result : null;
} catch {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import { AlertTriangle, Loader2, Search, X } from "lucide-react";
import { useTranslations } from "next-intl";
import { type ReactNode, useMemo, useState } from "react";
import { type ReactNode, useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
Expand Down Expand Up @@ -70,6 +70,13 @@ export function ProviderManager({
return providers.filter((p) => healthStatus[p.id]?.circuitState === "open").length;
}, [providers, healthStatus]);

// Auto-reset circuit broken filter when no providers are broken
useEffect(() => {
if (circuitBrokenCount === 0 && circuitBrokenFilter) {
setCircuitBrokenFilter(false);
}
}, [circuitBrokenCount, circuitBrokenFilter]);

// Extract unique groups from all providers
const allGroups = useMemo(() => {
const groups = new Set<string>();
Expand Down
6 changes: 5 additions & 1 deletion src/components/ui/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ function ChartContainer({
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer width="100%" height="100%">
<RechartsPrimitive.ResponsiveContainer
width="100%"
height="100%"
initialDimension={{ width: 0, height: 1 }}
>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
Expand Down
Loading