diff --git a/src/components/providers/forms/CodexFormFields.tsx b/src/components/providers/forms/CodexFormFields.tsx
index ff7c39d4e..4665bec57 100644
--- a/src/components/providers/forms/CodexFormFields.tsx
+++ b/src/components/providers/forms/CodexFormFields.tsx
@@ -232,6 +232,39 @@ const CODEX_REASONING_LEVELS = [
"ultra",
] as const;
+// Trigger label for the picked levels, kept narrow for the catalog cell. Each
+// run of three or more adjacent canonical levels collapses to "first → last"
+// ("none, low → max" when minimal is skipped); a range never spans a gap, so
+// it can't imply an unpicked level. Non-canonical order stays a plain list.
+export function formatReasoningLevelsLabel(levels: string[]): string {
+ const canonical = CODEX_REASONING_LEVELS as readonly string[];
+ const positions = levels.map((level) => canonical.indexOf(level));
+ const ordered = positions.every(
+ (position, index) =>
+ position >= 0 && (index === 0 || position > positions[index - 1]),
+ );
+ if (!ordered) return levels.join(", ");
+
+ const parts: string[] = [];
+ let runStart = 0;
+ for (let index = 1; index <= levels.length; index++) {
+ if (
+ index < levels.length &&
+ positions[index] === positions[index - 1] + 1
+ ) {
+ continue;
+ }
+ const run = levels.slice(runStart, index);
+ if (run.length >= 3) {
+ parts.push(`${run[0]} → ${run[run.length - 1]}`);
+ } else {
+ parts.push(...run);
+ }
+ runStart = index;
+ }
+ return parts.join(", ");
+}
+
// Sentinel for the default-level Select: Radix Select forbids empty item
// values, so "back to Auto" needs a non-empty value mapped to undefined.
const AUTO_DEFAULT_REASONING_LEVEL = "__auto__";
@@ -270,28 +303,47 @@ function ReasoningLevelsEditor({
const triggerLabel =
selected.length > 0
- ? selected.join(",")
+ ? formatReasoningLevelsLabel(selected)
: t("codexConfig.reasoningLevelsNotSet", {
defaultValue: "Not set",
});
+ // The label may be a range or truncated, so hovering spells out every
+ // picked level plus the default one.
+ const triggerTip =
+ selected.length > 0 ? (
+ <>
+
{selected.join(", ")}
+ {defaultLevel && (
+
+ {t("codexConfig.defaultReasoningLevelTip", {
+ level: defaultLevel,
+ defaultValue: "Default level: {{level}}",
+ })}
+
+ )}
+ >
+ ) : undefined;
+
return (
-
-
-
+
+ {triggerLabel}
+
+
+
+
+
{withDefault && }
@@ -1201,8 +1253,8 @@ export function CodexFormFields({
className={cn(
"grid grid-cols-1 gap-2",
withDefault
- ? "md:grid-cols-[36px_1fr_1fr_140px_1fr_36px]"
- : "md:grid-cols-[1fr_1fr_140px_1fr_36px]",
+ ? "md:grid-cols-[36px_minmax(0,1fr)_minmax(0,1fr)_140px_minmax(0,1fr)_36px]"
+ : "md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_140px_minmax(0,1fr)_36px]",
)}
>
{withDefault && renderDefaultStar(index)}
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index edc900639..f3b87e927 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -1824,6 +1824,7 @@
"reasoningLevelsEmpty": "No levels",
"defaultReasoningLevelLabel": "Default level",
"defaultReasoningLevelPlaceholder": "Auto",
+ "defaultReasoningLevelTip": "Default level: {{level}}",
"reasoningModeToggle": "Supports Thinking Mode",
"reasoningModeHint": "Enable when the upstream Chat Completions API supports toggling thinking on/off. Providers like Kimi, GLM and Qwen usually fall into this category.",
"reasoningEffortToggle": "Supports Reasoning Effort",
diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json
index 2d83dc3d7..534e72cc3 100644
--- a/src/i18n/locales/ja.json
+++ b/src/i18n/locales/ja.json
@@ -1824,6 +1824,7 @@
"reasoningLevelsEmpty": "レベルなし",
"defaultReasoningLevelLabel": "デフォルトレベル",
"defaultReasoningLevelPlaceholder": "自動",
+ "defaultReasoningLevelTip": "デフォルトレベル:{{level}}",
"reasoningModeToggle": "思考モードに対応",
"reasoningModeHint": "上流の Chat Completions API が思考(thinking)のオン/オフ切り替えに対応している場合に有効化します。Kimi、GLM、Qwen などが通常このタイプに該当します。",
"reasoningEffortToggle": "思考レベルに対応",
diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json
index 1671f1d0a..fb0eb4d9d 100644
--- a/src/i18n/locales/zh-TW.json
+++ b/src/i18n/locales/zh-TW.json
@@ -1817,6 +1817,7 @@
"reasoningLevelsEmpty": "無等級",
"defaultReasoningLevelLabel": "預設等級",
"defaultReasoningLevelPlaceholder": "自動",
+ "defaultReasoningLevelTip": "預設等級:{{level}}",
"catalogDisplayNamePlaceholder": "例如: DeepSeek V4 Flash",
"catalogModelPlaceholder": "例如: deepseek-v4-flash",
"contextWindow": "上下文視窗",
diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json
index bc50c9cfc..616e37e30 100644
--- a/src/i18n/locales/zh.json
+++ b/src/i18n/locales/zh.json
@@ -1824,6 +1824,7 @@
"reasoningLevelsEmpty": "无等级",
"defaultReasoningLevelLabel": "默认等级",
"defaultReasoningLevelPlaceholder": "自动",
+ "defaultReasoningLevelTip": "默认等级:{{level}}",
"reasoningModeToggle": "支持思考模式",
"reasoningModeHint": "上游 Chat Completions 接口支持开启或关闭 thinking 时启用。Kimi、GLM、Qwen 等通常属于这一类。",
"reasoningEffortToggle": "支持思考等级",
diff --git a/tests/components/codexReasoningLevelsLabel.test.ts b/tests/components/codexReasoningLevelsLabel.test.ts
new file mode 100644
index 000000000..0fcf178a2
--- /dev/null
+++ b/tests/components/codexReasoningLevelsLabel.test.ts
@@ -0,0 +1,44 @@
+import { describe, expect, it } from "vitest";
+import { formatReasoningLevelsLabel } from "@/components/providers/forms/CodexFormFields";
+
+describe("formatReasoningLevelsLabel", () => {
+ it("collapses a run of three or more adjacent levels into a range", () => {
+ expect(
+ formatReasoningLevelsLabel(["low", "medium", "high", "xhigh", "max"]),
+ ).toBe("low → max");
+ });
+
+ it("collapses each run separately, so a skipped level stays visible", () => {
+ // minimal sits between none and low: none must not join the low run.
+ expect(
+ formatReasoningLevelsLabel([
+ "none",
+ "low",
+ "medium",
+ "high",
+ "xhigh",
+ "max",
+ ]),
+ ).toBe("none, low → max");
+ expect(formatReasoningLevelsLabel(["none", "low", "medium", "high"])).toBe(
+ "none, low → high",
+ );
+ });
+
+ it("lists short runs and gapped picks as they are", () => {
+ expect(formatReasoningLevelsLabel(["high"])).toBe("high");
+ expect(formatReasoningLevelsLabel(["none", "high"])).toBe("none, high");
+ expect(formatReasoningLevelsLabel(["low", "high", "max"])).toBe(
+ "low, high, max",
+ );
+ expect(formatReasoningLevelsLabel(["low", "medium", "xhigh"])).toBe(
+ "low, medium, xhigh",
+ );
+ });
+
+ it("keeps a non-canonical order as a plain list", () => {
+ expect(formatReasoningLevelsLabel(["high", "medium", "low"])).toBe(
+ "high, medium, low",
+ );
+ });
+});