forked from XiaoMo/ChatGPT-Next-Web
feat: dynamic config
This commit is contained in:
parent
9b61cb1335
commit
d6e6dd09f0
21
app/api/config/route.ts
Normal file
21
app/api/config/route.ts
Normal file
@ -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,
|
||||||
|
});
|
||||||
|
}
|
@ -2,13 +2,7 @@
|
|||||||
|
|
||||||
require("../polyfill");
|
require("../polyfill");
|
||||||
|
|
||||||
import {
|
import { useState, useEffect, useRef } from "react";
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useCallback,
|
|
||||||
MouseEventHandler,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
@ -30,7 +24,6 @@ import { Chat } from "./chat";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { REPO_URL } from "../constant";
|
import { REPO_URL } from "../constant";
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
import { useDebounce } from "use-debounce";
|
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
return (
|
return (
|
||||||
@ -165,6 +158,7 @@ function _Home() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
config.tightBorder && !isMobileScreen()
|
config.tightBorder && !isMobileScreen()
|
||||||
@ -173,7 +167,9 @@ function _Home() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={styles.sidebar + ` ${showSideBar && styles["sidebar-show"]}`}
|
className={
|
||||||
|
styles.sidebar + ` ${showSideBar && styles["sidebar-show"]}`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-header"]}>
|
<div className={styles["sidebar-header"]}>
|
||||||
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
|
<div className={styles["sidebar-title"]}>ChatGPT Next</div>
|
||||||
@ -255,6 +251,7 @@ function _Home() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ import { SearchService, usePromptStore } from "../store/prompt";
|
|||||||
import { requestUsage } from "../requests";
|
import { requestUsage } from "../requests";
|
||||||
import { ErrorBoundary } from "./error";
|
import { ErrorBoundary } from "./error";
|
||||||
import { InputRange } from "./input-range";
|
import { InputRange } from "./input-range";
|
||||||
import { getClientSideConfig } from "../config/client";
|
|
||||||
|
|
||||||
function SettingItem(props: {
|
function SettingItem(props: {
|
||||||
title: string;
|
title: string;
|
||||||
@ -89,13 +88,13 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
|
|
||||||
const updateStore = useUpdateStore();
|
const updateStore = useUpdateStore();
|
||||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||||
const currentVersion = getClientSideConfig()?.version;
|
const currentVersion = updateStore.version;
|
||||||
const remoteId = updateStore.remoteId;
|
const remoteId = updateStore.remoteVersion;
|
||||||
const hasNewVersion = currentVersion !== remoteId;
|
const hasNewVersion = currentVersion !== remoteId;
|
||||||
|
|
||||||
function checkUpdate(force = false) {
|
function checkUpdate(force = false) {
|
||||||
setCheckingUpdate(true);
|
setCheckingUpdate(true);
|
||||||
updateStore.getLatestCommitId(force).then(() => {
|
updateStore.getLatestVersion(force).then(() => {
|
||||||
setCheckingUpdate(false);
|
setCheckingUpdate(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
17
app/page.tsx
17
app/page.tsx
@ -1,27 +1,14 @@
|
|||||||
import { Analytics } from "@vercel/analytics/react";
|
import { Analytics } from "@vercel/analytics/react";
|
||||||
|
|
||||||
import { Home } from "./components/home";
|
import { Home } from "./components/home";
|
||||||
|
|
||||||
import { getServerSideConfig } from "./config/server";
|
import { getServerSideConfig } from "./config/server";
|
||||||
import { RUNTIME_CONFIG_DOM } from "./constant";
|
|
||||||
|
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
|
||||||
// Danger! Don not write any secret value here!
|
export default async function App() {
|
||||||
// 警告!不要在这里写入任何敏感信息!
|
|
||||||
const DANGER_CONFIG = {
|
|
||||||
needCode: serverConfig?.needCode,
|
|
||||||
};
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
type DangerConfig = typeof DANGER_CONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: "none" }} id={RUNTIME_CONFIG_DOM}>
|
|
||||||
{JSON.stringify(DANGER_CONFIG)}
|
|
||||||
</div>
|
|
||||||
<Home />
|
<Home />
|
||||||
{serverConfig?.isVercel && <Analytics />}
|
{serverConfig?.isVercel && <Analytics />}
|
||||||
</>
|
</>
|
||||||
|
@ -1,26 +1,33 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { getClientSideConfig } from "../config/client";
|
|
||||||
|
|
||||||
export interface AccessControlStore {
|
export interface AccessControlStore {
|
||||||
accessCode: string;
|
accessCode: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
|
||||||
|
needCode: boolean;
|
||||||
|
|
||||||
updateToken: (_: string) => void;
|
updateToken: (_: string) => void;
|
||||||
updateCode: (_: string) => void;
|
updateCode: (_: string) => void;
|
||||||
enabledAccessControl: () => boolean;
|
enabledAccessControl: () => boolean;
|
||||||
isAuthorized: () => boolean;
|
isAuthorized: () => boolean;
|
||||||
|
fetch: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ACCESS_KEY = "access-control";
|
export const ACCESS_KEY = "access-control";
|
||||||
|
|
||||||
|
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
|
||||||
|
|
||||||
export const useAccessStore = create<AccessControlStore>()(
|
export const useAccessStore = create<AccessControlStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
token: "",
|
token: "",
|
||||||
accessCode: "",
|
accessCode: "",
|
||||||
|
needCode: true,
|
||||||
enabledAccessControl() {
|
enabledAccessControl() {
|
||||||
return !!getClientSideConfig()?.needCode;
|
get().fetch();
|
||||||
|
|
||||||
|
return get().needCode;
|
||||||
},
|
},
|
||||||
updateCode(code: string) {
|
updateCode(code: string) {
|
||||||
set((state) => ({ accessCode: code }));
|
set((state) => ({ accessCode: code }));
|
||||||
@ -34,6 +41,25 @@ export const useAccessStore = create<AccessControlStore>()(
|
|||||||
!!get().token || !!get().accessCode || !get().enabledAccessControl()
|
!!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,
|
name: ACCESS_KEY,
|
||||||
|
@ -1,28 +1,46 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { getClientSideConfig } from "../config/client";
|
|
||||||
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
import { FETCH_COMMIT_URL, FETCH_TAG_URL } from "../constant";
|
||||||
|
|
||||||
export interface UpdateStore {
|
export interface UpdateStore {
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
remoteId: string;
|
remoteVersion: string;
|
||||||
|
|
||||||
getLatestCommitId: (force: boolean) => Promise<string>;
|
version: string;
|
||||||
|
getLatestVersion: (force: boolean) => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UPDATE_KEY = "chat-update";
|
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<UpdateStore>()(
|
export const useUpdateStore = create<UpdateStore>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
lastUpdate: 0,
|
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 overTenMins = Date.now() - get().lastUpdate > 10 * 60 * 1000;
|
||||||
const shouldFetch = force || overTenMins;
|
const shouldFetch = force || overTenMins;
|
||||||
if (!shouldFetch) {
|
if (!shouldFetch) {
|
||||||
return getClientSideConfig()?.version ?? "";
|
return get().version ?? "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -32,13 +50,13 @@ export const useUpdateStore = create<UpdateStore>()(
|
|||||||
const remoteId = (data[0].sha as string).substring(0, 7);
|
const remoteId = (data[0].sha as string).substring(0, 7);
|
||||||
set(() => ({
|
set(() => ({
|
||||||
lastUpdate: Date.now(),
|
lastUpdate: Date.now(),
|
||||||
remoteId,
|
remoteVersion: remoteId,
|
||||||
}));
|
}));
|
||||||
console.log("[Got Upstream] ", remoteId);
|
console.log("[Got Upstream] ", remoteId);
|
||||||
return remoteId;
|
return remoteId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch Upstream Commit Id]", error);
|
console.error("[Fetch Upstream Commit Id]", error);
|
||||||
return getClientSideConfig()?.version ?? "";
|
return get().version ?? "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -32,7 +32,7 @@ export function middleware(req: NextRequest) {
|
|||||||
|
|
||||||
// inject api key
|
// inject api key
|
||||||
if (!token) {
|
if (!token) {
|
||||||
const apiKey = process.env.OPENAI_API_KEY;
|
const apiKey = serverConfig.apiKey;
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
console.log("[Auth] set system token");
|
console.log("[Auth] set system token");
|
||||||
req.headers.set("token", apiKey);
|
req.headers.set("token", apiKey);
|
||||||
|
Loading…
Reference in New Issue
Block a user