feat:修复apikey使用额度接口缓存问题和修复setting页面针对该接口频繁调用的问题

1.为了解决缓存问题,更新了调用apikey使用额度接口的方法。由于nextjs的fetch默认会缓存同样参数的请求,需要针对fetch加入cache: 'no-store' 的参数修复以避免这一现象。(https://beta.nextjs.org/docs/data-fetching/fetching#dynamic-data-fetching)
2.为了提升用户体验,引入了usageStore,该存储库能够在有数据的情况下,优先使用缓存数据来加载apikey使用额度的信息。只有当重新检查的按钮被点击时,才会再次调用apikey使用额度接口更新usageStore数据,并且在页面上显示最新的更新数据。通过实现这种操作,我们能够避免在进入setting页面时频繁地调用apikey使用额度接口,从而进一步提升响应速度和用户体验。
This commit is contained in:
ガオガオ 2023-04-13 01:27:11 +08:00
parent 9146b98285
commit 6ac26b7d4a
5 changed files with 82 additions and 22 deletions

View File

@ -8,6 +8,8 @@ const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
export async function requestOpenai(req: NextRequest) { export async function requestOpenai(req: NextRequest) {
const apiKey = req.headers.get("token"); const apiKey = req.headers.get("token");
const openaiPath = req.headers.get("path"); const openaiPath = req.headers.get("path");
const fetchCache =
req.headers.get("fetch-cache") == "enable" ? "default" : "no-store";
console.log("[Proxy] ", openaiPath); console.log("[Proxy] ", openaiPath);
@ -16,6 +18,7 @@ export async function requestOpenai(req: NextRequest) {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}, },
cache: fetchCache,
method: req.method, method: req.method,
body: req.body, body: req.body,
}); });

View File

@ -21,6 +21,7 @@ import {
ALL_MODELS, ALL_MODELS,
useUpdateStore, useUpdateStore,
useAccessStore, useAccessStore,
useUsageStore,
ModalConfigValidator, ModalConfigValidator,
} from "../store"; } from "../store";
import { Avatar } from "./chat"; import { Avatar } from "./chat";
@ -92,25 +93,33 @@ export function Settings(props: { closeSettings: () => void }) {
const remoteId = updateStore.remoteVersion; const remoteId = updateStore.remoteVersion;
const hasNewVersion = currentVersion !== remoteId; const hasNewVersion = currentVersion !== remoteId;
function checkUpdate(force = false) { function checkUpdate(force: boolean = false) {
setCheckingUpdate(true); setCheckingUpdate(true);
updateStore.getLatestVersion(force).then(() => { updateStore.getLatestVersion(force).then(() => {
setCheckingUpdate(false); setCheckingUpdate(false);
}); });
} }
const [usage, setUsage] = useState<{ // const [usage, setUsage] = useState<{
used?: number; // used?: number;
subscription?: number; // subscription?: number;
}>(); // }>();
const { used, subscription, updateUsage, hasUsageData } = useUsageStore();
const [loadingUsage, setLoadingUsage] = useState(false); const [loadingUsage, setLoadingUsage] = useState(false);
function checkUsage() { function checkUsage(forceUpdate: boolean = false) {
setLoadingUsage(true); if (!hasUsageData() || forceUpdate) {
requestUsage() setLoadingUsage(true);
.then((res) => setUsage(res)) requestUsage()
.finally(() => { .then((res) => {
setLoadingUsage(false); if (res) {
}); const { used, subscription } = res;
updateUsage(used?.toString(), subscription?.toString());
}
})
.finally(() => {
setLoadingUsage(false);
});
}
} }
const accessStore = useAccessStore(); const accessStore = useAccessStore();
@ -385,10 +394,7 @@ export function Settings(props: { closeSettings: () => void }) {
showUsage showUsage
? loadingUsage ? loadingUsage
? Locale.Settings.Usage.IsChecking ? Locale.Settings.Usage.IsChecking
: Locale.Settings.Usage.SubTitle( : Locale.Settings.Usage.SubTitle(used, subscription)
usage?.used ?? "[?]",
usage?.subscription ?? "[?]",
)
: Locale.Settings.Usage.NoAccess : Locale.Settings.Usage.NoAccess
} }
> >
@ -398,7 +404,9 @@ export function Settings(props: { closeSettings: () => void }) {
<IconButton <IconButton
icon={<ResetIcon></ResetIcon>} icon={<ResetIcon></ResetIcon>}
text={Locale.Settings.Usage.Check} text={Locale.Settings.Usage.Check}
onClick={checkUsage} onClick={() => {
checkUsage(true);
}}
/> />
)} )}
</SettingItem> </SettingItem>

View File

@ -49,15 +49,17 @@ function getHeaders() {
} }
export function requestOpenaiClient(path: string) { export function requestOpenaiClient(path: string) {
return (body: any, method = "POST") => return (body: any, method: string = "POST", fetchCache: boolean = true) =>
fetch("/api/openai?_vercel_no_cache=1", { fetch("/api/openai?_vercel_no_cache=1", {
method, method,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
path, path,
...getHeaders(), ...getHeaders(),
"fetch-cache": fetchCache ? "enable" : "disable",
}, },
body: body && JSON.stringify(body), body: body && JSON.stringify(body),
cache: fetchCache ? "default" : "no-store", //https://beta.nextjs.org/docs/data-fetching/fetching#dynamic-data-fetching
}); });
} }
@ -75,6 +77,7 @@ export async function requestChat(messages: Message[]) {
} }
export async function requestUsage() { export async function requestUsage() {
const useFetchCache = false;
const formatDate = (d: Date) => const formatDate = (d: Date) =>
`${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d
.getDate() .getDate()
@ -89,8 +92,12 @@ export async function requestUsage() {
const [used, subs] = await Promise.all([ const [used, subs] = await Promise.all([
requestOpenaiClient( requestOpenaiClient(
`dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, `dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`,
)(null, "GET"), )(null, "GET", useFetchCache),
requestOpenaiClient("dashboard/billing/subscription")(null, "GET"), requestOpenaiClient("dashboard/billing/subscription")(
null,
"GET",
useFetchCache,
),
]); ]);
const response = (await used.json()) as { const response = (await used.json()) as {

View File

@ -1,3 +1,4 @@
export * from "./app"; export * from "./app";
export * from "./update"; export * from "./update";
export * from "./access"; export * from "./access";
export * from "./usage";

41
app/store/usage.ts Normal file
View File

@ -0,0 +1,41 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
export interface UsageStore {
used: string;
subscription: string;
updateUsage: (used?: string, subscription?: string) => void;
hasUsageData: () => boolean;
}
const USAGE_KEY = "api-usage";
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
export const useUsageStore = create<UsageStore>()(
persist(
(set, get) => ({
used: "",
subscription: "",
updateUsage(used?: string, subscription?: string) {
set((state) => ({
used: used ?? "[?]",
subscription: subscription ?? "[?]",
}));
},
hasUsageData() {
const used = get().used;
const sub = get().subscription;
const hasUsed = used != "" && used != "[?]";
const hasSubscription = sub != "" && sub != "[?]";
return hasUsed && hasSubscription;
},
}),
{
name: USAGE_KEY,
version: 1,
},
),
);