Skip to content

Commit 50cd33d

Browse files
committed
feat: ChatGPTNextWeb#1000 ready to support client-side only
1 parent e6b49a6 commit 50cd33d

File tree

13 files changed

+133
-36
lines changed

13 files changed

+133
-36
lines changed

app/client/api.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ACCESS_CODE_PREFIX } from "../constant";
2-
import { ChatMessage, ModelConfig, ModelType, useAccessStore } from "../store";
2+
import { ChatMessage, ModelType, useAccessStore } from "../store";
33
import { ChatGPTApi } from "./platforms/openai";
44

55
export const ROLES = ["system", "user", "assistant"] as const;
@@ -42,6 +42,27 @@ export abstract class LLMApi {
4242
abstract usage(): Promise<LLMUsage>;
4343
}
4444

45+
type ProviderName = "openai" | "azure" | "claude" | "palm";
46+
47+
interface Model {
48+
name: string;
49+
provider: ProviderName;
50+
ctxlen: number;
51+
}
52+
53+
interface ChatProvider {
54+
name: ProviderName;
55+
apiConfig: {
56+
baseUrl: string;
57+
apiKey: string;
58+
summaryModel: Model;
59+
};
60+
models: Model[];
61+
62+
chat: () => void;
63+
usage: () => void;
64+
}
65+
4566
export class ClientApi {
4667
public llm: LLMApi;
4768

app/components/home.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { SideBar } from "./sidebar";
2525
import { useAppConfig } from "../store/config";
2626
import { AuthPage } from "./auth";
27+
import { getClientConfig } from "../config/client";
2728

2829
export function Loading(props: { noLogo?: boolean }) {
2930
return (
@@ -147,6 +148,10 @@ function Screen() {
147148
export function Home() {
148149
useSwitchTheme();
149150

151+
useEffect(() => {
152+
console.log("[Config] got config from build time", getClientConfig());
153+
}, []);
154+
150155
if (!useHasHydrated()) {
151156
return <Loading />;
152157
}

app/components/settings.tsx

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
1+
import { useState, useEffect, useMemo } from "react";
22

33
import styles from "./settings.module.scss";
44

@@ -45,6 +45,7 @@ import { ErrorBoundary } from "./error";
4545
import { InputRange } from "./input-range";
4646
import { useNavigate } from "react-router-dom";
4747
import { Avatar, AvatarPicker } from "./emoji";
48+
import { getClientConfig } from "../config/client";
4849

4950
function EditPromptModal(props: { id: number; onClose: () => void }) {
5051
const promptStore = usePromptStore();
@@ -541,6 +542,21 @@ export function Settings() {
541542
/>
542543
)}
543544
</ListItem>
545+
546+
{!accessStore.hideUserApiKey ? (
547+
<ListItem
548+
title={Locale.Settings.Endpoint.Title}
549+
subTitle={Locale.Settings.Endpoint.SubTitle}
550+
>
551+
<input
552+
type="text"
553+
value={accessStore.openaiUrl}
554+
onChange={(e) =>
555+
accessStore.updateOpenAiUrl(e.currentTarget.value)
556+
}
557+
></input>
558+
</ListItem>
559+
) : null}
544560
</List>
545561

546562
<List>

app/config/build.ts

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1-
const COMMIT_ID: string = (() => {
2-
try {
3-
const childProcess = require("child_process");
4-
return childProcess
5-
.execSync('git log -1 --format="%at000" --date=unix')
6-
.toString()
7-
.trim();
8-
} catch (e) {
9-
console.error("[Build Config] No git or not from git repo.");
10-
return "unknown";
11-
}
12-
})();
13-
141
export const getBuildConfig = () => {
152
if (typeof process === "undefined") {
163
throw Error(
174
"[Server Config] you are importing a nodejs-only module outside of nodejs",
185
);
196
}
207

8+
const COMMIT_ID: string = (() => {
9+
try {
10+
const childProcess = require("child_process");
11+
return childProcess
12+
.execSync('git log -1 --format="%at000" --date=unix')
13+
.toString()
14+
.trim();
15+
} catch (e) {
16+
console.error("[Build Config] No git or not from git repo.");
17+
return "unknown";
18+
}
19+
})();
20+
2121
return {
2222
commitId: COMMIT_ID,
23+
buildMode: process.env.BUILD_MODE ?? "standalone",
2324
};
2425
};
26+
27+
export type BuildConfig = ReturnType<typeof getBuildConfig>;

app/config/client.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { BuildConfig, getBuildConfig } from "./build";
2+
3+
export function getClientConfig() {
4+
if (typeof document !== "undefined") {
5+
// client side
6+
return JSON.parse(queryMeta("config")) as BuildConfig;
7+
}
8+
9+
if (typeof process !== "undefined") {
10+
// server side
11+
return getBuildConfig();
12+
}
13+
}
14+
15+
function queryMeta(key: string, defaultValue?: string): string {
16+
let ret: string;
17+
if (document) {
18+
const meta = document.head.querySelector(
19+
`meta[name='${key}']`,
20+
) as HTMLMetaElement;
21+
ret = meta?.content ?? "";
22+
} else {
23+
ret = defaultValue ?? "";
24+
}
25+
26+
return ret;
27+
}

app/config/server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ declare global {
1010
VERCEL?: string;
1111
HIDE_USER_API_KEY?: string; // disable user's api key input
1212
DISABLE_GPT4?: string; // allow user to use gpt-4 or not
13+
BUILD_MODE?: "standalone" | "export";
1314
}
1415
}
1516
}

app/constant.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const UPDATE_URL = `${REPO_URL}#keep-updated`;
66
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
77
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
88
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
9+
export const DEFAULT_API_HOST = "https://chatgpt.nextweb.fun/api/proxy";
910

1011
export enum Path {
1112
Home = "/",

app/layout.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import "./styles/globals.scss";
33
import "./styles/markdown.scss";
44
import "./styles/highlight.scss";
55
import { getBuildConfig } from "./config/build";
6-
7-
const buildConfig = getBuildConfig();
6+
import { getClientConfig } from "./config/client";
87

98
export const metadata = {
109
title: "ChatGPT Next Web",
@@ -32,7 +31,7 @@ export default function RootLayout({
3231
return (
3332
<html lang="en">
3433
<head>
35-
<meta name="version" content={buildConfig.commitId} />
34+
<meta name="config" content={JSON.stringify(getClientConfig())} />
3635
<link rel="manifest" href="/site.webmanifest"></link>
3736
<script src="/serviceWorkerRegister.js" defer></script>
3837
</head>

app/locales/cn.ts

+4
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ const cn = {
180180
SubTitle: "管理员已开启加密访问",
181181
Placeholder: "请输入访问密码",
182182
},
183+
Endpoint: {
184+
Title: "接口地址",
185+
SubTitle: "除默认地址外,必须包含 http(s)://",
186+
},
183187
Model: "模型 (model)",
184188
Temperature: {
185189
Title: "随机性 (temperature)",

app/locales/en.ts

+4
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ const en: RequiredLocaleType = {
181181
SubTitle: "Access control enabled",
182182
Placeholder: "Need Access Code",
183183
},
184+
Endpoint: {
185+
Title: "Endpoint",
186+
SubTitle: "Custom endpoint must start with http(s)://",
187+
},
184188
Model: "Model",
185189
Temperature: {
186190
Title: "Temperature",

app/store/access.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { create } from "zustand";
22
import { persist } from "zustand/middleware";
3-
import { StoreKey } from "../constant";
3+
import { DEFAULT_API_HOST, StoreKey } from "../constant";
44
import { getHeaders } from "../client/api";
55
import { BOT_HELLO } from "./chat";
66
import { ALL_MODELS } from "./config";
7+
import { getClientConfig } from "../config/client";
78

89
export interface AccessControlStore {
910
accessCode: string;
@@ -15,21 +16,26 @@ export interface AccessControlStore {
1516

1617
updateToken: (_: string) => void;
1718
updateCode: (_: string) => void;
19+
updateOpenAiUrl: (_: string) => void;
1820
enabledAccessControl: () => boolean;
1921
isAuthorized: () => boolean;
2022
fetch: () => void;
2123
}
2224

2325
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
2426

27+
const DEFAULT_OPENAI_URL =
28+
getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : "/api/openai/";
29+
console.log("[API] default openai url", DEFAULT_OPENAI_URL);
30+
2531
export const useAccessStore = create<AccessControlStore>()(
2632
persist(
2733
(set, get) => ({
2834
token: "",
2935
accessCode: "",
3036
needCode: true,
3137
hideUserApiKey: false,
32-
openaiUrl: "/api/openai/",
38+
openaiUrl: DEFAULT_OPENAI_URL,
3339

3440
enabledAccessControl() {
3541
get().fetch();
@@ -42,6 +48,9 @@ export const useAccessStore = create<AccessControlStore>()(
4248
updateToken(token: string) {
4349
set(() => ({ token }));
4450
},
51+
updateOpenAiUrl(url: string) {
52+
set(() => ({ openaiUrl: url }));
53+
},
4554
isAuthorized() {
4655
get().fetch();
4756

app/store/update.ts

+2-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { create } from "zustand";
22
import { persist } from "zustand/middleware";
33
import { FETCH_COMMIT_URL, StoreKey } from "../constant";
44
import { api } from "../client/api";
5-
import { showToast } from "../components/ui-lib";
5+
import { getClientConfig } from "../config/client";
66

77
export interface UpdateStore {
88
lastUpdate: number;
@@ -17,20 +17,6 @@ export interface UpdateStore {
1717
updateUsage: (force?: boolean) => Promise<void>;
1818
}
1919

20-
function queryMeta(key: string, defaultValue?: string): string {
21-
let ret: string;
22-
if (document) {
23-
const meta = document.head.querySelector(
24-
`meta[name='${key}']`,
25-
) as HTMLMetaElement;
26-
ret = meta?.content ?? "";
27-
} else {
28-
ret = defaultValue ?? "";
29-
}
30-
31-
return ret;
32-
}
33-
3420
const ONE_MINUTE = 60 * 1000;
3521

3622
export const useUpdateStore = create<UpdateStore>()(
@@ -44,7 +30,7 @@ export const useUpdateStore = create<UpdateStore>()(
4430
version: "unknown",
4531

4632
async getLatestVersion(force = false) {
47-
set(() => ({ version: queryMeta("version") ?? "unknown" }));
33+
set(() => ({ version: getClientConfig()?.commitId ?? "unknown" }));
4834

4935
const overTenMins = Date.now() - get().lastUpdate > 10 * ONE_MINUTE;
5036
if (!force && !overTenMins) return;

next.config.mjs

+21
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@ const nextConfig = {
1515
};
1616

1717
if (mode !== "export") {
18+
nextConfig.headers = async () => {
19+
return [
20+
{
21+
source: "/:path*",
22+
headers: [
23+
{ key: "Access-Control-Allow-Credentials", value: "true" },
24+
{ key: "Access-Control-Allow-Origin", value: "*" },
25+
{
26+
key: "Access-Control-Allow-Methods",
27+
value: "GET,OPTIONS,PATCH,DELETE,POST,PUT",
28+
},
29+
{
30+
key: "Access-Control-Allow-Headers",
31+
value:
32+
"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version",
33+
},
34+
],
35+
},
36+
];
37+
};
38+
1839
nextConfig.rewrites = async () => {
1940
const ret = [
2041
{

0 commit comments

Comments
 (0)