Skip to content

Commit 29c19e2

Browse files
committed
feat: add new components for managing provider profiles and configurations, including collapsible details and forms for adding providers and models
1 parent 431f904 commit 29c19e2

File tree

12 files changed

+483
-451
lines changed

12 files changed

+483
-451
lines changed

src-tauri/src/config/provider.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,36 @@ pub async fn add_or_update_model_provider(
6565
Ok(())
6666
}
6767

68+
#[command]
69+
pub async fn delete_model_provider(provider_name: String) -> Result<(), String> {
70+
let config_path = get_config_path()?;
71+
72+
if !config_path.exists() {
73+
return Err("Config file not found.".to_string());
74+
}
75+
76+
let content = fs::read_to_string(&config_path)
77+
.map_err(|e| format!("Failed to read config file: {}", e))?;
78+
79+
let mut codex_config: CodexConfig = toml::from_str(&content)
80+
.map_err(|e| format!("Failed to parse config file: {}", e))?;
81+
82+
codex_config.model_providers.remove(&provider_name);
83+
84+
// Remove profiles associated with the deleted model provider
85+
codex_config
86+
.profiles
87+
.retain(|_, profile| profile.model_provider != provider_name);
88+
89+
let toml_content = toml::to_string(&codex_config)
90+
.map_err(|e| format!("Failed to serialize config: {}", e))?;
91+
92+
fs::write(&config_path, toml_content)
93+
.map_err(|e| format!("Failed to write config file: {}", e))?;
94+
95+
Ok(())
96+
}
97+
6898
#[command]
6999
pub async fn ensure_default_providers() -> Result<(), String> {
70100
let config_path = get_config_path()?;

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub fn run() {
9696
config::profile::add_or_update_profile,
9797
config::profile::delete_profile,
9898
config::provider::add_or_update_model_provider,
99+
config::provider::delete_model_provider,
99100
config::provider::ensure_default_providers,
100101
enable_remote_ui,
101102
disable_remote_ui,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { open } from "@tauri-apps/plugin-shell";
2+
import { Button } from "../ui/button";
3+
4+
export function ConfigTip() {
5+
return (
6+
<Button
7+
onClick={() =>
8+
open("https://github.com/milisp/codexia/blob/main/docs/config.toml")
9+
}
10+
>
11+
online ~/.codex/config.toml example
12+
</Button>
13+
);
14+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useState } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Input } from "@/components/ui/input";
4+
import { Label } from "@/components/ui/label";
5+
import { toast } from "sonner";
6+
import { useProviderStore } from "@/stores/useProviderStore";
7+
import { ConfigTip } from "./config-tip";
8+
9+
export function AddProviderForm() {
10+
const [name, setName] = useState("");
11+
const [models, setModels] = useState("");
12+
const [baseUrl, setBaseUrl] = useState("");
13+
const [envKey, setEnvKey] = useState("");
14+
const { addProvider, providers } = useProviderStore();
15+
16+
const handleSubmit = (e: React.FormEvent) => {
17+
e.preventDefault();
18+
if (!name || !models) return;
19+
20+
if (providers.some((p) => p.name === name)) {
21+
toast.error("Provider with this name already exists.");
22+
return;
23+
}
24+
25+
addProvider({
26+
name,
27+
models: models
28+
.split(",")
29+
.map((m) => m.trim())
30+
.filter(Boolean),
31+
baseUrl: baseUrl || undefined,
32+
envKey: envKey || undefined,
33+
});
34+
toast.success("Provider added successfully!");
35+
setName("");
36+
setModels("");
37+
setBaseUrl("");
38+
setEnvKey("");
39+
};
40+
41+
return (
42+
<div>
43+
<form onSubmit={handleSubmit} className="p-4 space-y-4">
44+
<div className="space-y-2">
45+
<Label>Provider Name</Label>
46+
<Input
47+
value={name}
48+
onChange={(e) => setName(e.target.value)}
49+
placeholder="e.g., My Custom AI"
50+
/>
51+
</div>
52+
<div className="space-y-2">
53+
<Label>Models (comma-separated)</Label>
54+
<Input
55+
value={models}
56+
onChange={(e) => setModels(e.target.value)}
57+
placeholder="model-1, model-2"
58+
/>
59+
</div>
60+
<div className="space-y-2">
61+
<Label>Base URL (Optional)</Label>
62+
<Input
63+
value={baseUrl}
64+
onChange={(e) => setBaseUrl(e.target.value)}
65+
placeholder="e.g., https://api.example.com/v1"
66+
/>
67+
</div>
68+
<div className="space-y-2">
69+
<Label>Environment Key (Optional)</Label>
70+
<Input
71+
value={envKey}
72+
onChange={(e) => setEnvKey(e.target.value)}
73+
placeholder="e.g., MY_API_KEY"
74+
/>
75+
</div>
76+
<Button type="submit" size="sm" className="w-full">
77+
Add Provider
78+
</Button>
79+
</form>
80+
<div className="text-center">
81+
<ConfigTip />
82+
</div>
83+
</div>
84+
);
85+
}
86+
87+
export function AddModelForm({
88+
providerId,
89+
onAdd,
90+
}: {
91+
providerId: string;
92+
onAdd: () => void;
93+
}) {
94+
const [modelName, setModelName] = useState("");
95+
const { addModel } = useProviderStore();
96+
97+
const handleSubmit = (e: React.FormEvent) => {
98+
e.preventDefault();
99+
if (!modelName) return;
100+
addModel(providerId, modelName.trim());
101+
setModelName("");
102+
onAdd();
103+
};
104+
105+
return (
106+
<form onSubmit={handleSubmit} className="p-4 space-y-4">
107+
<div className="space-y-2">
108+
<Label>Model Name</Label>
109+
<Input
110+
value={modelName}
111+
onChange={(e) => setModelName(e.target.value)}
112+
placeholder="e.g., new-model-v1"
113+
/>
114+
</div>
115+
<Button type="submit" size="sm" className="w-full">
116+
Add Model
117+
</Button>
118+
</form>
119+
);
120+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useState } from "react";
2+
import { Button } from "@/components/ui/button";
3+
import { Input } from "@/components/ui/input";
4+
import { Label } from "@/components/ui/label";
5+
import { ChevronDown } from "lucide-react";
6+
import {
7+
Collapsible,
8+
CollapsibleContent,
9+
CollapsibleTrigger,
10+
} from "@/components/ui/collapsible";
11+
12+
interface ProviderDetailsCollapsibleProps {
13+
selectedProviderId: string | null;
14+
currentApiKeyVar: string;
15+
currentBaseUrl: string;
16+
handleApiKeyVarChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
17+
handleBaseUrlChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
18+
}
19+
20+
export function ProviderDetailsCollapsible({
21+
selectedProviderId,
22+
currentApiKeyVar,
23+
currentBaseUrl,
24+
handleApiKeyVarChange,
25+
handleBaseUrlChange,
26+
}: ProviderDetailsCollapsibleProps) {
27+
const [showProviderDetails, setShowProviderDetails] = useState(false);
28+
29+
return (
30+
<Collapsible
31+
open={showProviderDetails}
32+
onOpenChange={setShowProviderDetails}
33+
>
34+
<CollapsibleTrigger asChild>
35+
<Button
36+
type="button"
37+
variant="ghost"
38+
size="sm"
39+
className="h-8 px-2 gap-2 text-xs"
40+
disabled={!selectedProviderId}
41+
>
42+
{showProviderDetails
43+
? "Hide additional fields"
44+
: "Show additional fields"}
45+
<ChevronDown
46+
className={`h-3 w-3 transition-transform ${
47+
showProviderDetails ? "rotate-180" : ""
48+
}`}
49+
/>
50+
</Button>
51+
</CollapsibleTrigger>
52+
<CollapsibleContent className="mt-3 space-y-3">
53+
<div className="space-y-2">
54+
<Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
55+
API Key Variable
56+
</Label>
57+
<Input
58+
placeholder="e.g., OPENAI_API_KEY"
59+
value={currentApiKeyVar}
60+
onChange={handleApiKeyVarChange}
61+
className="font-mono text-xs"
62+
disabled={!selectedProviderId}
63+
/>
64+
</div>
65+
<div className="space-y-2">
66+
<Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
67+
Base URL
68+
</Label>
69+
<Input
70+
placeholder="https://api.example.com/v1"
71+
value={currentBaseUrl}
72+
onChange={handleBaseUrlChange}
73+
className="font-mono text-xs"
74+
disabled={!selectedProviderId}
75+
/>
76+
</div>
77+
</CollapsibleContent>
78+
</Collapsible>
79+
);
80+
}

0 commit comments

Comments
 (0)