Feat: more settings for ai engine & models (#611)
* may setup gpt ai engine & model * ai models setting works * update openai setting * validate engine setting before save * fail fast * clean code * refactor gpt preset
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import * as z from "zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
Select,
|
||||
@@ -6,47 +9,314 @@ import {
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
toast,
|
||||
Form,
|
||||
Button,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@renderer/components/ui";
|
||||
import { AISettingsProviderContext } from "@renderer/context";
|
||||
import { useContext } from "react";
|
||||
import {
|
||||
AISettingsProviderContext,
|
||||
AppSettingsProviderContext,
|
||||
} from "@renderer/context";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { GPT_PROVIDERS } from "@renderer/components";
|
||||
|
||||
export const DefaultEngineSettings = () => {
|
||||
const { defaultEngine, setDefaultEngine, openai } = useContext(
|
||||
const { currentEngine, setGptEngine, openai } = useContext(
|
||||
AISettingsProviderContext
|
||||
);
|
||||
const { webApi } = useContext(AppSettingsProviderContext);
|
||||
const [providers, setProviders] = useState<any>(GPT_PROVIDERS);
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const gptEngineSchema = z
|
||||
.object({
|
||||
name: z.enum(["enjoyai", "openai"]),
|
||||
models: z.object({
|
||||
default: z.string(),
|
||||
lookup: z.string().optional(),
|
||||
translate: z.string().optional(),
|
||||
analyze: z.string().optional(),
|
||||
extractStory: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
.required();
|
||||
|
||||
const form = useForm<z.infer<typeof gptEngineSchema>>({
|
||||
resolver: zodResolver(gptEngineSchema),
|
||||
values: {
|
||||
name: currentEngine.name as "enjoyai" | "openai",
|
||||
models: currentEngine.models || {},
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof gptEngineSchema>) => {
|
||||
const { name, models } = data;
|
||||
|
||||
models.default ||= providers[name].models[0];
|
||||
Object.keys(models).forEach((key: keyof typeof models) => {
|
||||
if (!providers[name].models.includes(models[key])) {
|
||||
delete models[key];
|
||||
}
|
||||
});
|
||||
|
||||
setGptEngine(data as GptEngineSettingType);
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
webApi
|
||||
.config("gpt_providers")
|
||||
.then((data) => {
|
||||
setProviders(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex items-start justify-between py-4">
|
||||
<div className="">
|
||||
<div className="flex items-center mb-2">
|
||||
<span>{t("defaultAiEngine")}</span>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{defaultEngine === "openai" && t("openAiEngineTips")}
|
||||
{defaultEngine === "enjoyai" && t("enjoyAiEngineTips")}
|
||||
</div>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="flex items-start justify-between py-4">
|
||||
<div className="">
|
||||
<div className="flex items-center mb-2">
|
||||
<span>{t("defaultAiEngine")}</span>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground space-y-3">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
{t("aiEngine")}:
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
disabled={!editing}
|
||||
onValueChange={(value) => {
|
||||
if (value === "openai" && !openai?.key) {
|
||||
toast.warning(t("openaiKeyRequired"));
|
||||
} else {
|
||||
field.onChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="min-w-fit">
|
||||
<SelectValue
|
||||
placeholder={t("defaultAiEngine")}
|
||||
></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="enjoyai">EnjoyAI</SelectItem>
|
||||
<SelectItem value="openai">OpenAI</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<FormMessage />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{form.watch("name") === "openai" && t("openAiEngineTips")}
|
||||
{form.watch("name") === "enjoyai" &&
|
||||
t("enjoyAiEngineTips")}
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="models.default"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
{t("defaultAiModel")}:
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
disabled={!editing}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger className="min-w-fit">
|
||||
<SelectValue
|
||||
placeholder={t("defaultAiModel")}
|
||||
></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{providers[form.watch("name")].models.map(
|
||||
(model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{editing && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="models.lookup"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
{t("lookupAiModel")}:
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
disabled={!editing}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger className="min-w-fit">
|
||||
<SelectValue
|
||||
placeholder={t("leaveEmptyToUseDefault")}
|
||||
></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{providers[form.watch("name")].models.map(
|
||||
(model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="models.translate"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
{t("translateAiModel")}:
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
disabled={!editing}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger className="min-w-fit">
|
||||
<SelectValue
|
||||
placeholder={t("leaveEmptyToUseDefault")}
|
||||
></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{providers[form.watch("name")].models.map(
|
||||
(model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="models.analyze"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
{t("analyzeAiModel")}:
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
disabled={!editing}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger className="min-w-fit">
|
||||
<SelectValue
|
||||
placeholder={t("leaveEmptyToUseDefault")}
|
||||
></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{providers[form.watch("name")].models.map(
|
||||
(model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="models.extractStory"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">
|
||||
{t("extractStoryAiModel")}:
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
disabled={!editing}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<SelectTrigger className="min-w-fit">
|
||||
<SelectValue
|
||||
placeholder={t("leaveEmptyToUseDefault")}
|
||||
></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{providers[form.watch("name")].models.map(
|
||||
(model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Select
|
||||
value={defaultEngine}
|
||||
onValueChange={(value) => {
|
||||
if (value === "openai" && !openai?.key) {
|
||||
toast.warning(t("openaiKeyRequired"));
|
||||
} else {
|
||||
setDefaultEngine(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="min-w-fit">
|
||||
<SelectValue placeholder={t("defaultAiEngine")}></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="enjoyai">EnjoyAI</SelectItem>
|
||||
<SelectItem value="openai">OpenAI</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant={editing ? "outline" : "secondary"}
|
||||
size="sm"
|
||||
type="reset"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
form.reset();
|
||||
setEditing(!editing);
|
||||
}}
|
||||
>
|
||||
{editing ? t("cancel") : t("edit")}
|
||||
</Button>
|
||||
<Button className={editing ? "" : "hidden"} size="sm" type="submit">
|
||||
{t("save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ export const GoogleGenerativeAiSettings = () => {
|
||||
ref={ref}
|
||||
type="password"
|
||||
defaultValue={googleGenerativeAi?.key}
|
||||
placeholder="*********"
|
||||
placeholder=""
|
||||
disabled={!editing}
|
||||
className="focus-visible:outline-0 focus-visible:ring-0 shadow-none"
|
||||
/>
|
||||
|
||||
@@ -8,17 +8,10 @@ import {
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormMessage,
|
||||
Input,
|
||||
toast,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
} from "@renderer/components/ui";
|
||||
import { GPT_PROVIDERS } from "@renderer/components";
|
||||
import { AISettingsProviderContext } from "@renderer/context";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
@@ -28,7 +21,6 @@ export const OpenaiSettings = () => {
|
||||
|
||||
const openAiConfigSchema = z.object({
|
||||
key: z.string().optional(),
|
||||
model: z.enum(GPT_PROVIDERS.openai.models),
|
||||
baseUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -36,7 +28,6 @@ export const OpenaiSettings = () => {
|
||||
resolver: zodResolver(openAiConfigSchema),
|
||||
values: {
|
||||
key: openai?.key,
|
||||
model: openai?.model,
|
||||
baseUrl: openai?.baseUrl,
|
||||
},
|
||||
});
|
||||
@@ -55,7 +46,7 @@ export const OpenaiSettings = () => {
|
||||
<div className="flex items-start justify-between py-4">
|
||||
<div className="">
|
||||
<div className="mb-2">Open AI</div>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div className="text-sm text-muted-foreground space-y-3">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="key"
|
||||
@@ -66,7 +57,7 @@ export const OpenaiSettings = () => {
|
||||
<Input
|
||||
disabled={!editing}
|
||||
type="password"
|
||||
placeholder="*********"
|
||||
placeholder=""
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
@@ -75,45 +66,15 @@ export const OpenaiSettings = () => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="model"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">{t("model")}:</FormLabel>
|
||||
<Select
|
||||
disabled={!editing}
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("selectAiModel")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(GPT_PROVIDERS.openai.models || []).map(
|
||||
(option: string) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="baseUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="min-w-max">{t("baseUrl")}:</FormLabel>
|
||||
<FormLabel className="min-w-max">
|
||||
{t("baseUrl")}:
|
||||
</FormLabel>
|
||||
<Input
|
||||
disabled={!editing}
|
||||
placeholder={t("leaveEmptyToUseDefault")}
|
||||
|
||||
@@ -29,8 +29,6 @@ export const Preferences = () => {
|
||||
<div className="font-semibold mb-4 capitilized">
|
||||
{t("basicSettings")}
|
||||
</div>
|
||||
<LibrarySettings />
|
||||
<Separator />
|
||||
<WhisperSettings />
|
||||
<Separator />
|
||||
<DefaultEngineSettings />
|
||||
@@ -69,6 +67,8 @@ export const Preferences = () => {
|
||||
</div>
|
||||
<UserSettings />
|
||||
<Separator />
|
||||
<LibrarySettings />
|
||||
<Separator />
|
||||
<EmailSettings />
|
||||
<Separator />
|
||||
<BalanceSettings />
|
||||
|
||||
Reference in New Issue
Block a user