diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
index 21deb36..611fff5 100644
--- a/src/pages/Settings.tsx
+++ b/src/pages/Settings.tsx
@@ -1,20 +1,24 @@
import { useEffect, useMemo, useState } from "react";
import type { FormEvent } from "react";
-import { api } from "../lib/api";
-import type { ModelCatalogProvider, ModelProfile, ResolvedApiKey } from "../lib/types";
+import { api } from "@/lib/api";
+import type { ModelCatalogProvider, ModelProfile, ResolvedApiKey } from "@/lib/types";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
-import {
- Select,
- SelectTrigger,
- SelectValue,
- SelectContent,
- SelectItem,
-} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
+import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
+import {
+ Command,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+} from "@/components/ui/command";
+import { ChevronsUpDown, Check } from "lucide-react";
+import { cn } from "@/lib/utils";
type ProfileForm = {
id: string;
@@ -38,7 +42,109 @@ function emptyForm(): ProfileForm {
};
}
-const CHAT_PROFILE_KEY = "clawpal_chat_profile";
+function ComboboxField({
+ value,
+ onChange,
+ onOpen,
+ options,
+ placeholder,
+}: {
+ value: string;
+ onChange: (val: string) => void;
+ onOpen?: () => void;
+ options: { value: string; label: string }[];
+ placeholder: string;
+}) {
+ const [open, setOpen] = useState(false);
+ const [search, setSearch] = useState("");
+
+ return (
+
{
+ setOpen(o);
+ if (o && onOpen) onOpen();
+ }}
+ >
+
+
+
+
+
+
+
+ No results found.
+
+ {/* Show typed value as option if it doesn't match any existing option */}
+ {search &&
+ !options.some(
+ (o) => o.value.toLowerCase() === search.toLowerCase(),
+ ) && (
+ {
+ onChange(search);
+ setOpen(false);
+ setSearch("");
+ }}
+ >
+
+ Use "{search}"
+
+ )}
+ {options
+ .filter(
+ (o) =>
+ !search ||
+ o.value.toLowerCase().includes(search.toLowerCase()) ||
+ o.label.toLowerCase().includes(search.toLowerCase()),
+ )
+ .map((option) => (
+ {
+ onChange(option.value);
+ setOpen(false);
+ setSearch("");
+ }}
+ >
+
+ {option.label}
+
+ ))}
+
+
+
+
+
+ );
+}
export function Settings() {
const [profiles, setProfiles] = useState
([]);
@@ -46,9 +152,6 @@ export function Settings() {
const [apiKeys, setApiKeys] = useState([]);
const [form, setForm] = useState(emptyForm());
const [message, setMessage] = useState("");
- const [chatProfileId, setChatProfileId] = useState(
- () => localStorage.getItem(CHAT_PROFILE_KEY) || "",
- );
const [catalogRefreshed, setCatalogRefreshed] = useState(false);
@@ -135,32 +238,19 @@ export function Settings() {
if (form.id === id) {
setForm(emptyForm());
}
- if (chatProfileId === id) {
- setChatProfileId("");
- localStorage.removeItem(CHAT_PROFILE_KEY);
- }
refreshProfiles();
})
.catch(() => setMessage("Delete failed"));
};
- const handleChatProfileChange = (value: string) => {
- setChatProfileId(value);
- if (value) {
- localStorage.setItem(CHAT_PROFILE_KEY, value);
- } else {
- localStorage.removeItem(CHAT_PROFILE_KEY);
- }
- };
-
return (
- Settings
+ Settings
{/* ---- Model Profiles ---- */}
{/* Create / Edit form */}
-
+
{form.id ? "Edit Profile" : "Add Profile"}
@@ -168,44 +258,34 @@ export function Settings() {
)}
@@ -272,37 +350,37 @@ export function Settings() {
{/* Profiles list */}
-
+
Model Profiles
{profiles.length === 0 && (
- No model profiles yet.
+ No model profiles yet.
)}
{profiles.map((profile) => (
{profile.provider}/{profile.model}
{profile.enabled ? (
-
+
enabled
) : (
-
+
disabled
)}
-
+
API Key: {maskedKeyMap.get(profile.id) || "..."}
{profile.baseUrl && (
-
+
URL: {profile.baseUrl}
)}
@@ -331,42 +409,8 @@ export function Settings() {
- {/* ---- Chat Model ---- */}
-
-
- Chat Model
-
-
-
- Select which model profile to use for the Chat feature.
-
-
-
-
-
{message && (
-
{message}
+
{message}
)}
);
diff --git a/src/styles.css b/src/styles.css
index d8a45e1..4fc10a3 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -2,38 +2,38 @@
:root {
--radius: 0.625rem;
- --background: oklch(0.129 0.042 264.695);
- --foreground: oklch(0.984 0.003 247.858);
- --card: oklch(0.208 0.042 265.755);
- --card-foreground: oklch(0.984 0.003 247.858);
- --popover: oklch(0.208 0.042 265.755);
- --popover-foreground: oklch(0.984 0.003 247.858);
- --primary: oklch(0.929 0.013 255.508);
- --primary-foreground: oklch(0.208 0.042 265.755);
- --secondary: oklch(0.279 0.041 260.031);
- --secondary-foreground: oklch(0.984 0.003 247.858);
- --muted: oklch(0.279 0.041 260.031);
- --muted-foreground: oklch(0.704 0.04 256.788);
- --accent: oklch(0.279 0.041 260.031);
- --accent-foreground: oklch(0.984 0.003 247.858);
- --destructive: oklch(0.704 0.191 22.216);
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.985 0 0);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.551 0.027 264.364);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.208 0.042 265.755);
- --sidebar-foreground: oklch(0.984 0.003 247.858);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.984 0.003 247.858);
- --sidebar-accent: oklch(0.279 0.041 260.031);
- --sidebar-accent-foreground: oklch(0.984 0.003 247.858);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.551 0.027 264.364);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
}
@theme inline {
@@ -70,22 +70,9 @@
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--radius: var(--radius);
-
- /* Custom app theme colors */
- --color-bg: #0f1220;
- --color-bg-gradient: #151935;
- --color-panel: #171b2f;
- --color-text-main: #e6ebff;
- --color-accent-blue: #6dd0ff;
- --color-border-subtle: #29325a;
- --color-btn-bg: #1f2750;
- --color-btn-border: #2d3560;
- --color-destructive-red: #ff6b6b;
- --color-success-green: #50c878;
}
body {
margin: 0;
font-family: ui-sans-serif, -apple-system, sans-serif;
- background: linear-gradient(120deg, var(--color-bg), var(--color-bg-gradient));
}