diff --git a/app/api/config/route.ts b/app/api/config/route.ts new file mode 100644 index 00000000..e04e22a0 --- /dev/null +++ b/app/api/config/route.ts @@ -0,0 +1,21 @@ +import { NextRequest, NextResponse } from "next/server"; + +import { getServerSideConfig } from "../../config/server"; + +const serverConfig = getServerSideConfig(); + +// Danger! Don not write any secret value here! +// 警告!不要在这里写入任何敏感信息! +const DANGER_CONFIG = { + needCode: serverConfig.needCode, +}; + +declare global { + type DangerConfig = typeof DANGER_CONFIG; +} + +export async function POST(req: NextRequest) { + return NextResponse.json({ + needCode: serverConfig.needCode, + }); +} diff --git a/app/components/home.tsx b/app/components/home.tsx index e73b8041..3055b688 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -2,13 +2,7 @@ require("../polyfill"); -import { - useState, - useEffect, - useRef, - useCallback, - MouseEventHandler, -} from "react"; +import { useState, useEffect, useRef } from "react"; import { IconButton } from "./button"; import styles from "./home.module.scss"; @@ -30,7 +24,6 @@ import { Chat } from "./chat"; import dynamic from "next/dynamic"; import { REPO_URL } from "../constant"; import { ErrorBoundary } from "./error"; -import { useDebounce } from "use-debounce"; export function Loading(props: { noLogo?: boolean }) { return ( @@ -165,96 +158,100 @@ function _Home() { } return ( -
+ <>
-
-
ChatGPT Next
-
- Build your own AI assistant. -
-
- -
-
-
{ - setOpenSettings(false); - setShowSideBar(false); - }} + className={ + styles.sidebar + ` ${showSideBar && styles["sidebar-show"]}` + } > - -
- -
-
-
- } - onClick={chatStore.deleteSession} - /> +
+
ChatGPT Next
+
+ Build your own AI assistant.
-
+
+ +
+
+ +
{ + setOpenSettings(false); + setShowSideBar(false); + }} + > + +
+ +
+
+
+ } + onClick={chatStore.deleteSession} + /> +
+
+ } + onClick={() => { + setOpenSettings(true); + setShowSideBar(false); + }} + shadow + /> +
+ +
+
} + icon={} + text={Locale.Home.NewChat} onClick={() => { - setOpenSettings(true); + createNewSession(); setShowSideBar(false); }} shadow />
- -
-
- } - text={Locale.Home.NewChat} - onClick={() => { - createNewSession(); - setShowSideBar(false); - }} - shadow - />
+ +
onDragMouseDown(e as any)} + >
-
onDragMouseDown(e as any)} - >
+
+ {openSettings ? ( + { + setOpenSettings(false); + setShowSideBar(true); + }} + /> + ) : ( + setShowSideBar(true)} + sideBarShowing={showSideBar} + /> + )} +
- -
- {openSettings ? ( - { - setOpenSettings(false); - setShowSideBar(true); - }} - /> - ) : ( - setShowSideBar(true)} - sideBarShowing={showSideBar} - /> - )} -
-
+ ); } diff --git a/app/components/settings.tsx b/app/components/settings.tsx index d3998596..e418d484 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -33,7 +33,6 @@ import { SearchService, usePromptStore } from "../store/prompt"; import { requestUsage } from "../requests"; import { ErrorBoundary } from "./error"; import { InputRange } from "./input-range"; -import { getClientSideConfig } from "../config/client"; function SettingItem(props: { title: string; @@ -89,13 +88,13 @@ export function Settings(props: { closeSettings: () => void }) { const updateStore = useUpdateStore(); const [checkingUpdate, setCheckingUpdate] = useState(false); - const currentVersion = getClientSideConfig()?.version; - const remoteId = updateStore.remoteId; + const currentVersion = updateStore.version; + const remoteId = updateStore.remoteVersion; const hasNewVersion = currentVersion !== remoteId; function checkUpdate(force = false) { setCheckingUpdate(true); - updateStore.getLatestCommitId(force).then(() => { + updateStore.getLatestVersion(force).then(() => { setCheckingUpdate(false); }); } diff --git a/app/config/client.ts b/app/config/client.ts deleted file mode 100644 index 0e9c50d0..00000000 --- a/app/config/client.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { RUNTIME_CONFIG_DOM } from "../constant"; - -function queryMeta(key: string, defaultValue?: string): string { - let ret: string; - if (document) { - const meta = document.head.querySelector( - `meta[name='${key}']`, - ) as HTMLMetaElement; - ret = meta?.content ?? ""; - } else { - ret = defaultValue ?? ""; - } - - return ret; -} - -export function getClientSideConfig() { - if (typeof window === "undefined") { - throw Error( - "[Client Config] you are importing a browser-only module outside of browser", - ); - } - - const dom = document.getElementById(RUNTIME_CONFIG_DOM); - - if (!dom) { - throw Error("[Config] Dont get config before page loading!"); - } - - try { - const fromServerConfig = JSON.parse(dom.innerText) as DangerConfig; - const fromBuildConfig = { - version: queryMeta("version"), - }; - return { - ...fromServerConfig, - ...fromBuildConfig, - }; - } catch (e) { - console.error("[Config] failed to parse client config"); - } -} diff --git a/app/page.tsx b/app/page.tsx index 484e2c20..20b50317 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,27 +1,14 @@ import { Analytics } from "@vercel/analytics/react"; import { Home } from "./components/home"; + import { getServerSideConfig } from "./config/server"; -import { RUNTIME_CONFIG_DOM } from "./constant"; const serverConfig = getServerSideConfig(); -// Danger! Don not write any secret value here! -// 警告!不要在这里写入任何敏感信息! -const DANGER_CONFIG = { - needCode: serverConfig?.needCode, -}; - -declare global { - type DangerConfig = typeof DANGER_CONFIG; -} - -export default function App() { +export default async function App() { return ( <> -
- {JSON.stringify(DANGER_CONFIG)} -
{serverConfig?.isVercel && } diff --git a/app/store/access.ts b/app/store/access.ts index 9eafc3a4..aed13168 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -1,26 +1,33 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import { getClientSideConfig } from "../config/client"; export interface AccessControlStore { accessCode: string; token: string; + needCode: boolean; + updateToken: (_: string) => void; updateCode: (_: string) => void; enabledAccessControl: () => boolean; isAuthorized: () => boolean; + fetch: () => void; } export const ACCESS_KEY = "access-control"; +let fetchState = 0; // 0 not fetch, 1 fetching, 2 done + export const useAccessStore = create()( persist( (set, get) => ({ token: "", accessCode: "", + needCode: true, enabledAccessControl() { - return !!getClientSideConfig()?.needCode; + get().fetch(); + + return get().needCode; }, updateCode(code: string) { set((state) => ({ accessCode: code })); @@ -34,6 +41,25 @@ export const useAccessStore = create()( !!get().token || !!get().accessCode || !get().enabledAccessControl() ); }, + fetch() { + if (fetchState > 0) return; + fetchState = 1; + fetch("/api/config", { + method: "post", + body: null, + }) + .then((res) => res.json()) + .then((res: DangerConfig) => { + console.log("[Config] got config from server", res); + set(() => ({ ...res })); + }) + .catch(() => { + console.error("[Config] failed to fetch config"); + }) + .finally(() => { + fetchState = 2; + }); + }, }), { name: ACCESS_KEY, diff --git a/app/store/update.ts b/app/store/update.ts index 78001f91..efcdc8a7 100644 --- a/app/store/update.ts +++ b/app/store/update.ts @@ -1,28 +1,46 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import { getClientSideConfig } from "../config/client"; import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant"; export interface UpdateStore { lastUpdate: number; - remoteId: string; + remoteVersion: string; - getLatestCommitId: (force: boolean) => Promise; + version: string; + getLatestVersion: (force: boolean) => Promise; } export const UPDATE_KEY = "chat-update"; +function queryMeta(key: string, defaultValue?: string): string { + let ret: string; + if (document) { + const meta = document.head.querySelector( + `meta[name='${key}']`, + ) as HTMLMetaElement; + ret = meta?.content ?? ""; + } else { + ret = defaultValue ?? ""; + } + + return ret; +} + export const useUpdateStore = create()( persist( (set, get) => ({ lastUpdate: 0, - remoteId: "", + remoteVersion: "", + + version: "unknown", + + async getLatestVersion(force = false) { + set(() => ({ version: queryMeta("version") })); - async getLatestCommitId(force = false) { const overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000; const shouldFetch = force || overTenMins; if (!shouldFetch) { - return getClientSideConfig()?.version ?? ""; + return get().version ?? "unknown"; } try { @@ -32,13 +50,13 @@ export const useUpdateStore = create()( const remoteId = (data[0].sha as string).substring(0, 7); set(() => ({ lastUpdate: Date.now(), - remoteId, + remoteVersion: remoteId, })); console.log("[Got Upstream] ", remoteId); return remoteId; } catch (error) { console.error("[Fetch Upstream Commit Id]", error); - return getClientSideConfig()?.version ?? ""; + return get().version ?? ""; } }, }), diff --git a/middleware.ts b/middleware.ts index 31a417a2..c2e07770 100644 --- a/middleware.ts +++ b/middleware.ts @@ -32,7 +32,7 @@ export function middleware(req: NextRequest) { // inject api key if (!token) { - const apiKey = process.env.OPENAI_API_KEY; + const apiKey = serverConfig.apiKey; if (apiKey) { console.log("[Auth] set system token"); req.headers.set("token", apiKey);