Files
Perplexica/src/components/Settings/Sections/Models/UpdateProviderDialog.tsx
2025-10-18 15:10:43 +05:30

189 lines
7.1 KiB
TypeScript

import { Dialog, DialogPanel } from '@headlessui/react';
import { Loader2, Pencil } from 'lucide-react';
import { useEffect, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import {
ConfigModelProvider,
StringUIConfigField,
UIConfigField,
} from '@/lib/config/types';
import { toast } from 'sonner';
const UpdateProvider = ({
modelProvider,
fields,
setProviders,
}: {
fields: UIConfigField[];
modelProvider: ConfigModelProvider;
setProviders: React.Dispatch<React.SetStateAction<ConfigModelProvider[]>>;
}) => {
const [open, setOpen] = useState(false);
const [config, setConfig] = useState<Record<string, any>>({});
const [name, setName] = useState(modelProvider.name);
const [loading, setLoading] = useState(false);
useEffect(() => {
const config: Record<string, any> = {
name: modelProvider.name,
};
fields.forEach((field) => {
config[field.key] =
modelProvider.config[field.key] || field.default || '';
});
setConfig(config);
}, [fields]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const res = await fetch(`/api/providers/${modelProvider.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
config: config,
}),
});
if (!res.ok) {
throw new Error('Failed to update provider');
}
const data: ConfigModelProvider = (await res.json()).provider;
setProviders((prev) => {
return prev.map((p) => {
if (p.id === modelProvider.id) {
return data;
}
return p;
});
});
toast.success('Provider updated successfully.');
} catch (error) {
console.error('Error updating provider:', error);
toast.error('Failed to update provider.');
} finally {
setLoading(false);
setOpen(false);
}
};
return (
<>
<button
onClick={(e) => {
e.stopPropagation();
setOpen(true);
}}
className="group p-1.5 rounded-md hover:bg-light-200 hover:dark:bg-dark-200 transition-colors group"
>
<Pencil
size={14}
className="text-black/60 dark:text-white/60 group-hover:text-black group-hover:dark:text-white"
/>
</button>
<AnimatePresence>
{open && (
<Dialog
static
open={open}
onClose={() => setOpen(false)}
className="relative z-[60]"
>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.1 }}
className="fixed inset-0 flex w-screen items-center justify-center p-4 bg-black/30 backdrop-blur-sm"
>
<DialogPanel className="w-full mx-4 lg:w-[600px] max-h-[85vh] flex flex-col border bg-light-primary dark:bg-dark-primary border-light-secondary dark:border-dark-secondary rounded-lg">
<form onSubmit={handleSubmit} className="flex flex-col flex-1">
<div className="px-6 pt-6 pb-4">
<h3 className="text-black/90 dark:text-white/90 font-medium">
Update provider
</h3>
</div>
<div className="border-t border-light-200 dark:border-dark-200" />
<div className="flex-1 overflow-y-auto px-6 py-4">
<div className="flex flex-col space-y-4">
<div
key="name"
className="flex flex-col items-start space-y-2"
>
<label className="text-xs text-black/70 dark:text-white/70">
Name*
</label>
<input
value={name}
onChange={(event) => setName(event.target.value)}
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
placeholder={'Provider Name'}
type="text"
required={true}
/>
</div>
{fields.map((field: UIConfigField) => (
<div
key={field.key}
className="flex flex-col items-start space-y-2"
>
<label className="text-xs text-black/70 dark:text-white/70">
{field.name}
{field.required && '*'}
</label>
<input
value={config[field.key] ?? field.default ?? ''}
onChange={(event) =>
setConfig((prev) => ({
...prev,
[field.key]: event.target.value,
}))
}
className="w-full rounded-lg border border-light-200 dark:border-dark-200 bg-light-primary dark:bg-dark-primary px-4 py-3 pr-10 text-sm text-black/80 dark:text-white/80 placeholder:text-black/40 dark:placeholder:text-white/40 focus-visible:outline-none focus-visible:border-light-300 dark:focus-visible:border-dark-300 transition-colors disabled:cursor-not-allowed disabled:opacity-60"
placeholder={
(field as StringUIConfigField).placeholder
}
type="text"
required={field.required}
/>
</div>
))}
</div>
</div>
<div className="border-t border-light-200 dark:border-dark-200" />
<div className="px-6 py-4 flex justify-end">
<button
type="submit"
disabled={loading}
className="px-4 py-2 rounded-lg text-sm bg-sky-500 text-white font-medium disabled:opacity-85 hover:opacity-85 active:scale-95 transition duration-200"
>
{loading ? (
<Loader2 className="animate-spin" size={16} />
) : (
'Update Provider'
)}
</button>
</div>
</form>
</DialogPanel>
</motion.div>
</Dialog>
)}
</AnimatePresence>
</>
);
};
export default UpdateProvider;