mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-10-21 23:08:15 +00:00
218 lines
8.4 KiB
TypeScript
218 lines
8.4 KiB
TypeScript
import { UIConfigField, ConfigModelProvider } from '@/lib/config/types';
|
|
import { cn } from '@/lib/utils';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { AlertCircle, ChevronDown, Pencil, Trash2, X } from 'lucide-react';
|
|
import { useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
import AddModel from './AddModelDialog';
|
|
import UpdateProvider from './UpdateProviderDialog';
|
|
import DeleteProvider from './DeleteProviderDialog';
|
|
|
|
const ModelProvider = ({
|
|
modelProvider,
|
|
setProviders,
|
|
fields,
|
|
}: {
|
|
modelProvider: ConfigModelProvider;
|
|
fields: UIConfigField[];
|
|
setProviders: React.Dispatch<React.SetStateAction<ConfigModelProvider[]>>;
|
|
}) => {
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const handleModelDelete = async (
|
|
type: 'chat' | 'embedding',
|
|
modelKey: string,
|
|
) => {
|
|
try {
|
|
const res = await fetch(`/api/providers/${modelProvider.id}/models`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ key: modelKey, type: type }),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new Error('Failed to delete model: ' + (await res.text()));
|
|
}
|
|
|
|
setProviders(
|
|
(prev) =>
|
|
prev.map((provider) => {
|
|
if (provider.id === modelProvider.id) {
|
|
return {
|
|
...provider,
|
|
...(type === 'chat'
|
|
? {
|
|
chatModels: provider.chatModels.filter(
|
|
(m) => m.key !== modelKey,
|
|
),
|
|
}
|
|
: {
|
|
embeddingModels: provider.embeddingModels.filter(
|
|
(m) => m.key !== modelKey,
|
|
),
|
|
}),
|
|
};
|
|
}
|
|
return provider;
|
|
}) as ConfigModelProvider[],
|
|
);
|
|
|
|
toast.success('Model deleted successfully.');
|
|
} catch (err) {
|
|
console.error('Failed to delete model', err);
|
|
toast.error('Failed to delete model.');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
key={modelProvider.id}
|
|
className="border border-light-200 dark:border-dark-200 rounded-lg overflow-hidden"
|
|
>
|
|
<div
|
|
className={cn(
|
|
'group px-5 py-4 flex flex-row justify-between w-full cursor-pointer hover:bg-light-secondary hover:dark:bg-dark-secondary transition duration-200 items-center',
|
|
!open && 'rounded-lg',
|
|
)}
|
|
onClick={() => setOpen(!open)}
|
|
>
|
|
<p className="text-sm lg:text-base text-black dark:text-white font-medium">
|
|
{modelProvider.name}
|
|
</p>
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex flex-row items-center">
|
|
<UpdateProvider
|
|
fields={fields}
|
|
modelProvider={modelProvider}
|
|
setProviders={setProviders}
|
|
/>
|
|
<DeleteProvider
|
|
modelProvider={modelProvider}
|
|
setProviders={setProviders}
|
|
/>
|
|
</div>
|
|
<ChevronDown
|
|
size={16}
|
|
className={cn(
|
|
open ? 'rotate-180' : '',
|
|
'transition duration-200 text-black/70 dark:text-white/70 group-hover:text-sky-500',
|
|
)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<AnimatePresence>
|
|
{open && (
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{ height: 'auto', opacity: 1 }}
|
|
exit={{ height: 0, opacity: 0 }}
|
|
transition={{ duration: 0.1 }}
|
|
>
|
|
<div className="border-t border-light-200 dark:border-dark-200" />
|
|
<div className="flex flex-col gap-y-4 px-5 py-4">
|
|
{modelProvider.chatModels.length > 0 && (
|
|
<div className="flex flex-col gap-y-2">
|
|
<div className="flex flex-row w-full justify-between items-center">
|
|
<p className="text-[11px] lg:text-xs text-black/70 dark:text-white/70">
|
|
Chat models
|
|
</p>
|
|
<AddModel
|
|
providerId={modelProvider.id}
|
|
setProviders={setProviders}
|
|
type="chat"
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
{modelProvider.chatModels.some((m) => m.key === 'error') ? (
|
|
<div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
|
|
<AlertCircle size={16} className="shrink-0" />
|
|
<span className="break-words">
|
|
{
|
|
modelProvider.chatModels.find(
|
|
(m) => m.key === 'error',
|
|
)?.name
|
|
}
|
|
</span>
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-row flex-wrap gap-2">
|
|
{modelProvider.chatModels.map((model, index) => (
|
|
<div
|
|
key={`${modelProvider.id}-chat-${model.key}-${index}`}
|
|
className="flex flex-row items-center space-x-1 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5"
|
|
>
|
|
<span>{model.name}</span>
|
|
<button
|
|
onClick={() => {
|
|
handleModelDelete('chat', model.key);
|
|
}}
|
|
>
|
|
<X size={12} />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{modelProvider.embeddingModels.length > 0 && (
|
|
<div className="flex flex-col gap-y-2">
|
|
<div className="flex flex-row w-full justify-between items-center">
|
|
<p className="text-[11px] lg:text-xs text-black/70 dark:text-white/70">
|
|
Embedding models
|
|
</p>
|
|
<AddModel
|
|
providerId={modelProvider.id}
|
|
setProviders={setProviders}
|
|
type="embedding"
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
{modelProvider.embeddingModels.some(
|
|
(m) => m.key === 'error',
|
|
) ? (
|
|
<div className="flex flex-row items-center gap-2 text-xs lg:text-sm text-red-500 dark:text-red-400 rounded-lg bg-red-50 dark:bg-red-950/20 px-3 py-2 border border-red-200 dark:border-red-900/30">
|
|
<AlertCircle size={16} className="shrink-0" />
|
|
<span className="break-words">
|
|
{
|
|
modelProvider.embeddingModels.find(
|
|
(m) => m.key === 'error',
|
|
)?.name
|
|
}
|
|
</span>
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-row flex-wrap gap-2">
|
|
{modelProvider.embeddingModels.map((model, index) => (
|
|
<div
|
|
key={`${modelProvider.id}-embedding-${model.key}-${index}`}
|
|
className="flex flex-row items-center space-x-1 text-xs lg:text-sm text-black/70 dark:text-white/70 rounded-lg bg-light-secondary dark:bg-dark-secondary px-3 py-1.5"
|
|
>
|
|
<span>{model.name}</span>
|
|
<button
|
|
onClick={() => {
|
|
handleModelDelete('embedding', model.key);
|
|
}}
|
|
>
|
|
<X size={12} />
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ModelProvider;
|