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,5 +1,5 @@
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { RESPONSE_JSON_FORMAT_MODELS } from "@/constants";
|
||||
import { NOT_SUPPORTE_JSON_FORMAT_MODELS } from "@/constants";
|
||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
||||
|
||||
export const jsonCommand = async (
|
||||
@@ -15,7 +15,7 @@ export const jsonCommand = async (
|
||||
const { key, temperature = 0, baseUrl, schema } = options;
|
||||
let { modelName = "gpt-4o" } = options;
|
||||
|
||||
if (RESPONSE_JSON_FORMAT_MODELS.indexOf(modelName) === -1) {
|
||||
if (NOT_SUPPORTE_JSON_FORMAT_MODELS.indexOf(modelName) > -1) {
|
||||
modelName = "gpt-4o";
|
||||
}
|
||||
|
||||
|
||||
@@ -125,16 +125,10 @@ export const PROCESS_TIMEOUT = 1000 * 60 * 15;
|
||||
export const AI_GATEWAY_ENDPOINT =
|
||||
"https://gateway.ai.cloudflare.com/v1/11d43ab275eb7e1b271ba4089ecc3864/enjoy";
|
||||
|
||||
export const RESPONSE_JSON_FORMAT_MODELS = [
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-1106",
|
||||
"gpt-4o",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-turbo-2024-04-09",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-1106-preview",
|
||||
export const NOT_SUPPORTE_JSON_FORMAT_MODELS = [
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4",
|
||||
"gpt-4-32k",
|
||||
];
|
||||
|
||||
export const CONVERSATION_PRESETS = [
|
||||
|
||||
@@ -368,6 +368,12 @@
|
||||
"whisperModelIsNotWorking": "Whisper model is not working",
|
||||
"relaunchIsNeededAfterChanged": "Relaunch is needed after changed",
|
||||
"defaultAiEngine": "Default AI engine",
|
||||
"aiEngine": "AI engine",
|
||||
"defaultAiModel": "Default AI model",
|
||||
"lookupAiModel": "AI Lookup",
|
||||
"translateAiModel": "AI Translate",
|
||||
"analyzeAiModel": "AI Analyze",
|
||||
"extractStoryAiModel": "AI extract vocabulary",
|
||||
"openAiEngineTips": "Use OpenAI with your own key as default AI engine.",
|
||||
"enjoyAiEngineTips": "Use EnjoyAI as default AI engine. It is a paid service.",
|
||||
"openaiKeySaved": "OpenAI key saved",
|
||||
|
||||
@@ -368,6 +368,12 @@
|
||||
"whisperModelIsNotWorking": "Whisper 模型无法正常工作,请尝试更换模型后重试,或联系开发者",
|
||||
"relaunchIsNeededAfterChanged": "更改后需要重新启动",
|
||||
"defaultAiEngine": "默认 AI 引擎",
|
||||
"aiEngine": "AI 引擎",
|
||||
"defaultAiModel": "默认 AI 模型",
|
||||
"lookupAiModel": "智能词典模型",
|
||||
"translateAiModel": "智能翻译模型",
|
||||
"analyzeAiModel": "句子分析模型",
|
||||
"extractStoryAiModel": "提取生词模型",
|
||||
"openAiEngineTips": "使用 OpenAI 作为默认 AI 引擎,需要配置 API 密钥。",
|
||||
"enjoyAiEngineTips": "使用 EnjoyAI 作为默认 AI 引擎,收费服务。",
|
||||
"openaiKeySaved": "OpenAI 密钥已保存",
|
||||
|
||||
@@ -154,6 +154,14 @@ export default {
|
||||
return settings.setSync("defaultEngine", engine);
|
||||
});
|
||||
|
||||
ipcMain.handle("settings-get-gpt-engine", (_event) => {
|
||||
return settings.getSync("engine.gpt");
|
||||
});
|
||||
|
||||
ipcMain.handle("settings-set-gpt-engine", (_event, engine) => {
|
||||
return settings.setSync("engine.gpt", engine);
|
||||
});
|
||||
|
||||
ipcMain.handle("settings-get-default-hotkeys", (_event) => {
|
||||
return settings.getSync("defaultHotkeys");
|
||||
});
|
||||
|
||||
@@ -177,6 +177,12 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
|
||||
setDefaultEngine: (engine: "enjoyai" | "openai") => {
|
||||
return ipcRenderer.invoke("settings-set-default-engine", engine);
|
||||
},
|
||||
getGptEngine: () => {
|
||||
return ipcRenderer.invoke("settings-get-gpt-engine");
|
||||
},
|
||||
setGptEngine: (engine: GptEngineSettingType) => {
|
||||
return ipcRenderer.invoke("settings-set-gpt-engine", engine);
|
||||
},
|
||||
getLlm: (provider: string) => {
|
||||
return ipcRenderer.invoke("settings-get-llm", provider);
|
||||
},
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -10,9 +10,8 @@ type AISettingsProviderState = {
|
||||
setOpenai?: (config: LlmProviderType) => void;
|
||||
googleGenerativeAi?: LlmProviderType;
|
||||
setGoogleGenerativeAi?: (config: LlmProviderType) => void;
|
||||
defaultEngine?: string;
|
||||
setDefaultEngine?: (engine: string) => void;
|
||||
currentEngine?: LlmProviderType;
|
||||
setGptEngine?: (engine: GptEngineSettingType) => void;
|
||||
currentEngine?: GptEngineSettingType;
|
||||
};
|
||||
|
||||
const initialState: AISettingsProviderState = {};
|
||||
@@ -25,12 +24,17 @@ export const AISettingsProvider = ({
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [defaultEngine, setDefaultEngine] = useState<string>("openai");
|
||||
const [gptEngine, setGptEngine] = useState<GptEngineSettingType>({
|
||||
name: "enjoyai",
|
||||
models: {
|
||||
default: "gpt-4o",
|
||||
},
|
||||
});
|
||||
const [openai, setOpenai] = useState<LlmProviderType>(null);
|
||||
const [googleGenerativeAi, setGoogleGenerativeAi] =
|
||||
useState<LlmProviderType>(null);
|
||||
const [whisperConfig, setWhisperConfig] = useState<WhisperConfigType>(null);
|
||||
const { EnjoyApp, apiUrl, user, libraryPath } = useContext(
|
||||
const { EnjoyApp, libraryPath, user, apiUrl } = useContext(
|
||||
AppSettingsProviderContext
|
||||
);
|
||||
|
||||
@@ -79,15 +83,39 @@ export const AISettingsProvider = ({
|
||||
}
|
||||
|
||||
const _defaultEngine = await EnjoyApp.settings.getDefaultEngine();
|
||||
if (_defaultEngine) {
|
||||
setDefaultEngine(_defaultEngine);
|
||||
const _gptEngine = await EnjoyApp.settings.getGptEngine();
|
||||
if (_gptEngine) {
|
||||
setGptEngine(_gptEngine);
|
||||
} else if (_defaultEngine) {
|
||||
// Migrate default engine to gpt engine
|
||||
const engine = {
|
||||
name: _defaultEngine,
|
||||
models: {
|
||||
default: "gpt-4o",
|
||||
},
|
||||
};
|
||||
EnjoyApp.settings.setGptEngine(engine).then(() => {
|
||||
setGptEngine(engine);
|
||||
});
|
||||
} else if (_openai?.key) {
|
||||
EnjoyApp.settings.setDefaultEngine("openai").then(() => {
|
||||
setDefaultEngine("openai");
|
||||
const engine = {
|
||||
name: "openai",
|
||||
models: {
|
||||
default: "gpt-4o",
|
||||
},
|
||||
};
|
||||
EnjoyApp.settings.setGptEngine(engine).then(() => {
|
||||
setGptEngine(engine);
|
||||
});
|
||||
} else {
|
||||
EnjoyApp.settings.setDefaultEngine("enjoyai").then(() => {
|
||||
setDefaultEngine("enjoyai");
|
||||
const engine = {
|
||||
name: "enjoyai",
|
||||
models: {
|
||||
default: "gpt-4o",
|
||||
},
|
||||
};
|
||||
EnjoyApp.settings.setGptEngine(engine).then(() => {
|
||||
setGptEngine(engine);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -114,20 +142,21 @@ export const AISettingsProvider = ({
|
||||
return (
|
||||
<AISettingsProviderContext.Provider
|
||||
value={{
|
||||
defaultEngine,
|
||||
setDefaultEngine: (engine: "openai" | "enjoyai") => {
|
||||
EnjoyApp.settings.setDefaultEngine(engine).then(() => {
|
||||
setDefaultEngine(engine);
|
||||
setGptEngine: (engine: GptEngineSettingType) => {
|
||||
EnjoyApp.settings.setGptEngine(engine).then(() => {
|
||||
setGptEngine(engine);
|
||||
});
|
||||
},
|
||||
currentEngine: {
|
||||
openai: openai,
|
||||
enjoyai: {
|
||||
name: "enjoyai" as LlmProviderType["name"],
|
||||
key: user?.accessToken,
|
||||
baseUrl: `${apiUrl}/api/ai`,
|
||||
},
|
||||
}[defaultEngine],
|
||||
currentEngine:
|
||||
gptEngine.name === "openai"
|
||||
? Object.assign(gptEngine, {
|
||||
key: openai.key,
|
||||
baseUrl: openai.baseUrl,
|
||||
})
|
||||
: Object.assign(gptEngine, {
|
||||
key: user?.accessToken,
|
||||
baseUrl: `${apiUrl}/api/ai`,
|
||||
}),
|
||||
openai,
|
||||
setOpenai: (config: LlmProviderType) => handleSetLlm("openai", config),
|
||||
googleGenerativeAi,
|
||||
|
||||
@@ -38,6 +38,9 @@ export const useAiCommand = () => {
|
||||
return lookup;
|
||||
}
|
||||
|
||||
const modelName =
|
||||
currentEngine.models.lookup || currentEngine.models.default;
|
||||
|
||||
const res = await lookupCommand(
|
||||
{
|
||||
word,
|
||||
@@ -46,12 +49,13 @@ export const useAiCommand = () => {
|
||||
},
|
||||
{
|
||||
key: currentEngine.key,
|
||||
modelName: currentEngine.model,
|
||||
modelName,
|
||||
baseUrl: currentEngine.baseUrl,
|
||||
}
|
||||
);
|
||||
|
||||
if (res.context_translation?.trim()) {
|
||||
// Accept result from gpt-3/4 models
|
||||
if (modelName.match(/^gpt-(3|4)\S*/i) && res.context_translation?.trim()) {
|
||||
return webApi.updateLookup(lookup.id, {
|
||||
meaning: res,
|
||||
sourceId,
|
||||
@@ -61,17 +65,17 @@ export const useAiCommand = () => {
|
||||
};
|
||||
|
||||
const extractStory = async (story: StoryType) => {
|
||||
return extractStoryCommand(story.content, {
|
||||
const res = await extractStoryCommand(story.content, {
|
||||
key: currentEngine.key,
|
||||
modelName: currentEngine.model,
|
||||
modelName:
|
||||
currentEngine.models.extractStory || currentEngine.models.default,
|
||||
baseUrl: currentEngine.baseUrl,
|
||||
}).then((res) => {
|
||||
const { words = [], idioms = [] } = res;
|
||||
});
|
||||
const { words = [], idioms = [] } = res;
|
||||
|
||||
return webApi.extractVocabularyFromStory(story.id, {
|
||||
words,
|
||||
idioms,
|
||||
});
|
||||
return webApi.extractVocabularyFromStory(story.id, {
|
||||
words,
|
||||
idioms,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -81,7 +85,7 @@ export const useAiCommand = () => {
|
||||
): Promise<string> => {
|
||||
return translateCommand(text, {
|
||||
key: currentEngine.key,
|
||||
modelName: currentEngine.model,
|
||||
modelName: currentEngine.models.translate || currentEngine.models.default,
|
||||
baseUrl: currentEngine.baseUrl,
|
||||
}).then((res) => {
|
||||
if (cacheKey) {
|
||||
@@ -92,22 +96,22 @@ export const useAiCommand = () => {
|
||||
};
|
||||
|
||||
const analyzeText = async (text: string, cacheKey?: string) => {
|
||||
return analyzeCommand(text, {
|
||||
const res = await analyzeCommand(text, {
|
||||
key: currentEngine.key,
|
||||
modelName: currentEngine.model,
|
||||
modelName: currentEngine.models.analyze || currentEngine.models.default,
|
||||
baseUrl: currentEngine.baseUrl,
|
||||
}).then((res) => {
|
||||
if (cacheKey) {
|
||||
EnjoyApp.cacheObjects.set(cacheKey, res);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
if (cacheKey) {
|
||||
EnjoyApp.cacheObjects.set(cacheKey, res);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
const punctuateText = async (text: string) => {
|
||||
return punctuateCommand(text, {
|
||||
key: currentEngine.key,
|
||||
modelName: currentEngine.model,
|
||||
modelName: currentEngine.models.default,
|
||||
baseUrl: currentEngine.baseUrl,
|
||||
});
|
||||
};
|
||||
@@ -115,7 +119,7 @@ export const useAiCommand = () => {
|
||||
const summarizeTopic = async (text: string) => {
|
||||
return summarizeTopicCommand(text, {
|
||||
key: currentEngine.key,
|
||||
modelName: currentEngine.model,
|
||||
modelName: currentEngine.models.default,
|
||||
baseUrl: currentEngine.baseUrl,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ export const useConversation = () => {
|
||||
configuration: {
|
||||
baseURL: `${apiUrl}/api/ai`,
|
||||
},
|
||||
maxRetries: 2,
|
||||
maxRetries: 0,
|
||||
modelName: model,
|
||||
temperature,
|
||||
maxTokens,
|
||||
@@ -55,7 +55,7 @@ export const useConversation = () => {
|
||||
configuration: {
|
||||
baseURL: baseUrl || openai.baseUrl,
|
||||
},
|
||||
maxRetries: 2,
|
||||
maxRetries: 0,
|
||||
modelName: model,
|
||||
temperature,
|
||||
maxTokens,
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
} from "@renderer/context";
|
||||
import { conversationsReducer } from "@renderer/reducers";
|
||||
import { CONVERSATION_PRESETS } from "@/constants";
|
||||
import { set } from "lodash";
|
||||
|
||||
export default () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
@@ -124,9 +123,10 @@ export default () => {
|
||||
name: t("custom"),
|
||||
configuration: {
|
||||
type: "gpt",
|
||||
engine: currentEngine?.name || "enjoyai",
|
||||
engine: currentEngine.name,
|
||||
model: currentEngine.models.default,
|
||||
tts: {
|
||||
engine: currentEngine?.name || "enjoyai",
|
||||
engine: currentEngine.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -137,7 +137,7 @@ export default () => {
|
||||
configuration: {
|
||||
type: "tts",
|
||||
tts: {
|
||||
engine: currentEngine?.name || "enjoyai",
|
||||
engine: currentEngine.name,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -150,12 +150,13 @@ export default () => {
|
||||
presets = gptPresets;
|
||||
defaultGpt.key = "custom";
|
||||
defaultGpt.name = t("custom");
|
||||
defaultGpt.engine = currentEngine?.name || "enjoyai";
|
||||
defaultGpt.configuration.tts.engine = currentEngine?.name || "enjoyai";
|
||||
defaultGpt.engine = currentEngine.name;
|
||||
defaultGpt.configuration.model = currentEngine.models.default;
|
||||
defaultGpt.configuration.tts.engine = currentEngine.name;
|
||||
defaultGptPreset = defaultGpt;
|
||||
|
||||
defaultTts.engine = currentEngine?.name || "enjoyai";
|
||||
defaultTts.configuration.tts.engine = currentEngine?.name || "enjoyai";
|
||||
defaultTts.engine = currentEngine.name;
|
||||
defaultTts.configuration.tts.engine = currentEngine.name;
|
||||
defaultTtsPreset = defaultTts;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -166,9 +167,10 @@ export default () => {
|
||||
engine: currentEngine?.name,
|
||||
configuration: {
|
||||
...preset.configuration,
|
||||
model: currentEngine.models.default,
|
||||
tts: {
|
||||
...preset.configuration.tts,
|
||||
engine: currentEngine?.name,
|
||||
engine: currentEngine.name,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
4
enjoy/src/types/enjoy-app.d.ts
vendored
4
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -111,7 +111,9 @@ type EnjoyAppType = {
|
||||
setUser: (user: UserType) => Promise<void>;
|
||||
getUserDataPath: () => Promise<string>;
|
||||
getDefaultEngine: () => Promise<string>;
|
||||
setDefaultEngine: (engine: "enjoyai" | "openai") => Promise<void>;
|
||||
setDefaultEngine: (string) => Promise<string>;
|
||||
getGptEngine: () => Promise<GptEngineSettingType>;
|
||||
setGptEngine: (GptEngineSettingType) => Promise<GptEngineSettingType>;
|
||||
getLlm: (provider: SupportedLlmProviderType) => Promise<LlmProviderType>;
|
||||
setLlm: (
|
||||
provider: SupportedLlmProviderType,
|
||||
|
||||
13
enjoy/src/types/index.d.ts
vendored
13
enjoy/src/types/index.d.ts
vendored
@@ -161,3 +161,16 @@ type YoutubeVideoType = {
|
||||
videoId: string;
|
||||
duration: string;
|
||||
};
|
||||
|
||||
type GptEngineSettingType = {
|
||||
name: string;
|
||||
models: {
|
||||
default: string;
|
||||
lookup?: string;
|
||||
translate?: string;
|
||||
analyze?: string;
|
||||
extractStory?: string;
|
||||
};
|
||||
baseUrl?: string;
|
||||
key?: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user