Feat may set api url (#850)
* add api url settings * may edit api setting before login
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 模型",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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[]) => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
112
enjoy/src/renderer/components/preferences/api-url-settings.tsx
Normal file
112
enjoy/src/renderer/components/preferences/api-url-settings.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./preferences";
|
||||
export * from "./about";
|
||||
export * from "./api-url-settings";
|
||||
export * from "./appearance";
|
||||
|
||||
export * from "./hotkeys";
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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,
|
||||
|
||||
2
enjoy/src/types/enjoy-app.d.ts
vendored
2
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user