import { Group, NumberInput, Select, Skeleton, Slider, Stack, Switch, Text, TextInput, Textarea, } from "@mantine/core"; import { useForm } from "@mantine/form"; import { IconInfoCircle } from "@tabler/icons-react"; import { usePubSub } from "create-pubsub/react"; import { Suspense, lazy, useEffect, useState } from "react"; import { Pattern, match } from "ts-pattern"; import { addLogEntry } from "../../../../modules/logEntries"; import { getOpenAiClient } from "../../../../modules/openai"; import { settingsPubSub } from "../../../../modules/pubSub"; import { defaultSettings, inferenceTypes } from "../../../../modules/settings"; import { isWebGPUAvailable } from "../../../../modules/webGpu"; const WebLlmModelSelect = lazy( () => import("../../../AiResponse/WebLlmModelSelect"), ); const WllamaModelSelect = lazy( () => import("../../../AiResponse/WllamaModelSelect"), ); export default function AISettingsForm() { const [settings, setSettings] = usePubSub(settingsPubSub); const [openAiModels, setOpenAiModels] = useState< { label: string; value: string; }[] >([]); const [openAiApiModelError, setOpenAiApiModelError] = useState< string | undefined >(undefined); const form = useForm({ initialValues: settings, onValuesChange: setSettings, }); useEffect(() => { async function fetchOpenAiModels() { try { const openai = getOpenAiClient({ baseURL: settings.openAiApiBaseUrl, apiKey: settings.openAiApiKey, }); const response = await openai.models.list(); const models = response.data.map((model) => ({ label: model.id, value: model.id, })); setOpenAiModels(models); setOpenAiApiModelError(undefined); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); addLogEntry(`Error fetching OpenAI models: ${errorMessage}`); setOpenAiModels([]); setOpenAiApiModelError(errorMessage); } } if (settings.inferenceType === "openai" && settings.openAiApiBaseUrl) { fetchOpenAiModels(); } }, [ settings.inferenceType, settings.openAiApiBaseUrl, settings.openAiApiKey, ]); useEffect(() => { if (openAiApiModelError === form.errors.openAiApiModel) return; form.setFieldError("openAiApiModel", openAiApiModelError); }, [openAiApiModelError, form.setFieldError, form.errors.openAiApiModel]); useEffect(() => { if (openAiModels.length > 0) { const hasNoModelSelected = !form.values.openAiApiModel; const isModelInvalid = !openAiModels.find( (model) => model.value === form.values.openAiApiModel, ); if (hasNoModelSelected || isModelInvalid) { form.setFieldValue("openAiApiModel", openAiModels[0].value); } } else if (form.values.openAiApiModel) { form.setFieldValue("openAiApiModel", ""); } }, [openAiModels, form.setFieldValue, form.values.openAiApiModel]); const isUsingCustomInstructions = form.values.systemPrompt !== defaultSettings.systemPrompt; const handleRestoreDefaultInstructions = () => { form.setFieldValue("systemPrompt", defaultSettings.systemPrompt); }; const suggestedCpuThreads = (navigator.hardwareConcurrency && Math.max( defaultSettings.cpuThreads, navigator.hardwareConcurrency - 2, )) ?? defaultSettings.cpuThreads; return ( {form.values.enableAiResponse && ( <> Search results to consider Determines the number of search results to consider when generating AI responses. A higher value may enhance accuracy, but it will also increase response time. ({ value: index, label: index.toString(), }))} /> )} {form.values.inferenceType === "browser" && ( <> {isWebGPUAvailable && ( )} {match([isWebGPUAvailable, form.values.enableWebGpu]) .with([true, true], () => ( }> form.setFieldValue("webLlmModelId", value) } /> )) .with([false, Pattern.any], [Pattern.any, false], () => ( <> }> form.setFieldValue("wllamaModelId", value) } /> Number of threads to use for the AI model. Lower values will use less CPU but may take longer to respond. A value that is too high may cause the app to hang. {suggestedCpuThreads > defaultSettings.cpuThreads && ( {" "} The default value is{" "} form.setFieldValue( "cpuThreads", defaultSettings.cpuThreads, ) } > {defaultSettings.cpuThreads} , but based on the number of logical processors in your CPU, the suggested value is{" "} form.setFieldValue( "cpuThreads", suggestedCpuThreads, ) } > {suggestedCpuThreads} . )} } min={1} {...form.getInputProps("cpuThreads")} /> )) .otherwise(() => null)} )}