Exched/DeepSeek_Coder
0
1"use client";2import classNames from "classnames";3 4import {5 Popover,6 PopoverContent,7 PopoverTrigger,8} from "@/components/ui/popover";9import { PROVIDERS, MODELS } from "@/lib/providers";10import { Button } from "@/components/ui/button";11import {12 Select,13 SelectContent,14 SelectGroup,15 SelectItem,16 SelectLabel,17 SelectTrigger,18 SelectValue,19} from "@/components/ui/select";20import { useMemo, useState, useEffect } from "react";21import { useUpdateEffect } from "react-use";22import Image from "next/image";23import { Brain, BrainIcon, CheckCheck, ChevronDown } from "lucide-react";24import { useAi } from "@/hooks/useAi";25import { getProviders } from "@/lib/get-providers";26import Loading from "@/components/loading";27 28export function Settings({29 open,30 onClose,31 error,32 isFollowUp = false,33}: {34 open: boolean;35 error?: string;36 isFollowUp?: boolean;37 onClose: React.Dispatch<React.SetStateAction<boolean>>;38}) {39 const {40 model,41 provider,42 setProvider,43 setModel,44 selectedModel,45 globalAiLoading,46 } = useAi();47 const [isMounted, setIsMounted] = useState(false);48 const [loadingProviders, setLoadingProviders] = useState(false);49 50 useEffect(() => {51 setIsMounted(true);52 }, []);53 54 // const modelAvailableProviders = useMemo(() => {55 // const availableProviders = MODELS.find(56 // (m: { value: string }) => m.value === model57 // )?.providers;58 // if (!availableProviders) return Object.keys(PROVIDERS);59 // return Object.keys(PROVIDERS).filter((id) =>60 // availableProviders.includes(id)61 // );62 // }, [model]);63 64 useUpdateEffect(() => {65 if (provider !== "auto" && !providers.includes(provider as string)) {66 setProvider("auto");67 }68 }, [model, provider]);69 70 const formattedModels = useMemo(() => {71 const lists: ((typeof MODELS)[0] | { isCategory: true; name: string })[] =72 [];73 const keys = new Set<string>();74 MODELS.forEach((model) => {75 if (!keys.has(model.companyName)) {76 lists.push({77 isCategory: true,78 name: model.companyName,79 logo: model.logo,80 });81 keys.add(model.companyName);82 }83 lists.push(model);84 });85 return lists;86 }, [MODELS]);87 88 const [providers, setProviders] = useState<any[]>([]);89 const [failedImages, setFailedImages] = useState<Set<string>>(new Set());90 91 useEffect(() => {92 const loadProviders = async () => {93 setLoadingProviders(true);94 if (!model) {95 setProviders([]);96 return;97 }98 try {99 const result = await getProviders(model);100 setProviders(result);101 } catch (error) {102 console.error("Failed to load providers:", error);103 setProviders([]);104 } finally {105 setLoadingProviders(false);106 }107 };108 109 loadProviders();110 }, [model]);111 112 const handleImageError = (providerId: string) => {113 setFailedImages((prev) => new Set([...prev, providerId]));114 };115 116 return (117 <Popover open={open} onOpenChange={onClose}>118 <PopoverTrigger asChild>119 <Button120 variant={open ? "default" : "outline"}121 className="!rounded-md"122 disabled={globalAiLoading || loadingProviders}123 size="xs"124 >125 {/* <Brain className="size-3.5" /> */}126 {selectedModel?.logo && (127 <Image128 src={selectedModel?.logo}129 alt={selectedModel.label}130 className={`size-3.5 ${open ? "" : "filter invert"}`}131 width={20}132 height={20}133 />134 )}135 <span className="truncate max-w-[120px]">136 {isMounted137 ? selectedModel?.label?.split(" ").join("-").toLowerCase()138 : "..."}139 </span>140 <ChevronDown className="size-3.5" />141 </Button>142 </PopoverTrigger>143 <PopoverContent144 className="!rounded-2xl p-0 !w-96 overflow-hidden !bg-neutral-900"145 align="center"146 >147 <header className="flex items-center justify-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">148 Customize Settings149 </header>150 <main className="px-4 pt-5 pb-6 space-y-5">151 {error !== "" && (152 <p className="text-red-500 text-sm font-medium mb-2 flex items-center justify-between bg-red-500/10 p-2 rounded-md">153 {error}154 </p>155 )}156 <label className="block">157 <p className="text-neutral-300 text-sm mb-2.5">Choose a model</p>158 <Select defaultValue={model} onValueChange={setModel}>159 <SelectTrigger className="w-full">160 <SelectValue placeholder="Select a model" />161 </SelectTrigger>162 <SelectContent>163 <SelectGroup>164 {formattedModels.map((item: any) => {165 if ("isCategory" in item) {166 return (167 <SelectLabel168 key={item.name}169 className="flex items-center gap-1"170 >171 {item.name}172 </SelectLabel>173 );174 }175 const {176 value,177 label,178 isNew = false,179 isThinker = false,180 } = item;181 return (182 <SelectItem183 key={value}184 value={value}185 className=""186 disabled={isThinker && isFollowUp}187 >188 {label}189 {isNew && (190 <span className="text-xs bg-gradient-to-br from-sky-400 to-sky-600 text-white rounded-full px-1.5 py-0.5">191 New192 </span>193 )}194 </SelectItem>195 );196 })}197 </SelectGroup>198 </SelectContent>199 </Select>200 </label>201 {/* {isFollowUp && (202 <div className="bg-amber-500/10 border-amber-500/10 p-3 text-xs text-amber-500 border rounded-lg">203 Note: You can't use a Thinker model for follow-up requests.204 We automatically switch to the default model for you.205 </div>206 )} */}207 <div className="flex flex-col gap-3">208 <div className="flex items-center justify-between">209 <div>210 <p className="text-neutral-300 text-sm mb-1.5">211 Use auto-provider212 </p>213 <p className="text-xs text-neutral-400/70">214 We'll automatically select the best provider for you215 based on your prompt.216 </p>217 </div>218 <div219 className={classNames(220 "bg-neutral-700 rounded-full min-w-10 w-10 h-6 flex items-center justify-between p-1 cursor-pointer transition-all duration-200",221 {222 "!bg-sky-500": provider === "auto",223 }224 )}225 onClick={() => {226 const foundModel = MODELS.find(227 (m: { value: string }) => m.value === model228 );229 if (provider === "auto" && foundModel?.autoProvider) {230 setProvider(foundModel.autoProvider);231 } else {232 setProvider("auto");233 }234 }}235 >236 <div237 className={classNames(238 "w-4 h-4 rounded-full shadow-md transition-all duration-200 bg-neutral-200",239 {240 "translate-x-4": provider === "auto",241 }242 )}243 />244 </div>245 </div>246 <label className="block">247 <p className="text-neutral-300 text-sm mb-2">248 Inference Provider249 </p>250 <div className="grid grid-cols-2 gap-1.5 relative">251 {loadingProviders ? (252 <Loading overlay={false} />253 ) : (254 providers.map((id: string) => (255 <Button256 key={id}257 variant={id === provider ? "default" : "secondary"}258 size="sm"259 onClick={() => {260 setProvider(id);261 }}262 >263 {failedImages.has(id) ? (264 <BrainIcon className="size-4 mr-2" />265 ) : (266 <Image267 src={`/providers/${id}.svg`}268 alt={id}269 className="size-5 mr-2"270 width={20}271 height={20}272 onError={() => handleImageError(id)}273 />274 )}275 {PROVIDERS?.[id as keyof typeof PROVIDERS]?.name || id}276 {id === provider && (277 <CheckCheck className="ml-2 size-4 text-blue-500" />278 )}279 </Button>280 ))281 )}282 </div>283 </label>284 </div>285 </main>286 </PopoverContent>287 </Popover>288 );289}290 