You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

500 lines
19 KiB

11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
  1. import React, { useEffect, useRef, useState } from "react";
  2. import { useTranslation } from "react-i18next";
  3. import Sidebar from "@/components/SettingsSidebar";
  4. import { isMobile } from "react-device-detect";
  5. import System from "@/models/system";
  6. import showToast from "@/utils/toast";
  7. import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
  8. import OpenAiLogo from "@/media/llmprovider/openai.png";
  9. import GenericOpenAiLogo from "@/media/llmprovider/generic-openai.png";
  10. import AzureOpenAiLogo from "@/media/llmprovider/azure.png";
  11. import AnthropicLogo from "@/media/llmprovider/anthropic.png";
  12. import GeminiLogo from "@/media/llmprovider/gemini.png";
  13. import OllamaLogo from "@/media/llmprovider/ollama.png";
  14. import NovitaLogo from "@/media/llmprovider/novita.png";
  15. import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
  16. import LocalAiLogo from "@/media/llmprovider/localai.png";
  17. import TogetherAILogo from "@/media/llmprovider/togetherai.png";
  18. import FireworksAILogo from "@/media/llmprovider/fireworksai.jpeg";
  19. import MistralLogo from "@/media/llmprovider/mistral.jpeg";
  20. import HuggingFaceLogo from "@/media/llmprovider/huggingface.png";
  21. import PerplexityLogo from "@/media/llmprovider/perplexity.png";
  22. import OpenRouterLogo from "@/media/llmprovider/openrouter.jpeg";
  23. import GroqLogo from "@/media/llmprovider/groq.png";
  24. import KoboldCPPLogo from "@/media/llmprovider/koboldcpp.png";
  25. import TextGenWebUILogo from "@/media/llmprovider/text-generation-webui.png";
  26. import CohereLogo from "@/media/llmprovider/cohere.png";
  27. import LiteLLMLogo from "@/media/llmprovider/litellm.png";
  28. import AWSBedrockLogo from "@/media/llmprovider/bedrock.png";
  29. import DeepSeekLogo from "@/media/llmprovider/deepseek.png";
  30. import APIPieLogo from "@/media/llmprovider/apipie.png";
  31. import XAILogo from "@/media/llmprovider/xai.png";
  32. import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png";
  33. import PreLoader from "@/components/Preloader";
  34. import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
  35. import GenericOpenAiOptions from "@/components/LLMSelection/GenericOpenAiOptions";
  36. import AzureAiOptions from "@/components/LLMSelection/AzureAiOptions";
  37. import AnthropicAiOptions from "@/components/LLMSelection/AnthropicAiOptions";
  38. import LMStudioOptions from "@/components/LLMSelection/LMStudioOptions";
  39. import LocalAiOptions from "@/components/LLMSelection/LocalAiOptions";
  40. import GeminiLLMOptions from "@/components/LLMSelection/GeminiLLMOptions";
  41. import OllamaLLMOptions from "@/components/LLMSelection/OllamaLLMOptions";
  42. import NovitaLLMOptions from "@/components/LLMSelection/NovitaLLMOptions";
  43. import TogetherAiOptions from "@/components/LLMSelection/TogetherAiOptions";
  44. import FireworksAiOptions from "@/components/LLMSelection/FireworksAiOptions";
  45. import MistralOptions from "@/components/LLMSelection/MistralOptions";
  46. import HuggingFaceOptions from "@/components/LLMSelection/HuggingFaceOptions";
  47. import PerplexityOptions from "@/components/LLMSelection/PerplexityOptions";
  48. import OpenRouterOptions from "@/components/LLMSelection/OpenRouterOptions";
  49. import GroqAiOptions from "@/components/LLMSelection/GroqAiOptions";
  50. import CohereAiOptions from "@/components/LLMSelection/CohereAiOptions";
  51. import KoboldCPPOptions from "@/components/LLMSelection/KoboldCPPOptions";
  52. import TextGenWebUIOptions from "@/components/LLMSelection/TextGenWebUIOptions";
  53. import LiteLLMOptions from "@/components/LLMSelection/LiteLLMOptions";
  54. import AWSBedrockLLMOptions from "@/components/LLMSelection/AwsBedrockLLMOptions";
  55. import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions";
  56. import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions";
  57. import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions";
  58. import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions";
  59. import LLMItem from "@/components/LLMSelection/LLMItem";
  60. import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react";
  61. import CTAButton from "@/components/lib/CTAButton";
  62. export const AVAILABLE_LLM_PROVIDERS = [
  63. {
  64. name: "OpenAI",
  65. value: "openai",
  66. logo: OpenAiLogo,
  67. options: (settings) => <OpenAiOptions settings={settings} />,
  68. description: "The standard option for most non-commercial use.",
  69. requiredConfig: ["OpenAiKey"],
  70. },
  71. {
  72. name: "Azure OpenAI",
  73. value: "azure",
  74. logo: AzureOpenAiLogo,
  75. options: (settings) => <AzureAiOptions settings={settings} />,
  76. description: "The enterprise option of OpenAI hosted on Azure services.",
  77. requiredConfig: ["AzureOpenAiEndpoint"],
  78. },
  79. {
  80. name: "Anthropic",
  81. value: "anthropic",
  82. logo: AnthropicLogo,
  83. options: (settings) => <AnthropicAiOptions settings={settings} />,
  84. description: "A friendly AI Assistant hosted by Anthropic.",
  85. requiredConfig: ["AnthropicApiKey"],
  86. },
  87. {
  88. name: "Gemini",
  89. value: "gemini",
  90. logo: GeminiLogo,
  91. options: (settings) => <GeminiLLMOptions settings={settings} />,
  92. description: "Google's largest and most capable AI model",
  93. requiredConfig: ["GeminiLLMApiKey"],
  94. },
  95. {
  96. name: "NVIDIA NIM",
  97. value: "nvidia-nim",
  98. logo: NvidiaNimLogo,
  99. options: (settings) => <NvidiaNimOptions settings={settings} />,
  100. description:
  101. "Run full parameter LLMs directly on your NVIDIA RTX GPU using NVIDIA NIM.",
  102. requiredConfig: ["NvidiaNimLLMBasePath"],
  103. },
  104. {
  105. name: "HuggingFace",
  106. value: "huggingface",
  107. logo: HuggingFaceLogo,
  108. options: (settings) => <HuggingFaceOptions settings={settings} />,
  109. description:
  110. "Access 150,000+ open-source LLMs and the world's AI community",
  111. requiredConfig: [
  112. "HuggingFaceLLMEndpoint",
  113. "HuggingFaceLLMAccessToken",
  114. "HuggingFaceLLMTokenLimit",
  115. ],
  116. },
  117. {
  118. name: "Ollama",
  119. value: "ollama",
  120. logo: OllamaLogo,
  121. options: (settings) => <OllamaLLMOptions settings={settings} />,
  122. description: "Run LLMs locally on your own machine.",
  123. requiredConfig: ["OllamaLLMBasePath"],
  124. },
  125. {
  126. name: "Novita AI",
  127. value: "novita",
  128. logo: NovitaLogo,
  129. options: (settings) => <NovitaLLMOptions settings={settings} />,
  130. description:
  131. "Reliable, Scalable, and Cost-Effective for LLMs from Novita AI",
  132. requiredConfig: ["NovitaLLMApiKey"],
  133. },
  134. {
  135. name: "LM Studio",
  136. value: "lmstudio",
  137. logo: LMStudioLogo,
  138. options: (settings) => <LMStudioOptions settings={settings} />,
  139. description:
  140. "Discover, download, and run thousands of cutting edge LLMs in a few clicks.",
  141. requiredConfig: ["LMStudioBasePath"],
  142. },
  143. {
  144. name: "Local AI",
  145. value: "localai",
  146. logo: LocalAiLogo,
  147. options: (settings) => <LocalAiOptions settings={settings} />,
  148. description: "Run LLMs locally on your own machine.",
  149. requiredConfig: ["LocalAiApiKey", "LocalAiBasePath", "LocalAiTokenLimit"],
  150. },
  151. {
  152. name: "Together AI",
  153. value: "togetherai",
  154. logo: TogetherAILogo,
  155. options: (settings) => <TogetherAiOptions settings={settings} />,
  156. description: "Run open source models from Together AI.",
  157. requiredConfig: ["TogetherAiApiKey"],
  158. },
  159. {
  160. name: "Fireworks AI",
  161. value: "fireworksai",
  162. logo: FireworksAILogo,
  163. options: (settings) => <FireworksAiOptions settings={settings} />,
  164. description:
  165. "The fastest and most efficient inference engine to build production-ready, compound AI systems.",
  166. requiredConfig: ["FireworksAiLLMApiKey"],
  167. },
  168. {
  169. name: "Mistral",
  170. value: "mistral",
  171. logo: MistralLogo,
  172. options: (settings) => <MistralOptions settings={settings} />,
  173. description: "Run open source models from Mistral AI.",
  174. requiredConfig: ["MistralApiKey"],
  175. },
  176. {
  177. name: "Perplexity AI",
  178. value: "perplexity",
  179. logo: PerplexityLogo,
  180. options: (settings) => <PerplexityOptions settings={settings} />,
  181. description:
  182. "Run powerful and internet-connected models hosted by Perplexity AI.",
  183. requiredConfig: ["PerplexityApiKey"],
  184. },
  185. {
  186. name: "OpenRouter",
  187. value: "openrouter",
  188. logo: OpenRouterLogo,
  189. options: (settings) => <OpenRouterOptions settings={settings} />,
  190. description: "A unified interface for LLMs.",
  191. requiredConfig: ["OpenRouterApiKey"],
  192. },
  193. {
  194. name: "Groq",
  195. value: "groq",
  196. logo: GroqLogo,
  197. options: (settings) => <GroqAiOptions settings={settings} />,
  198. description:
  199. "The fastest LLM inferencing available for real-time AI applications.",
  200. requiredConfig: ["GroqApiKey"],
  201. },
  202. {
  203. name: "KoboldCPP",
  204. value: "koboldcpp",
  205. logo: KoboldCPPLogo,
  206. options: (settings) => <KoboldCPPOptions settings={settings} />,
  207. description: "Run local LLMs using koboldcpp.",
  208. requiredConfig: [
  209. "KoboldCPPModelPref",
  210. "KoboldCPPBasePath",
  211. "KoboldCPPTokenLimit",
  212. ],
  213. },
  214. {
  215. name: "Oobabooga Web UI",
  216. value: "textgenwebui",
  217. logo: TextGenWebUILogo,
  218. options: (settings) => <TextGenWebUIOptions settings={settings} />,
  219. description: "Run local LLMs using Oobabooga's Text Generation Web UI.",
  220. requiredConfig: ["TextGenWebUIBasePath", "TextGenWebUITokenLimit"],
  221. },
  222. {
  223. name: "Cohere",
  224. value: "cohere",
  225. logo: CohereLogo,
  226. options: (settings) => <CohereAiOptions settings={settings} />,
  227. description: "Run Cohere's powerful Command models.",
  228. requiredConfig: ["CohereApiKey"],
  229. },
  230. {
  231. name: "LiteLLM",
  232. value: "litellm",
  233. logo: LiteLLMLogo,
  234. options: (settings) => <LiteLLMOptions settings={settings} />,
  235. description: "Run LiteLLM's OpenAI compatible proxy for various LLMs.",
  236. requiredConfig: ["LiteLLMBasePath"],
  237. },
  238. {
  239. name: "DeepSeek",
  240. value: "deepseek",
  241. logo: DeepSeekLogo,
  242. options: (settings) => <DeepSeekOptions settings={settings} />,
  243. description: "Run DeepSeek's powerful LLMs.",
  244. requiredConfig: ["DeepSeekApiKey"],
  245. },
  246. {
  247. name: "AWS Bedrock",
  248. value: "bedrock",
  249. logo: AWSBedrockLogo,
  250. options: (settings) => <AWSBedrockLLMOptions settings={settings} />,
  251. description: "Run powerful foundation models privately with AWS Bedrock.",
  252. requiredConfig: [
  253. "AwsBedrockLLMAccessKeyId",
  254. "AwsBedrockLLMAccessKey",
  255. "AwsBedrockLLMRegion",
  256. "AwsBedrockLLMModel",
  257. ],
  258. },
  259. {
  260. name: "APIpie",
  261. value: "apipie",
  262. logo: APIPieLogo,
  263. options: (settings) => <ApiPieLLMOptions settings={settings} />,
  264. description: "A unified API of AI services from leading providers",
  265. requiredConfig: ["ApipieLLMApiKey", "ApipieLLMModelPref"],
  266. },
  267. {
  268. name: "Generic OpenAI",
  269. value: "generic-openai",
  270. logo: GenericOpenAiLogo,
  271. options: (settings) => <GenericOpenAiOptions settings={settings} />,
  272. description:
  273. "Connect to any OpenAi-compatible service via a custom configuration",
  274. requiredConfig: [
  275. "GenericOpenAiBasePath",
  276. "GenericOpenAiModelPref",
  277. "GenericOpenAiTokenLimit",
  278. "GenericOpenAiKey",
  279. ],
  280. },
  281. {
  282. name: "xAI",
  283. value: "xai",
  284. logo: XAILogo,
  285. options: (settings) => <XAILLMOptions settings={settings} />,
  286. description: "Run xAI's powerful LLMs like Grok-2 and more.",
  287. requiredConfig: ["XAIApiKey", "XAIModelPref"],
  288. },
  289. ];
  290. export default function GeneralLLMPreference() {
  291. const [saving, setSaving] = useState(false);
  292. const [hasChanges, setHasChanges] = useState(false);
  293. const [settings, setSettings] = useState(null);
  294. const [loading, setLoading] = useState(true);
  295. const [searchQuery, setSearchQuery] = useState("");
  296. const [filteredLLMs, setFilteredLLMs] = useState([]);
  297. const [selectedLLM, setSelectedLLM] = useState(null);
  298. const [searchMenuOpen, setSearchMenuOpen] = useState(false);
  299. const searchInputRef = useRef(null);
  300. const { t } = useTranslation();
  301. const handleSubmit = async (e) => {
  302. e.preventDefault();
  303. const form = e.target;
  304. const data = { LLMProvider: selectedLLM };
  305. const formData = new FormData(form);
  306. for (var [key, value] of formData.entries()) data[key] = value;
  307. const { error } = await System.updateSystem(data);
  308. setSaving(true);
  309. if (error) {
  310. showToast(`Failed to save LLM settings: ${error}`, "error");
  311. } else {
  312. showToast("LLM preferences saved successfully.", "success");
  313. }
  314. setSaving(false);
  315. setHasChanges(!!error);
  316. };
  317. const updateLLMChoice = (selection) => {
  318. setSearchQuery("");
  319. setSelectedLLM(selection);
  320. setSearchMenuOpen(false);
  321. setHasChanges(true);
  322. };
  323. const handleXButton = () => {
  324. if (searchQuery.length > 0) {
  325. setSearchQuery("");
  326. if (searchInputRef.current) searchInputRef.current.value = "";
  327. } else {
  328. setSearchMenuOpen(!searchMenuOpen);
  329. }
  330. };
  331. useEffect(() => {
  332. async function fetchKeys() {
  333. const _settings = await System.keys();
  334. setSettings(_settings);
  335. setSelectedLLM(_settings?.LLMProvider);
  336. setLoading(false);
  337. }
  338. fetchKeys();
  339. }, []);
  340. useEffect(() => {
  341. const filtered = AVAILABLE_LLM_PROVIDERS.filter((llm) =>
  342. llm.name.toLowerCase().includes(searchQuery.toLowerCase())
  343. );
  344. setFilteredLLMs(filtered);
  345. }, [searchQuery, selectedLLM]);
  346. const selectedLLMObject = AVAILABLE_LLM_PROVIDERS.find(
  347. (llm) => llm.value === selectedLLM
  348. );
  349. return (
  350. <div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
  351. <Sidebar />
  352. {loading ? (
  353. <div
  354. style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
  355. className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll p-4 md:p-0"
  356. >
  357. <div className="w-full h-full flex justify-center items-center">
  358. <PreLoader />
  359. </div>
  360. </div>
  361. ) : (
  362. <div
  363. style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
  364. className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll p-4 md:p-0"
  365. >
  366. <form onSubmit={handleSubmit} className="flex w-full">
  367. <div className="flex flex-col w-full px-1 md:pl-6 md:pr-[50px] md:py-6 py-16">
  368. <div className="w-full flex flex-col gap-y-1 pb-6 border-white light:border-theme-sidebar-border border-b-2 border-opacity-10">
  369. <div className="flex gap-x-4 items-center">
  370. <p className="text-lg leading-6 font-bold text-white">
  371. {t("llm.title")}
  372. </p>
  373. </div>
  374. <p className="text-xs leading-[18px] font-base text-white text-opacity-60">
  375. {t("llm.description")}
  376. </p>
  377. </div>
  378. <div className="w-full justify-end flex">
  379. {hasChanges && (
  380. <CTAButton
  381. onClick={() => handleSubmit()}
  382. className="mt-3 mr-0 -mb-14 z-10"
  383. >
  384. {saving ? "Saving..." : "Save changes"}
  385. </CTAButton>
  386. )}
  387. </div>
  388. <div className="text-base font-bold text-white mt-6 mb-4">
  389. {t("llm.provider")}
  390. </div>
  391. <div className="relative">
  392. {searchMenuOpen && (
  393. <div
  394. className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-70 backdrop-blur-sm z-10"
  395. onClick={() => setSearchMenuOpen(false)}
  396. />
  397. )}
  398. {searchMenuOpen ? (
  399. <div className="absolute top-0 left-0 w-full max-w-[640px] max-h-[310px] overflow-auto white-scrollbar min-h-[64px] bg-theme-settings-input-bg rounded-lg flex flex-col justify-between cursor-pointer border-2 border-primary-button z-20">
  400. <div className="w-full flex flex-col gap-y-1">
  401. <div className="flex items-center sticky top-0 border-b border-[#9CA3AF] mx-4 bg-theme-settings-input-bg">
  402. <MagnifyingGlass
  403. size={20}
  404. weight="bold"
  405. className="absolute left-4 z-30 text-theme-text-primary -ml-4 my-2"
  406. />
  407. <input
  408. type="text"
  409. name="llm-search"
  410. autoComplete="off"
  411. placeholder="Search all LLM providers"
  412. className="border-none -ml-4 my-2 bg-transparent z-20 pl-12 h-[38px] w-full px-4 py-1 text-sm outline-none text-theme-text-primary placeholder:text-theme-text-primary placeholder:font-medium"
  413. onChange={(e) => setSearchQuery(e.target.value)}
  414. ref={searchInputRef}
  415. onKeyDown={(e) => {
  416. if (e.key === "Enter") e.preventDefault();
  417. }}
  418. />
  419. <X
  420. size={20}
  421. weight="bold"
  422. className="cursor-pointer text-white hover:text-x-button"
  423. onClick={handleXButton}
  424. />
  425. </div>
  426. <div className="flex-1 pl-4 pr-2 flex flex-col gap-y-1 overflow-y-auto white-scrollbar pb-4">
  427. {filteredLLMs.map((llm) => {
  428. return (
  429. <LLMItem
  430. key={llm.name}
  431. name={llm.name}
  432. value={llm.value}
  433. image={llm.logo}
  434. description={llm.description}
  435. checked={selectedLLM === llm.value}
  436. onClick={() => updateLLMChoice(llm.value)}
  437. />
  438. );
  439. })}
  440. </div>
  441. </div>
  442. </div>
  443. ) : (
  444. <button
  445. className="w-full max-w-[640px] h-[64px] bg-theme-settings-input-bg rounded-lg flex items-center p-[14px] justify-between cursor-pointer border-2 border-transparent hover:border-primary-button transition-all duration-300"
  446. type="button"
  447. onClick={() => setSearchMenuOpen(true)}
  448. >
  449. <div className="flex gap-x-4 items-center">
  450. <img
  451. src={selectedLLMObject?.logo || AnythingLLMIcon}
  452. alt={`${selectedLLMObject?.name} logo`}
  453. className="w-10 h-10 rounded-md"
  454. />
  455. <div className="flex flex-col text-left">
  456. <div className="text-sm font-semibold text-white">
  457. {selectedLLMObject?.name || "暂无选择"}
  458. </div>
  459. <div className="mt-1 text-xs text-description">
  460. {selectedLLMObject?.description ||
  461. "你需要选择一个大模型"}
  462. </div>
  463. </div>
  464. </div>
  465. <CaretUpDown
  466. size={24}
  467. weight="bold"
  468. className="text-white"
  469. />
  470. </button>
  471. )}
  472. </div>
  473. <div
  474. onChange={() => setHasChanges(true)}
  475. className="mt-4 flex flex-col gap-y-1"
  476. >
  477. {selectedLLM &&
  478. AVAILABLE_LLM_PROVIDERS.find(
  479. (llm) => llm.value === selectedLLM
  480. )?.options?.(settings)}
  481. </div>
  482. </div>
  483. </form>
  484. </div>
  485. )}
  486. </div>
  487. );
  488. }