feat: 🎸 add net state (#947)

This commit is contained in:
divisey
2024-08-08 14:22:27 +08:00
committed by GitHub
parent 517f9d40d3
commit e5a328e284
13 changed files with 196 additions and 35 deletions

View File

@@ -64,6 +64,10 @@ export class Client {
);
}
up() {
return this.api.get("/up");
}
auth(params: {
provider: "mixin" | "github" | "bandu" | "email";
code?: string;

View File

@@ -653,5 +653,10 @@
"continueLearning": "Continue learning",
"enrollNow": "Enroll now",
"enrollments": "Enrollments",
"noLikesYet": "No likes yet"
"noLikesYet": "No likes yet",
"apiConnectTime": "API Connect Time ({{apiUrl}})",
"ipInfo": "IP Information",
"platformInfo": "Platform Information",
"networkState": "Network State",
"connectError": "Connect Error"
}

View File

@@ -653,5 +653,10 @@
"continueLearning": "继续练习",
"enrollNow": "加入练习",
"enrollments": "参加的课程",
"noLikesYet": "还没有点赞"
"noLikesYet": "还没有点赞",
"apiConnectTime": "API 延时 ({{apiUrl}})",
"ipInfo": "IP 信息",
"platformInfo": "设备信息",
"networkState": "网络状态",
"connectError": "连接错误"
}

View File

@@ -280,6 +280,14 @@ main.init = () => {
});
// App options
ipcMain.handle("app-platform-info", () => {
return {
platform: process.platform,
arch: process.arch,
version: process.getSystemVersion(),
};
});
ipcMain.handle("app-reset", () => {
fs.removeSync(settings.userDataPath());
fs.removeSync(settings.file());

View File

@@ -6,6 +6,9 @@ import { Timeline } from "echogarden/dist/utilities/Timeline";
contextBridge.exposeInMainWorld("__ENJOY_APP__", {
app: {
getPlatformInfo: () => {
return ipcRenderer.invoke("app-platform-info");
},
reset: () => {
ipcRenderer.invoke("app-reset");
},

View File

@@ -25,6 +25,7 @@ import {
MixinLoginButton,
ProxySettings,
ApiUrlSettings,
NetworkState,
} from "@renderer/components";
import { EmailLoginForm } from "./email-login-form";
import { Client } from "@/api";
@@ -128,6 +129,8 @@ export const LoginForm = () => {
<ApiUrlSettings />
<Separator />
<ProxySettings />
<Separator />
<NetworkState />
</CardContent>
</Card>
</TabsContent>
@@ -166,6 +169,8 @@ export const LoginForm = () => {
<ApiUrlSettings />
<Separator />
<ProxySettings />
<Separator />
<NetworkState />
</CardContent>
</Card>
</TabsContent>

View File

@@ -56,7 +56,7 @@ export const DefaultEngineSettings = () => {
if (form.watch("name") === "openai") {
const customModels = openai?.models?.split(",")?.filter(Boolean);
return customModels.length ? customModels : providers.openai.models;
return customModels?.length ? customModels : providers.openai.models;
} else {
return providers.enjoyai.models;
}

View File

@@ -27,4 +27,5 @@ export * from "./theme-settings";
export * from "./proxy-settings";
export * from "./whisper-model-options";
export * from "./whisper-model-options";
export * from "./network-state";

View File

@@ -0,0 +1,150 @@
import { Client } from "@/api";
import { t } from "i18next";
import { useState, useContext, useEffect, useMemo } from "react";
import { AppSettingsProviderContext } from "@renderer/context";
import { LoaderIcon } from "lucide-react";
export const NetworkState = () => {
const { apiUrl, EnjoyApp } = useContext(AppSettingsProviderContext);
let timeoutId: ReturnType<typeof setTimeout | null> = null;
const [apiConnected, setApiConnected] = useState(false);
const [apiConnecting, setApiConnecting] = useState(false);
const [apiConnectError, setApiConnectError] = useState(false);
const [apiConnectTime, setApiConnectTime] = useState<number>(null);
const [ipInfoError, setIpInfoError] = useState(false);
const [ipInfo, setIpInfo] = useState(null);
const [platformInfo, setPlatformInfo] = useState<PlatformInfo>(null);
const items = useMemo(() => {
const apiStateColor =
apiConnectTime < 200
? "text-green-500"
: apiConnectTime < 800
? "text-yellow-500"
: "text-red-500";
return [
{
title: t("apiConnectTime", { apiUrl }),
loading: !apiConnected && apiConnecting,
error: apiConnectError,
value: <span className={apiStateColor}>{apiConnectTime}ms</span>,
},
{
title: t("ipInfo"),
loading: false,
error: ipInfoError,
value: (
<span>
{ipInfo
? `${ipInfo.ip} (${ipInfo.city}, ${ipInfo.country_name})`
: "-"}
</span>
),
},
{
title: t("platformInfo"),
loading: false,
error: false,
value: (
<span>
{platformInfo
? `${platformInfo.platform} ${platformInfo.arch} ${platformInfo.version}`
: "-"}
</span>
),
},
];
}, [
apiConnectTime,
apiConnecting,
apiConnected,
apiConnectError,
ipInfo,
ipInfoError,
platformInfo,
]);
useEffect(() => {
pollingAction();
getPlatformInfo();
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, []);
async function pollingAction() {
await Promise.all([getApiConnectTime(), getIpInfo()]);
timeoutId = setTimeout(() => pollingAction(), 10000);
}
async function getApiConnectTime() {
setApiConnecting(true);
try {
const client = new Client({ baseUrl: apiUrl });
const startTime = new Date().getTime();
await client.up();
const endTime = new Date().getTime();
setApiConnectTime(endTime - startTime);
setApiConnected(true);
} catch (error) {
setApiConnectError(true);
setApiConnected(false);
}
setApiConnecting(false);
}
async function getIpInfo() {
try {
await fetch("https://ipapi.co/json")
.then((resp) => resp.json())
.then((info) => setIpInfo(info));
} catch (error) {
setIpInfoError(true);
}
}
async function getPlatformInfo() {
const info = await EnjoyApp.app.getPlatformInfo();
setPlatformInfo(info);
}
return (
<div className="py-4">
<div className="flex items-start justify-between">
<div className="mb-2">{t("networkState")}</div>
</div>
<div>
{items.map((item, index) => {
return (
<div
key={index}
className="text-sm text-muted-foreground flex justify-between my-2"
>
<div className="">{item.title}</div>
<div className="">
{item.loading ? (
<LoaderIcon className="w-4 h-4 animate-spin" />
) : item.error ? (
<span className="text-red-500">{t("connectError")}</span>
) : (
item.value
)}
</div>
</div>
);
})}
</div>
</div>
);
};

View File

@@ -16,6 +16,7 @@ import {
ResetAllSettings,
NativeLanguageSettings,
LearningLanguageSettings,
NetworkState,
} from "@renderer/components";
import { useState } from "react";
import { Tooltip } from "react-tooltip";
@@ -56,6 +57,8 @@ export const Preferences = () => {
<Separator />
<ProxySettings />
<Separator />
<NetworkState />
<Separator />
<ResetSettings />
<Separator />
<ResetAllSettings />

View File

@@ -12,13 +12,11 @@ import {
Input,
toast,
} from "@renderer/components/ui";
import { InfoIcon } from "lucide-react";
import { AppSettingsProviderContext } from "@renderer/context";
import { useContext, useState, useEffect } from "react";
import { useContext, useState } from "react";
export const ProxySettings = () => {
const { proxy, setProxy } = useContext(AppSettingsProviderContext);
const [ipData, setIpData] = useState(null);
const [editing, setEditing] = useState(false);
const proxyConfigSchema = z.object({
@@ -51,22 +49,6 @@ export const ProxySettings = () => {
});
};
const checkIp = async () => {
fetch("https://ipapi.co/json")
.then((response) => response.json())
.then((data) => {
setIpData(data);
})
.catch((error) => {
toast.error(error.message);
setIpData(null);
});
};
useEffect(() => {
checkIp();
}, [proxy]);
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
@@ -91,18 +73,6 @@ export const ProxySettings = () => {
)}
/>
</div>
{form.getValues("enabled") && ipData && (
<div className="text-sm text-muted-foreground mb-2 ml-1">
<div className="flex items-center space-x-2">
<div>
IP: {ipData.ip} ({ipData.city}, {ipData.country_name})
</div>
<div>
<InfoIcon size={16} className="cursor-pointer" />
</div>
</div>
</div>
)}
</div>
<div className="flex items-center space-x-2 justify-end">

View File

@@ -1,5 +1,6 @@
type EnjoyAppType = {
app: {
getPlatformInfo: () => Promise<PlatformInfo>;
reset: () => Promise<void>;
resetSettings: () => Promise<void>;
relaunch: () => Promise<void>;

View File

@@ -177,3 +177,9 @@ type GptEngineSettingType = {
baseUrl?: string;
key?: string;
};
type PlatformInfo = {
platform: string;
arch: string;
version: string;
};