icebear0828 Claude Opus 4.6 commited on
Commit
c52388f
Β·
1 Parent(s): 794d451

fix: collapsible model dropdown, default to gpt-5.4, remove legacy gpt-5

Browse files

- Model selector now collapses after selection (click-outside close)
- Default model changed from gpt-5.2-codex to gpt-5.4
- Removed deprecated gpt-5, gpt-5-codex, gpt-5-codex-mini

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

config/default.yaml CHANGED
@@ -9,7 +9,7 @@ client:
9
  arch: arm64
10
  chromium_version: "144"
11
  model:
12
- default: gpt-5.2-codex
13
  default_reasoning_effort: medium
14
  suppress_desktop_directives: true
15
  auth:
 
9
  arch: arm64
10
  chromium_version: "144"
11
  model:
12
+ default: gpt-5.4
13
  default_reasoning_effort: medium
14
  suppress_desktop_directives: true
15
  auth:
config/models.yaml CHANGED
@@ -11,7 +11,7 @@ models:
11
  - id: gpt-5.4
12
  displayName: GPT-5.4
13
  description: Latest Codex flagship model
14
- isDefault: false
15
  supportedReasoningEfforts:
16
  - { reasoningEffort: minimal, description: "Minimal reasoning" }
17
  - { reasoningEffort: low, description: "Fastest responses" }
@@ -110,7 +110,7 @@ models:
110
  - id: gpt-5.2-codex
111
  displayName: GPT-5.2 Codex
112
  description: GPT-5.2 Codex flagship
113
- isDefault: true
114
  supportedReasoningEfforts:
115
  - { reasoningEffort: low, description: "Fastest responses" }
116
  - { reasoningEffort: medium, description: "Balanced speed and quality" }
@@ -250,29 +250,6 @@ models:
250
  supportsPersonality: false
251
  upgrade: null
252
 
253
- # ── GPT-5 Codex (legacy) ──────────────────────────────────────────
254
- - id: gpt-5-codex
255
- displayName: GPT-5 Codex
256
- description: Original GPT-5 Codex
257
- isDefault: false
258
- supportedReasoningEfforts:
259
- - { reasoningEffort: medium, description: "Default" }
260
- defaultReasoningEffort: medium
261
- inputModalities: [text]
262
- supportsPersonality: false
263
- upgrade: null
264
-
265
- - id: gpt-5-codex-mini
266
- displayName: GPT-5 Codex Mini
267
- description: Original lightweight Codex
268
- isDefault: false
269
- supportedReasoningEfforts:
270
- - { reasoningEffort: medium, description: "Default" }
271
- defaultReasoningEffort: medium
272
- inputModalities: [text]
273
- supportsPersonality: false
274
- upgrade: null
275
-
276
  # ── Base GPT models (also usable via Codex endpoint) ───────────────
277
  - id: gpt-5.3
278
  displayName: GPT-5.3
@@ -313,16 +290,5 @@ models:
313
  supportsPersonality: true
314
  upgrade: null
315
 
316
- - id: gpt-5
317
- displayName: GPT-5
318
- description: General-purpose GPT-5
319
- isDefault: false
320
- supportedReasoningEfforts:
321
- - { reasoningEffort: medium, description: "Default" }
322
- defaultReasoningEffort: medium
323
- inputModalities: [text, image]
324
- supportsPersonality: true
325
- upgrade: null
326
-
327
  aliases:
328
  codex: "gpt-5.4"
 
11
  - id: gpt-5.4
12
  displayName: GPT-5.4
13
  description: Latest Codex flagship model
14
+ isDefault: true
15
  supportedReasoningEfforts:
16
  - { reasoningEffort: minimal, description: "Minimal reasoning" }
17
  - { reasoningEffort: low, description: "Fastest responses" }
 
110
  - id: gpt-5.2-codex
111
  displayName: GPT-5.2 Codex
112
  description: GPT-5.2 Codex flagship
113
+ isDefault: false
114
  supportedReasoningEfforts:
115
  - { reasoningEffort: low, description: "Fastest responses" }
116
  - { reasoningEffort: medium, description: "Balanced speed and quality" }
 
250
  supportsPersonality: false
251
  upgrade: null
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  # ── Base GPT models (also usable via Codex endpoint) ───────────────
254
  - id: gpt-5.3
255
  displayName: GPT-5.3
 
290
  supportsPersonality: true
291
  upgrade: null
292
 
 
 
 
 
 
 
 
 
 
 
 
293
  aliases:
294
  codex: "gpt-5.4"
shared/hooks/use-status.ts CHANGED
@@ -44,8 +44,8 @@ function isTierVariant(id: string): boolean {
44
  export function useStatus(accountCount: number) {
45
  const [baseUrl, setBaseUrl] = useState("Loading...");
46
  const [apiKey, setApiKey] = useState("Loading...");
47
- const [models, setModels] = useState<string[]>(["codex"]);
48
- const [selectedModel, setSelectedModel] = useState("codex");
49
  const [modelCatalog, setModelCatalog] = useState<CatalogModel[]>([]);
50
  const [selectedEffort, setSelectedEffort] = useState("medium");
51
 
@@ -62,11 +62,11 @@ export function useStatus(accountCount: number) {
62
  const ids: string[] = data.data.map((m: { id: string }) => m.id);
63
  if (ids.length > 0) {
64
  setModels(ids);
65
- const preferred = ids.find((n) => n === "codex");
66
  if (preferred) setSelectedModel(preferred);
67
  }
68
  } catch {
69
- setModels(["codex"]);
70
  }
71
  }, []);
72
 
 
44
  export function useStatus(accountCount: number) {
45
  const [baseUrl, setBaseUrl] = useState("Loading...");
46
  const [apiKey, setApiKey] = useState("Loading...");
47
+ const [models, setModels] = useState<string[]>(["gpt-5.4"]);
48
+ const [selectedModel, setSelectedModel] = useState("gpt-5.4");
49
  const [modelCatalog, setModelCatalog] = useState<CatalogModel[]>([]);
50
  const [selectedEffort, setSelectedEffort] = useState("medium");
51
 
 
62
  const ids: string[] = data.data.map((m: { id: string }) => m.id);
63
  if (ids.length > 0) {
64
  setModels(ids);
65
+ const preferred = ids.find((n) => n === "gpt-5.4");
66
  if (preferred) setSelectedModel(preferred);
67
  }
68
  } catch {
69
+ setModels(["gpt-5.4"]);
70
  }
71
  }, []);
72
 
web/src/components/ApiConfig.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import { useT } from "../../../shared/i18n/context";
2
  import { CopyButton } from "./CopyButton";
3
- import { useCallback } from "preact/hooks";
4
  import type { ModelFamily } from "../../../shared/hooks/use-status";
5
 
6
  interface ApiConfigProps {
@@ -38,10 +38,26 @@ export function ApiConfig({
38
  const getBaseUrl = useCallback(() => baseUrl, [baseUrl]);
39
  const getApiKey = useCallback(() => apiKey, [apiKey]);
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  // When a family is selected, update model + snap effort to default if current effort is unsupported
42
  const handleFamilySelect = useCallback(
43
  (family: ModelFamily) => {
44
  onModelChange(family.id);
 
45
  const supportedEfforts = family.efforts.map((e) => e.reasoningEffort);
46
  if (!supportedEfforts.includes(selectedEffort)) {
47
  onEffortChange(family.defaultEffort);
@@ -85,26 +101,40 @@ export function ApiConfig({
85
  <div class="space-y-1.5">
86
  <label class="text-xs font-semibold text-slate-700 dark:text-text-main">{t("defaultModel")}</label>
87
  {showMatrix ? (
88
- <div class="border border-gray-200 dark:border-border-dark rounded-lg overflow-hidden">
89
- {/* Model family list */}
90
- <div class="max-h-[200px] overflow-y-auto">
91
- {modelFamilies.map((f) => (
92
- <button
93
- key={f.id}
94
- onClick={() => handleFamilySelect(f)}
95
- class={`w-full text-left px-3 py-2 text-[0.78rem] font-medium border-b border-gray-100 dark:border-border-dark last:border-b-0 transition-colors ${
96
- selectedModel === f.id
97
- ? "bg-primary/10 text-primary dark:bg-primary/20"
98
- : "text-slate-700 dark:text-text-main hover:bg-slate-50 dark:hover:bg-[#21262d]"
99
- }`}
100
- >
101
- {f.displayName}
102
- </button>
103
- ))}
104
- </div>
105
- {/* Reasoning effort buttons for selected family */}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  {currentEfforts.length > 1 && (
107
- <div class="flex gap-1.5 p-2 bg-slate-50 dark:bg-bg-dark/50 border-t border-gray-200 dark:border-border-dark flex-wrap">
108
  {currentEfforts.map((e) => (
109
  <button
110
  key={e.reasoningEffort}
 
1
  import { useT } from "../../../shared/i18n/context";
2
  import { CopyButton } from "./CopyButton";
3
+ import { useCallback, useState, useEffect, useRef } from "preact/hooks";
4
  import type { ModelFamily } from "../../../shared/hooks/use-status";
5
 
6
  interface ApiConfigProps {
 
38
  const getBaseUrl = useCallback(() => baseUrl, [baseUrl]);
39
  const getApiKey = useCallback(() => apiKey, [apiKey]);
40
 
41
+ const [open, setOpen] = useState(false);
42
+ const dropdownRef = useRef<HTMLDivElement>(null);
43
+
44
+ // Close dropdown on click outside
45
+ useEffect(() => {
46
+ if (!open) return;
47
+ const handler = (e: MouseEvent) => {
48
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
49
+ setOpen(false);
50
+ }
51
+ };
52
+ document.addEventListener("mousedown", handler);
53
+ return () => document.removeEventListener("mousedown", handler);
54
+ }, [open]);
55
+
56
  // When a family is selected, update model + snap effort to default if current effort is unsupported
57
  const handleFamilySelect = useCallback(
58
  (family: ModelFamily) => {
59
  onModelChange(family.id);
60
+ setOpen(false);
61
  const supportedEfforts = family.efforts.map((e) => e.reasoningEffort);
62
  if (!supportedEfforts.includes(selectedEffort)) {
63
  onEffortChange(family.defaultEffort);
 
101
  <div class="space-y-1.5">
102
  <label class="text-xs font-semibold text-slate-700 dark:text-text-main">{t("defaultModel")}</label>
103
  {showMatrix ? (
104
+ <div ref={dropdownRef} class="relative">
105
+ {/* Trigger button */}
106
+ <button
107
+ onClick={() => setOpen(!open)}
108
+ class="w-full flex items-center justify-between px-3 py-2.5 bg-white dark:bg-bg-dark border border-gray-200 dark:border-border-dark rounded-lg text-[0.78rem] text-slate-700 dark:text-text-main font-medium focus:ring-1 focus:ring-primary focus:border-primary outline-none cursor-pointer transition-colors"
109
+ >
110
+ <span>{currentFamily?.displayName ?? selectedModel}</span>
111
+ <svg class={`size-[18px] text-slate-500 dark:text-text-dim transition-transform ${open ? "rotate-180" : ""}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
112
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
113
+ </svg>
114
+ </button>
115
+ {/* Dropdown list */}
116
+ {open && (
117
+ <div class="absolute z-10 mt-1 w-full border border-gray-200 dark:border-border-dark rounded-lg overflow-hidden bg-white dark:bg-card-dark shadow-lg">
118
+ <div class="max-h-[200px] overflow-y-auto">
119
+ {modelFamilies.map((f) => (
120
+ <button
121
+ key={f.id}
122
+ onClick={() => handleFamilySelect(f)}
123
+ class={`w-full text-left px-3 py-2 text-[0.78rem] font-medium border-b border-gray-100 dark:border-border-dark last:border-b-0 transition-colors ${
124
+ selectedModel === f.id
125
+ ? "bg-primary/10 text-primary dark:bg-primary/20"
126
+ : "text-slate-700 dark:text-text-main hover:bg-slate-50 dark:hover:bg-[#21262d]"
127
+ }`}
128
+ >
129
+ {f.displayName}
130
+ </button>
131
+ ))}
132
+ </div>
133
+ </div>
134
+ )}
135
+ {/* Reasoning effort buttons β€” always visible */}
136
  {currentEfforts.length > 1 && (
137
+ <div class="flex gap-1.5 mt-2 flex-wrap">
138
  {currentEfforts.map((e) => (
139
  <button
140
  key={e.reasoningEffort}