From 6ac26b7d4ab24a961273d392f2a46d1ba5c3efb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AB=E3=82=99=E3=82=AA=E3=82=AB=E3=82=99=E3=82=AA?= Date: Thu, 13 Apr 2023 01:27:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BF=AE=E5=A4=8Dapikey=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E9=A2=9D=E5=BA=A6=E6=8E=A5=E5=8F=A3=E7=BC=93=E5=AD=98=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E5=92=8C=E4=BF=AE=E5=A4=8Dsetting=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E9=92=88=E5=AF=B9=E8=AF=A5=E6=8E=A5=E5=8F=A3=E9=A2=91=E7=B9=81?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E7=9A=84=E9=97=AE=E9=A2=98=201.=E4=B8=BA?= =?UTF-8?q?=E4=BA=86=E8=A7=A3=E5=86=B3=E7=BC=93=E5=AD=98=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=E4=BA=86=E8=B0=83=E7=94=A8apikey?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=A2=9D=E5=BA=A6=E6=8E=A5=E5=8F=A3=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E3=80=82=E7=94=B1=E4=BA=8Enextjs=E7=9A=84fet?= =?UTF-8?q?ch=E9=BB=98=E8=AE=A4=E4=BC=9A=E7=BC=93=E5=AD=98=E5=90=8C?= =?UTF-8?q?=E6=A0=B7=E5=8F=82=E6=95=B0=E7=9A=84=E8=AF=B7=E6=B1=82=EF=BC=8C?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E9=92=88=E5=AF=B9fetch=E5=8A=A0=E5=85=A5cach?= =?UTF-8?q?e:=20'no-store'=20=E7=9A=84=E5=8F=82=E6=95=B0=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BB=A5=E9=81=BF=E5=85=8D=E8=BF=99=E4=B8=80=E7=8E=B0=E8=B1=A1?= =?UTF-8?q?=E3=80=82=EF=BC=88https://beta.nextjs.org/docs/data-fetching/fe?= =?UTF-8?q?tching#dynamic-data-fetching=EF=BC=89=202.=E4=B8=BA=E4=BA=86?= =?UTF-8?q?=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C=EF=BC=8C?= =?UTF-8?q?=E5=BC=95=E5=85=A5=E4=BA=86usageStore=EF=BC=8C=E8=AF=A5?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=BA=93=E8=83=BD=E5=A4=9F=E5=9C=A8=E6=9C=89?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E6=83=85=E5=86=B5=E4=B8=8B=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=85=88=E4=BD=BF=E7=94=A8=E7=BC=93=E5=AD=98=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=9D=A5=E5=8A=A0=E8=BD=BDapikey=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E9=A2=9D=E5=BA=A6=E7=9A=84=E4=BF=A1=E6=81=AF=E3=80=82=E5=8F=AA?= =?UTF-8?q?=E6=9C=89=E5=BD=93=E9=87=8D=E6=96=B0=E6=A3=80=E6=9F=A5=E7=9A=84?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E8=A2=AB=E7=82=B9=E5=87=BB=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=89=8D=E4=BC=9A=E5=86=8D=E6=AC=A1=E8=B0=83=E7=94=A8apikey?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E9=A2=9D=E5=BA=A6=E6=8E=A5=E5=8F=A3=E6=9B=B4?= =?UTF-8?q?=E6=96=B0usageStore=E6=95=B0=E6=8D=AE=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=B8=94=E5=9C=A8=E9=A1=B5=E9=9D=A2=E4=B8=8A=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84=E6=9B=B4=E6=96=B0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E3=80=82=E9=80=9A=E8=BF=87=E5=AE=9E=E7=8E=B0=E8=BF=99=E7=A7=8D?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=EF=BC=8C=E6=88=91=E4=BB=AC=E8=83=BD=E5=A4=9F?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E5=9C=A8=E8=BF=9B=E5=85=A5setting=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=97=B6=E9=A2=91=E7=B9=81=E5=9C=B0=E8=B0=83=E7=94=A8?= =?UTF-8?q?apikey=E4=BD=BF=E7=94=A8=E9=A2=9D=E5=BA=A6=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=8C=E4=BB=8E=E8=80=8C=E8=BF=9B=E4=B8=80=E6=AD=A5=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E5=93=8D=E5=BA=94=E9=80=9F=E5=BA=A6=E5=92=8C=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/common.ts | 3 +++ app/components/settings.tsx | 42 ++++++++++++++++++++++--------------- app/requests.ts | 17 ++++++++++----- app/store/index.ts | 1 + app/store/usage.ts | 41 ++++++++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 app/store/usage.ts diff --git a/app/api/common.ts b/app/api/common.ts index 842eeacaf..b2e8f918f 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -8,6 +8,8 @@ const BASE_URL = process.env.BASE_URL ?? OPENAI_URL; export async function requestOpenai(req: NextRequest) { const apiKey = req.headers.get("token"); const openaiPath = req.headers.get("path"); + const fetchCache = + req.headers.get("fetch-cache") == "enable" ? "default" : "no-store"; console.log("[Proxy] ", openaiPath); @@ -16,6 +18,7 @@ export async function requestOpenai(req: NextRequest) { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, + cache: fetchCache, method: req.method, body: req.body, }); diff --git a/app/components/settings.tsx b/app/components/settings.tsx index e418d4843..b74414d2c 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -21,6 +21,7 @@ import { ALL_MODELS, useUpdateStore, useAccessStore, + useUsageStore, ModalConfigValidator, } from "../store"; import { Avatar } from "./chat"; @@ -92,25 +93,33 @@ export function Settings(props: { closeSettings: () => void }) { const remoteId = updateStore.remoteVersion; const hasNewVersion = currentVersion !== remoteId; - function checkUpdate(force = false) { + function checkUpdate(force: boolean = false) { setCheckingUpdate(true); updateStore.getLatestVersion(force).then(() => { setCheckingUpdate(false); }); } - const [usage, setUsage] = useState<{ - used?: number; - subscription?: number; - }>(); + // const [usage, setUsage] = useState<{ + // used?: number; + // subscription?: number; + // }>(); + const { used, subscription, updateUsage, hasUsageData } = useUsageStore(); const [loadingUsage, setLoadingUsage] = useState(false); - function checkUsage() { - setLoadingUsage(true); - requestUsage() - .then((res) => setUsage(res)) - .finally(() => { - setLoadingUsage(false); - }); + function checkUsage(forceUpdate: boolean = false) { + if (!hasUsageData() || forceUpdate) { + setLoadingUsage(true); + requestUsage() + .then((res) => { + if (res) { + const { used, subscription } = res; + updateUsage(used?.toString(), subscription?.toString()); + } + }) + .finally(() => { + setLoadingUsage(false); + }); + } } const accessStore = useAccessStore(); @@ -385,10 +394,7 @@ export function Settings(props: { closeSettings: () => void }) { showUsage ? loadingUsage ? Locale.Settings.Usage.IsChecking - : Locale.Settings.Usage.SubTitle( - usage?.used ?? "[?]", - usage?.subscription ?? "[?]", - ) + : Locale.Settings.Usage.SubTitle(used, subscription) : Locale.Settings.Usage.NoAccess } > @@ -398,7 +404,9 @@ export function Settings(props: { closeSettings: () => void }) { } text={Locale.Settings.Usage.Check} - onClick={checkUsage} + onClick={() => { + checkUsage(true); + }} /> )} diff --git a/app/requests.ts b/app/requests.ts index 86d180f71..cc87030c3 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -49,15 +49,17 @@ function getHeaders() { } 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", { method, headers: { "Content-Type": "application/json", path, ...getHeaders(), + "fetch-cache": fetchCache ? "enable" : "disable", }, 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() { + const useFetchCache = false; const formatDate = (d: Date) => `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d .getDate() @@ -89,8 +92,12 @@ export async function requestUsage() { const [used, subs] = await Promise.all([ requestOpenaiClient( `dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, - )(null, "GET"), - requestOpenaiClient("dashboard/billing/subscription")(null, "GET"), + )(null, "GET", useFetchCache), + requestOpenaiClient("dashboard/billing/subscription")( + null, + "GET", + useFetchCache, + ), ]); const response = (await used.json()) as { @@ -171,11 +178,11 @@ export async function requestChatStream( const resTimeoutId = setTimeout(() => finish(), TIME_OUT_MS); const content = await reader?.read(); clearTimeout(resTimeoutId); - + if (!content || !content.value) { break; } - + const text = decoder.decode(content.value, { stream: true }); responseText += text; diff --git a/app/store/index.ts b/app/store/index.ts index 3bdb58ca2..814acd926 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -1,3 +1,4 @@ export * from "./app"; export * from "./update"; export * from "./access"; +export * from "./usage"; diff --git a/app/store/usage.ts b/app/store/usage.ts new file mode 100644 index 000000000..90a58bb59 --- /dev/null +++ b/app/store/usage.ts @@ -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()( + 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, + }, + ), +);