diff --git a/ui/src/app/settings/page.tsx b/ui/src/app/settings/page.tsx
index 84345d7..461286b 100644
--- a/ui/src/app/settings/page.tsx
+++ b/ui/src/app/settings/page.tsx
@@ -3,6 +3,7 @@
import { ExternalLink } from "lucide-react";
import { MCPSection } from "@/components/MCPSection";
+import { ModelConfigurationPreferencesSection } from "@/components/ModelConfigurationPreferencesSection";
import { TelemetrySection } from "@/components/TelemetrySection";
import {
Card,
@@ -23,6 +24,19 @@ export default function SettingsPage() {
+
+
+ Preferences
+
+ Set organization-wide defaults such as the test phone number and
+ timezone.
+
+
+
+
+
+
+
MCP Server
diff --git a/ui/src/components/ModelConfigurationPreferencesSection.tsx b/ui/src/components/ModelConfigurationPreferencesSection.tsx
new file mode 100644
index 0000000..6fb03ec
--- /dev/null
+++ b/ui/src/components/ModelConfigurationPreferencesSection.tsx
@@ -0,0 +1,221 @@
+"use client";
+
+import { Save } from "lucide-react";
+import { useEffect, useId, useRef, useState } from "react";
+import TimezoneSelect, { type ITimezoneOption } from "react-timezone-select";
+import { toast } from "sonner";
+
+import {
+ getModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesGet,
+ saveModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesPut,
+} from "@/client/sdk.gen";
+import type { OrganizationAiModelConfigurationPreferences } from "@/client/types.gen";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { useUserConfig } from "@/context/UserConfigContext";
+import { detailFromError } from "@/lib/apiError";
+import { useAuth } from "@/lib/auth";
+
+const emptyPreferences: OrganizationAiModelConfigurationPreferences = {
+ test_phone_number: "",
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
+};
+
+const timezoneSelectStyles = {
+ control: (base: Record, state: { isFocused: boolean }) => ({
+ ...base,
+ minHeight: "36px",
+ fontSize: "14px",
+ backgroundColor: "var(--background)",
+ borderColor: state.isFocused ? "var(--ring)" : "var(--border)",
+ boxShadow: state.isFocused
+ ? "0 0 0 2px color-mix(in srgb, var(--ring) 20%, transparent)"
+ : "none",
+ "&:hover": { borderColor: "var(--border)" },
+ }),
+ menu: (base: Record) => ({
+ ...base,
+ zIndex: 9999,
+ backgroundColor: "var(--popover)",
+ border: "1px solid var(--border)",
+ boxShadow:
+ "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
+ }),
+ menuList: (base: Record) => ({
+ ...base,
+ backgroundColor: "var(--popover)",
+ padding: 0,
+ }),
+ option: (
+ base: Record,
+ state: { isFocused: boolean; isSelected: boolean },
+ ) => ({
+ ...base,
+ backgroundColor: state.isSelected
+ ? "var(--accent)"
+ : state.isFocused
+ ? "var(--accent)"
+ : "var(--popover)",
+ color: "var(--foreground)",
+ cursor: "pointer",
+ "&:active": { backgroundColor: "var(--accent)" },
+ }),
+ singleValue: (base: Record) => ({
+ ...base,
+ color: "var(--foreground)",
+ }),
+ input: (base: Record) => ({
+ ...base,
+ color: "var(--foreground)",
+ }),
+ placeholder: (base: Record) => ({
+ ...base,
+ color: "var(--muted-foreground)",
+ }),
+ indicatorSeparator: (base: Record) => ({
+ ...base,
+ backgroundColor: "var(--border)",
+ }),
+ dropdownIndicator: (base: Record) => ({
+ ...base,
+ color: "var(--muted-foreground)",
+ "&:hover": { color: "var(--foreground)" },
+ }),
+};
+
+function getTimezoneValue(tz: ITimezoneOption | string): string {
+ return typeof tz === "string" ? tz : tz.value;
+}
+
+export function ModelConfigurationPreferencesSection() {
+ const { user, loading: authLoading } = useAuth();
+ const { refreshConfig } = useUserConfig();
+ const timezoneSelectId = useId();
+ const hasFetched = useRef(false);
+
+ const [preferences, setPreferences] =
+ useState(emptyPreferences);
+ const [timezone, setTimezone] = useState(
+ emptyPreferences.timezone || "UTC",
+ );
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+
+ useEffect(() => {
+ if (authLoading || !user || hasFetched.current) {
+ return;
+ }
+ hasFetched.current = true;
+ void fetchPreferences();
+ }, [authLoading, user]);
+
+ async function fetchPreferences() {
+ setLoading(true);
+ try {
+ const result =
+ await getModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesGet();
+
+ if (result.error) {
+ toast.error(
+ detailFromError(
+ result.error,
+ "Failed to load model configuration preferences",
+ ),
+ );
+ return;
+ }
+
+ const nextPreferences = result.data || emptyPreferences;
+ setPreferences({
+ test_phone_number: nextPreferences.test_phone_number || "",
+ timezone: nextPreferences.timezone || emptyPreferences.timezone,
+ });
+ setTimezone(
+ nextPreferences.timezone || emptyPreferences.timezone || "UTC",
+ );
+ } catch {
+ toast.error("Failed to load model configuration preferences");
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ async function handleSave(e: React.FormEvent) {
+ e.preventDefault();
+ setSaving(true);
+ try {
+ const result =
+ await saveModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesPut(
+ {
+ body: {
+ test_phone_number: preferences.test_phone_number || null,
+ timezone: getTimezoneValue(timezone),
+ },
+ },
+ );
+
+ if (result.error) {
+ toast.error(detailFromError(result.error, "Failed to save preferences"));
+ return;
+ }
+ if (!result.data) {
+ toast.error("Failed to save preferences");
+ return;
+ }
+
+ setPreferences({
+ test_phone_number: result.data.test_phone_number || "",
+ timezone: result.data.timezone || emptyPreferences.timezone,
+ });
+ setTimezone(result.data.timezone || emptyPreferences.timezone || "UTC");
+ await refreshConfig();
+ toast.success("Preferences saved");
+ } catch {
+ toast.error("Failed to save preferences");
+ } finally {
+ setSaving(false);
+ }
+ }
+
+ if (loading) {
+ return Loading...
;
+ }
+
+ return (
+
+ );
+}
diff --git a/ui/src/components/ModelConfigurationV2.tsx b/ui/src/components/ModelConfigurationV2.tsx
index 6cb4e6e..8f0a06d 100644
--- a/ui/src/components/ModelConfigurationV2.tsx
+++ b/ui/src/components/ModelConfigurationV2.tsx
@@ -1,19 +1,15 @@
"use client";
-import { ExternalLink, RefreshCw, Save } from "lucide-react";
-import { useEffect, useId, useRef, useState } from "react";
-import TimezoneSelect, { type ITimezoneOption } from "react-timezone-select";
+import { ExternalLink, RefreshCw } from "lucide-react";
+import { useEffect, useRef, useState } from "react";
import {
- getModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesGet,
getModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2Get,
getModelConfigurationV2DefaultsApiV1OrganizationsModelConfigurationsV2DefaultsGet,
migrateModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2MigratePost,
- saveModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesPut,
saveModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2Put,
} from "@/client/sdk.gen";
import type {
- OrganizationAiModelConfigurationPreferences,
OrganizationAiModelConfigurationResponse,
OrganizationAiModelConfigurationV2,
} from "@/client/types.gen";
@@ -30,62 +26,11 @@ import {
} from "@/components/ui/alert-dialog";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
import { Skeleton } from "@/components/ui/skeleton";
import { useUserConfig } from "@/context/UserConfigContext";
import { detailFromError } from "@/lib/apiError";
import { useAuth } from "@/lib/auth";
-const emptyPreferences: OrganizationAiModelConfigurationPreferences = {
- test_phone_number: "",
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
-};
-
-const timezoneSelectStyles = {
- control: (base: Record, state: { isFocused: boolean }) => ({
- ...base,
- minHeight: "36px",
- fontSize: "14px",
- backgroundColor: "var(--background)",
- borderColor: state.isFocused ? "var(--ring)" : "var(--border)",
- boxShadow: state.isFocused ? "0 0 0 2px color-mix(in srgb, var(--ring) 20%, transparent)" : "none",
- "&:hover": { borderColor: "var(--border)" },
- }),
- menu: (base: Record) => ({
- ...base,
- zIndex: 9999,
- backgroundColor: "var(--popover)",
- border: "1px solid var(--border)",
- boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
- }),
- menuList: (base: Record) => ({
- ...base,
- backgroundColor: "var(--popover)",
- padding: 0,
- }),
- option: (base: Record, state: { isSelected: boolean; isFocused: boolean }) => ({
- ...base,
- backgroundColor: state.isSelected ? "var(--accent)" : state.isFocused ? "var(--accent)" : "var(--popover)",
- color: "var(--foreground)",
- cursor: "pointer",
- "&:active": { backgroundColor: "var(--accent)" },
- }),
- singleValue: (base: Record) => ({ ...base, color: "var(--foreground)" }),
- input: (base: Record) => ({ ...base, color: "var(--foreground)" }),
- placeholder: (base: Record) => ({ ...base, color: "var(--muted-foreground)" }),
- indicatorSeparator: (base: Record) => ({ ...base, backgroundColor: "var(--border)" }),
- dropdownIndicator: (base: Record) => ({
- ...base,
- color: "var(--muted-foreground)",
- "&:hover": { color: "var(--foreground)" },
- }),
-};
-
-function getTimezoneValue(tz: ITimezoneOption | string): string {
- return typeof tz === "string" ? tz : tz.value;
-}
-
export default function ModelConfigurationV2({
docsUrl,
initialAction,
@@ -95,16 +40,12 @@ export default function ModelConfigurationV2({
}) {
const auth = useAuth();
const { refreshConfig, saveUserConfig } = useUserConfig();
- const timezoneSelectId = useId();
const hasFetched = useRef(false);
const hasAppliedInitialMigrationAction = useRef(false);
const [defaults, setDefaults] = useState(null);
const [response, setResponse] = useState(null);
- const [preferences, setPreferences] = useState(emptyPreferences);
- const [timezone, setTimezone] = useState(emptyPreferences.timezone || "UTC");
const [loading, setLoading] = useState(true);
- const [savingPreferences, setSavingPreferences] = useState(false);
const [migrating, setMigrating] = useState(false);
const [migrationDialogOpen, setMigrationDialogOpen] = useState(false);
const [error, setError] = useState(null);
@@ -121,10 +62,9 @@ export default function ModelConfigurationV2({
const load = async () => {
setLoading(true);
setError(null);
- const [defaultsResult, configResult, preferencesResult] = await Promise.all([
+ const [defaultsResult, configResult] = await Promise.all([
getModelConfigurationV2DefaultsApiV1OrganizationsModelConfigurationsV2DefaultsGet(),
getModelConfigurationV2ApiV1OrganizationsModelConfigurationsV2Get(),
- getModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesGet(),
]);
if (defaultsResult.error) {
@@ -137,11 +77,6 @@ export default function ModelConfigurationV2({
setLoading(false);
return;
}
- if (preferencesResult.error) {
- setError(detailFromError(preferencesResult.error, "Failed to load model configuration preferences"));
- setLoading(false);
- return;
- }
const nextDefaults = defaultsResult.data as ModelConfigurationDefaultsV2;
if (!nextDefaults || !configResult.data) {
@@ -151,13 +86,6 @@ export default function ModelConfigurationV2({
}
setDefaults(nextDefaults);
applyResponse(configResult.data);
-
- const nextPreferences = preferencesResult.data || emptyPreferences;
- setPreferences({
- test_phone_number: nextPreferences.test_phone_number || "",
- timezone: nextPreferences.timezone || emptyPreferences.timezone,
- });
- setTimezone(nextPreferences.timezone || emptyPreferences.timezone || "UTC");
setLoading(false);
};
@@ -194,30 +122,6 @@ export default function ModelConfigurationV2({
setNotice("Model configuration saved");
};
- const savePreferences = async () => {
- setSavingPreferences(true);
- setError(null);
- setNotice(null);
-
- const result = await saveModelConfigurationPreferencesApiV1OrganizationsModelConfigurationsPreferencesPut({
- body: {
- test_phone_number: preferences.test_phone_number || null,
- timezone: getTimezoneValue(timezone),
- },
- });
-
- if (result.error) {
- setError(detailFromError(result.error, "Failed to save preferences"));
- } else if (!result.data) {
- setError("Failed to save preferences");
- } else {
- setPreferences(result.data);
- await refreshConfig();
- setNotice("Preferences saved");
- }
- setSavingPreferences(false);
- };
-
const migrateConfiguration = async () => {
if (!defaults) return;
setMigrating(true);
@@ -364,36 +268,6 @@ export default function ModelConfigurationV2({
onSave={saveConfiguration}
/>
)}
-
-
-
-
Preferences
-
-
-
-
- setPreferences({ ...preferences, test_phone_number: event.target.value })}
- placeholder="+15551234567"
- />
-
-
-
-
-
-
-
-
{migrationWarningDialog}
);