Skip to content

Commit d6408a2

Browse files
authored
refactor(settings): simplify settings layout and controls (#1288)
1 parent e823f8f commit d6408a2

5 files changed

Lines changed: 833 additions & 538 deletions

File tree

apps/web/src/appSettings.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getCustomModelsByProvider,
1010
getCustomModelsForProvider,
1111
getDefaultCustomModelsForProvider,
12+
getProviderStartOptions,
1213
MODEL_PROVIDER_SETTINGS,
1314
normalizeCustomModelSlugs,
1415
patchCustomModels,
@@ -118,6 +119,35 @@ describe("provider-specific custom models", () => {
118119
});
119120
});
120121

122+
describe("getProviderStartOptions", () => {
123+
it("returns only populated provider overrides", () => {
124+
expect(
125+
getProviderStartOptions({
126+
claudeBinaryPath: "/usr/local/bin/claude",
127+
codexBinaryPath: "",
128+
codexHomePath: "/Users/you/.codex",
129+
}),
130+
).toEqual({
131+
claudeAgent: {
132+
binaryPath: "/usr/local/bin/claude",
133+
},
134+
codex: {
135+
homePath: "/Users/you/.codex",
136+
},
137+
});
138+
});
139+
140+
it("returns undefined when no provider overrides are configured", () => {
141+
expect(
142+
getProviderStartOptions({
143+
claudeBinaryPath: "",
144+
codexBinaryPath: "",
145+
codexHomePath: "",
146+
}),
147+
).toBeUndefined();
148+
});
149+
});
150+
121151
describe("provider-indexed custom model settings", () => {
122152
const settings = {
123153
customCodexModels: ["custom/codex-model"],
@@ -209,6 +239,7 @@ describe("AppSettingsSchema", () => {
209239
}),
210240
),
211241
).toMatchObject({
242+
claudeBinaryPath: "",
212243
codexBinaryPath: "/usr/local/bin/codex",
213244
codexHomePath: "",
214245
defaultThreadEnvMode: "local",

apps/web/src/appSettings.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { useCallback } from "react";
22
import { Option, Schema } from "effect";
3-
import { TrimmedNonEmptyString, type ProviderKind } from "@t3tools/contracts";
3+
import {
4+
TrimmedNonEmptyString,
5+
type ProviderKind,
6+
type ProviderStartOptions,
7+
} from "@t3tools/contracts";
48
import {
59
getDefaultModel,
610
getModelOptions,
@@ -47,6 +51,7 @@ const withDefaults =
4751
);
4852

4953
export const AppSettingsSchema = Schema.Struct({
54+
claudeBinaryPath: Schema.String.check(Schema.isMaxLength(4096)).pipe(withDefaults(() => "")),
5055
codexBinaryPath: Schema.String.check(Schema.isMaxLength(4096)).pipe(withDefaults(() => "")),
5156
codexHomePath: Schema.String.check(Schema.isMaxLength(4096)).pipe(withDefaults(() => "")),
5257
defaultThreadEnvMode: EnvMode.pipe(withDefaults(() => "local" as const satisfies EnvMode)),
@@ -221,6 +226,30 @@ export function getCustomModelOptionsByProvider(
221226
};
222227
}
223228

229+
export function getProviderStartOptions(
230+
settings: Pick<AppSettings, "claudeBinaryPath" | "codexBinaryPath" | "codexHomePath">,
231+
): ProviderStartOptions | undefined {
232+
const providerOptions: ProviderStartOptions = {
233+
...(settings.codexBinaryPath || settings.codexHomePath
234+
? {
235+
codex: {
236+
...(settings.codexBinaryPath ? { binaryPath: settings.codexBinaryPath } : {}),
237+
...(settings.codexHomePath ? { homePath: settings.codexHomePath } : {}),
238+
},
239+
}
240+
: {}),
241+
...(settings.claudeBinaryPath
242+
? {
243+
claudeAgent: {
244+
binaryPath: settings.claudeBinaryPath,
245+
},
246+
}
247+
: {}),
248+
};
249+
250+
return Object.keys(providerOptions).length > 0 ? providerOptions : undefined;
251+
}
252+
224253
export function useAppSettings() {
225254
const [settings, setSettings] = useLocalStorage(
226255
APP_SETTINGS_STORAGE_KEY,

apps/web/src/components/ChatView.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ import { readNativeApi } from "~/nativeApi";
122122
import {
123123
getCustomModelOptionsByProvider,
124124
getCustomModelsByProvider,
125+
getProviderStartOptions,
125126
resolveAppModelSelection,
126127
useAppSettings,
127128
} from "../appSettings";
@@ -618,17 +619,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
618619
);
619620
const selectedPromptEffort = composerProviderState.promptEffort;
620621
const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch;
621-
const providerOptionsForDispatch = useMemo(() => {
622-
if (!settings.codexBinaryPath && !settings.codexHomePath) {
623-
return undefined;
624-
}
625-
return {
626-
codex: {
627-
...(settings.codexBinaryPath ? { binaryPath: settings.codexBinaryPath } : {}),
628-
...(settings.codexHomePath ? { homePath: settings.codexHomePath } : {}),
629-
},
630-
};
631-
}, [settings.codexBinaryPath, settings.codexHomePath]);
622+
const providerOptionsForDispatch = useMemo(() => getProviderStartOptions(settings), [settings]);
632623
const selectedModelForPicker = selectedModel;
633624
const modelOptionsByProvider = useMemo(
634625
() => getCustomModelOptionsByProvider(settings),

apps/web/src/components/ui/select.tsx

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -164,32 +164,45 @@ function SelectPopup({
164164
);
165165
}
166166

167-
function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) {
167+
function SelectItem({
168+
className,
169+
children,
170+
hideIndicator = false,
171+
...props
172+
}: SelectPrimitive.Item.Props & {
173+
hideIndicator?: boolean;
174+
}) {
168175
return (
169176
<SelectPrimitive.Item
170177
className={cn(
171-
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
178+
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
179+
hideIndicator ? "grid-cols-[1fr] ps-3 pe-3" : "grid-cols-[1rem_1fr] ps-2 pe-4",
172180
className,
173181
)}
174182
data-slot="select-item"
175183
{...props}
176184
>
177-
<SelectPrimitive.ItemIndicator className="col-start-1">
178-
<svg
179-
fill="none"
180-
height="24"
181-
stroke="currentColor"
182-
strokeLinecap="round"
183-
strokeLinejoin="round"
184-
strokeWidth="2"
185-
viewBox="0 0 24 24"
186-
width="24"
187-
xmlns="http://www.w3.org/1500/svg"
188-
>
189-
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
190-
</svg>
191-
</SelectPrimitive.ItemIndicator>
192-
<SelectPrimitive.ItemText className="col-start-2 min-w-0">
185+
{hideIndicator ? null : (
186+
<SelectPrimitive.ItemIndicator className="col-start-1" data-slot="select-item-indicator">
187+
<svg
188+
fill="none"
189+
height="24"
190+
stroke="currentColor"
191+
strokeLinecap="round"
192+
strokeLinejoin="round"
193+
strokeWidth="2"
194+
viewBox="0 0 24 24"
195+
width="24"
196+
xmlns="http://www.w3.org/1500/svg"
197+
>
198+
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
199+
</svg>
200+
</SelectPrimitive.ItemIndicator>
201+
)}
202+
<SelectPrimitive.ItemText
203+
className={cn("min-w-0", hideIndicator ? "col-start-1" : "col-start-2")}
204+
data-slot="select-item-text"
205+
>
193206
{children}
194207
</SelectPrimitive.ItemText>
195208
</SelectPrimitive.Item>

0 commit comments

Comments
 (0)