Feat may set api url (#850)

* add api url settings

* may edit api setting before login
This commit is contained in:
an-lee
2024-07-20 17:18:33 +08:00
committed by GitHub
parent fef5aac7b2
commit 223ae743ed
11 changed files with 242 additions and 58 deletions

View File

@@ -258,6 +258,8 @@
"resetSettingsConfirmation": "It will reset all of your settings, are you sure? The library will not be affected.",
"proxySettings": "Proxy Settings",
"proxyConfigUpdated": "Proxy config updated",
"apiSettings": "API Settings",
"apiUrlUpdated": "API url updated",
"logoutAndRemoveAllPersonalData": "Logout and remove all personal data",
"logoutAndRemoveAllPersonalSettings": "Logout and remove all personal settings",
"hotkeys": "Hotkeys",
@@ -380,6 +382,7 @@
"whisperModelIsWorkingGood": "Whisper model is working good",
"whisperModelIsNotWorking": "Whisper model is not working",
"relaunchIsNeededAfterChanged": "Relaunch is needed after changed",
"reloadIsNeededAfterChanged": "Reload is needed after changed",
"defaultAiEngine": "Default AI engine",
"aiEngine": "AI engine",
"defaultAiModel": "Default AI model",

View File

@@ -258,6 +258,8 @@
"resetSettingsConfirmation": "您确定要重置个人设置选项吗?资料库不会受影响。",
"proxySettings": "代理设置",
"proxyConfigUpdated": "代理配置已更新",
"apiSettings": "API 设置",
"apiUrlUpdated": "API 网址已更新",
"logoutAndRemoveAllPersonalData": "退出登录并删除所有个人数据",
"logoutAndRemoveAllPersonalSettings": "退出登录并删除所有个人设置选项",
"hotkeys": "快捷键",
@@ -380,6 +382,7 @@
"whisperModelIsWorkingGood": "Whisper 模型正常工作",
"whisperModelIsNotWorking": "Whisper 模型无法正常工作,请尝试更换模型后重试,或联系开发者",
"relaunchIsNeededAfterChanged": "更改后需要重新启动",
"reloadIsNeededAfterChanged": "更改后需要重新加载",
"defaultAiEngine": "默认 AI 引擎",
"aiEngine": "AI 引擎",
"defaultAiModel": "默认 AI 模型",

View File

@@ -176,6 +176,14 @@ export default {
ipcMain.handle("settings-set-default-hotkeys", (_event, records) => {
return settings.setSync("defaultHotkeys", records);
});
ipcMain.handle("settings-get-api-url", (_event, url) => {
return settings.getSync("apiUrl");
});
ipcMain.handle("settings-set-api-url", (_event, url) => {
return settings.setSync("apiUrl", url);
});
},
cachePath,
libraryPath,

View File

@@ -315,11 +315,13 @@ main.init = () => {
});
ipcMain.handle("app-api-url", () => {
return process.env.WEB_API_URL || WEB_API_URL;
const apiUrl = settings.getSync("apiUrl");
return process.env.WEB_API_URL || apiUrl || WEB_API_URL;
});
ipcMain.handle("app-ws-url", () => {
return process.env.WS_URL || WS_URL;
const wsUrl = settings.getSync("wsUrl");
return process.env.WS_URL || wsUrl || WS_URL;
});
ipcMain.handle("app-quit", () => {

View File

@@ -218,6 +218,12 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
setDefaultHotkeys: (records: Record<string, string>) => {
return ipcRenderer.invoke("settings-set-default-hotkeys", records);
},
getApiUrl: () => {
return ipcRenderer.invoke("settings-get-api-url");
},
setApiUrl: (url: string) => {
return ipcRenderer.invoke("settings-set-api-url", url);
},
},
path: {
join: (...paths: string[]) => {

View File

@@ -9,6 +9,10 @@ import {
AvatarFallback,
Button,
toast,
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "@renderer/components/ui";
import { useContext, useEffect, useState } from "react";
import { AppSettingsProviderContext } from "@renderer/context";
@@ -19,6 +23,8 @@ import {
GithubLoginButton,
BanduLoginButton,
MixinLoginButton,
ProxySettings,
ApiUrlSettings,
} from "@renderer/components";
import { EmailLoginForm } from "./email-login-form";
import { Client } from "@/api";
@@ -68,71 +74,101 @@ export const LoginForm = () => {
if (rememberedUser) {
return (
<div className="px-4 py-2 border rounded-lg w-full max-w-sm">
<div className="flex items-start justify-between py-4">
<div className="">
<div className="flex items-center space-x-2">
<Avatar>
<AvatarImage
crossOrigin="anonymous"
src={rememberedUser.avatarUrl}
/>
<AvatarFallback className="text-xl">
{rememberedUser.name[0].toUpperCase()}
</AvatarFallback>
</Avatar>
<Tabs className="w-full max-w-md" defaultValue="login">
<TabsList className="w-full grid grid-cols-2">
<TabsTrigger value="login">{t("login")}</TabsTrigger>
<TabsTrigger value="advanced">{t("advanced")}</TabsTrigger>
</TabsList>
<TabsContent value="login">
<div className="px-4 py-2 border rounded-lg w-full max-w-md">
<div className="flex items-start justify-between py-4">
<div className="">
<div className="text-sm font-semibold">
{rememberedUser.name}
</div>
<div className="text-xs text-muted-foreground">
{rememberedUser.id}
<div className="flex items-center space-x-2">
<Avatar>
<AvatarImage
crossOrigin="anonymous"
src={rememberedUser.avatarUrl}
/>
<AvatarFallback className="text-xl">
{rememberedUser.name[0].toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="">
<div className="text-sm font-semibold">
{rememberedUser.name}
</div>
<div className="text-xs text-muted-foreground">
{rememberedUser.id}
</div>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<Button
variant="secondary"
size="sm"
onClick={() => setRememberedUser(null)}
>
{t("reLogin")}
</Button>
<Button
variant="default"
size="sm"
onClick={loginWithRememberedUser}
>
{t("login")}
</Button>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<Button
variant="secondary"
size="sm"
onClick={() => setRememberedUser(null)}
>
{t("reLogin")}
</Button>
<Button
variant="default"
size="sm"
onClick={loginWithRememberedUser}
>
{t("login")}
</Button>
</div>
</div>
</div>
</TabsContent>
<TabsContent value="advanced">
<Card className="w-full max-w-md">
<CardContent className="mt-6">
<ApiUrlSettings />
<Separator />
<ProxySettings />
</CardContent>
</Card>
</TabsContent>
</Tabs>
);
}
return (
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>{t("login")}</CardTitle>
</CardHeader>
<Tabs className="w-full max-w-md" defaultValue="login">
<TabsList className="w-full grid grid-cols-2">
<TabsTrigger value="login">{t("login")}</TabsTrigger>
<TabsTrigger value="advanced">{t("advanced")}</TabsTrigger>
</TabsList>
<TabsContent value="login">
<Card className="w-full max-w-md">
<CardContent className="mt-6">
<EmailLoginForm />
<CardContent>
<EmailLoginForm />
<div className="">
<Separator className="my-4" />
<div className="flex items-center justify-center text-xs text-muted-foreground mb-4">
{t("youCanAlsoLoginWith")}
</div>
<div className="flex items-center space-x-2 justify-center">
<GithubLoginButton />
<MixinLoginButton />
<BanduLoginButton />
</div>
</div>
</CardContent>
</Card>
<div className="">
<Separator className="my-4" />
<div className="flex items-center justify-center text-xs text-muted-foreground mb-4">
{t("youCanAlsoLoginWith")}
</div>
<div className="flex items-center space-x-2 justify-center">
<GithubLoginButton />
<MixinLoginButton />
<BanduLoginButton />
</div>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="advanced">
<Card className="w-full max-w-md">
<CardContent className="mt-6">
<ApiUrlSettings />
<Separator />
<ProxySettings />
</CardContent>
</Card>
</TabsContent>
</Tabs>
);
};

View File

@@ -0,0 +1,112 @@
import * as z from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "i18next";
import {
Button,
Form,
FormField,
FormItem,
FormControl,
Input,
toast,
} from "@renderer/components/ui";
import { AppSettingsProviderContext } from "@renderer/context";
import { useContext, useState, useEffect } from "react";
import { InfoIcon } from "lucide-react";
export const ApiUrlSettings = () => {
const { apiUrl, setApiUrl } = useContext(AppSettingsProviderContext);
const [editing, setEditing] = useState(false);
const apiConfigSchema = z.object({
url: z.string().url(),
});
const form = useForm({
mode: "onBlur",
resolver: zodResolver(apiConfigSchema),
values: {
url: apiUrl,
},
});
const onSubmit = async (data: z.infer<typeof apiConfigSchema>) => {
setApiUrl(data.url).then(() => {
toast.success(t("apiUrlUpdated"));
setEditing(false);
});
};
useEffect(() => {}, [apiUrl]);
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex items-start justify-between py-4">
<div className="">
<div className="mb-2">{t("apiSettings")}</div>
<div className="text-sm text-muted-foreground mb-2 ml-1">
<FormField
control={form.control}
name="url"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
disabled={!editing}
placeholder="https://enjoy.bot"
value={field.value || ""}
onChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</div>
</div>
<div className="">
<div className="flex items-center space-x-2 justify-end mb-2">
{editing ? (
<>
<Button
variant="secondary"
onClick={(e) => {
setEditing(!editing);
e.preventDefault();
}}
size="sm"
>
{t("cancel")}
</Button>
<Button
variant="default"
onClick={() => onSubmit(form.getValues())}
size="sm"
>
{t("save")}
</Button>
</>
) : (
<Button
variant="secondary"
onClick={(e) => {
setEditing(!editing);
e.preventDefault();
}}
size="sm"
>
{t("edit")}
</Button>
)}
</div>
<div className="text-xs text-muted-foreground">
<InfoIcon className="mr-1 w-3 h-3 inline" />
<span>{t("reloadIsNeededAfterChanged")}</span>
</div>
</div>
</div>
</form>
</Form>
);
};

View File

@@ -1,5 +1,6 @@
export * from "./preferences";
export * from "./about";
export * from "./api-url-settings";
export * from "./appearance";
export * from "./hotkeys";

View File

@@ -2,6 +2,7 @@ import { t } from "i18next";
import { Button, ScrollArea, Separator } from "@renderer/components/ui";
import {
About,
ApiUrlSettings,
Appearance,
DefaultEngineSettings,
Hotkeys,
@@ -54,6 +55,8 @@ export const Preferences = () => {
<div className="font-semibold mb-4 capitilized">
{t("advancedSettings")}
</div>
<ApiUrlSettings />
<Separator />
<ProxySettings />
<Separator />
<ResetSettings />

View File

@@ -10,6 +10,7 @@ import { SENTRY_DSN } from "@/constants";
type AppSettingsProviderState = {
webApi: Client;
apiUrl?: string;
setApiUrl?: (url: string) => Promise<void>;
user: UserType | null;
initialized: boolean;
version?: string;
@@ -164,6 +165,12 @@ export const AppSettingsProvider = ({
});
};
const setApiUrlHandler = async (url: string) => {
EnjoyApp.settings.setApiUrl(url).then(() => {
EnjoyApp.app.reload();
});
};
const createCable = async (token: string) => {
const wsUrl = await EnjoyApp.app.wsUrl();
const consumer = createConsumer(wsUrl + "/cable?token=" + token);
@@ -220,6 +227,7 @@ export const AppSettingsProvider = ({
version,
webApi,
apiUrl,
setApiUrl: setApiUrlHandler,
user,
login,
logout,

View File

@@ -128,6 +128,8 @@ type EnjoyAppType = {
switchLanguage: (language: string) => Promise<void>;
getDefaultHotkeys: () => Promise<Record<string, string> | undefined>;
setDefaultHotkeys: (records: Record<string, string>) => Promise<void>;
getApiUrl: () => Promise<string>;
setApiUrl: (url: string) => Promise<void>;
};
fs: {
ensureDir: (path: string) => Promise<boolean>;