mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: close #935 add azure support
This commit is contained in:
		@@ -28,7 +28,7 @@ export function auth(req: NextRequest) {
 | 
				
			|||||||
  const authToken = req.headers.get("Authorization") ?? "";
 | 
					  const authToken = req.headers.get("Authorization") ?? "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // check if it is openai api key or user token
 | 
					  // check if it is openai api key or user token
 | 
				
			||||||
  const { accessCode, apiKey: token } = parseApiKey(authToken);
 | 
					  const { accessCode, apiKey } = parseApiKey(authToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const hashedCode = md5.hash(accessCode ?? "").trim();
 | 
					  const hashedCode = md5.hash(accessCode ?? "").trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,7 +39,7 @@ export function auth(req: NextRequest) {
 | 
				
			|||||||
  console.log("[User IP] ", getIP(req));
 | 
					  console.log("[User IP] ", getIP(req));
 | 
				
			||||||
  console.log("[Time] ", new Date().toLocaleString());
 | 
					  console.log("[Time] ", new Date().toLocaleString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !token) {
 | 
					  if (serverConfig.needCode && !serverConfig.codes.has(hashedCode) && !apiKey) {
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      error: true,
 | 
					      error: true,
 | 
				
			||||||
      msg: !accessCode ? "empty access code" : "wrong access code",
 | 
					      msg: !accessCode ? "empty access code" : "wrong access code",
 | 
				
			||||||
@@ -47,11 +47,17 @@ export function auth(req: NextRequest) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // if user does not provide an api key, inject system api key
 | 
					  // if user does not provide an api key, inject system api key
 | 
				
			||||||
  if (!token) {
 | 
					  if (!apiKey) {
 | 
				
			||||||
    const apiKey = serverConfig.apiKey;
 | 
					    const serverApiKey = serverConfig.isAzure
 | 
				
			||||||
    if (apiKey) {
 | 
					      ? serverConfig.azureApiKey
 | 
				
			||||||
 | 
					      : serverConfig.apiKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (serverApiKey) {
 | 
				
			||||||
      console.log("[Auth] use system api key");
 | 
					      console.log("[Auth] use system api key");
 | 
				
			||||||
      req.headers.set("Authorization", `Bearer ${apiKey}`);
 | 
					      req.headers.set(
 | 
				
			||||||
 | 
					        "Authorization",
 | 
				
			||||||
 | 
					        `${serverConfig.isAzure ? "" : "Bearer "}${serverApiKey}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      console.log("[Auth] admin did not provide an api key");
 | 
					      console.log("[Auth] admin did not provide an api key");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,24 @@
 | 
				
			|||||||
import { NextRequest, NextResponse } from "next/server";
 | 
					import { NextRequest, NextResponse } from "next/server";
 | 
				
			||||||
import { getServerSideConfig } from "../config/server";
 | 
					import { getServerSideConfig } from "../config/server";
 | 
				
			||||||
import { DEFAULT_MODELS, OPENAI_BASE_URL } from "../constant";
 | 
					import { DEFAULT_MODELS, OPENAI_BASE_URL } from "../constant";
 | 
				
			||||||
import { collectModelTable, collectModels } from "../utils/model";
 | 
					import { collectModelTable } from "../utils/model";
 | 
				
			||||||
 | 
					import { makeAzurePath } from "../azure";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const serverConfig = getServerSideConfig();
 | 
					const serverConfig = getServerSideConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function requestOpenai(req: NextRequest) {
 | 
					export async function requestOpenai(req: NextRequest) {
 | 
				
			||||||
  const controller = new AbortController();
 | 
					  const controller = new AbortController();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const authValue = req.headers.get("Authorization") ?? "";
 | 
					  const authValue = req.headers.get("Authorization") ?? "";
 | 
				
			||||||
  const openaiPath = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
 | 
					  const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll(
 | 
				
			||||||
    "/api/openai/",
 | 
					    "/api/openai/",
 | 
				
			||||||
    "",
 | 
					    "",
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let baseUrl = serverConfig.baseUrl ?? OPENAI_BASE_URL;
 | 
					  let baseUrl =
 | 
				
			||||||
 | 
					    serverConfig.azureUrl ?? serverConfig.baseUrl ?? OPENAI_BASE_URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!baseUrl.startsWith("http")) {
 | 
					  if (!baseUrl.startsWith("http")) {
 | 
				
			||||||
    baseUrl = `https://${baseUrl}`;
 | 
					    baseUrl = `https://${baseUrl}`;
 | 
				
			||||||
@@ -23,7 +28,7 @@ export async function requestOpenai(req: NextRequest) {
 | 
				
			|||||||
    baseUrl = baseUrl.slice(0, -1);
 | 
					    baseUrl = baseUrl.slice(0, -1);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log("[Proxy] ", openaiPath);
 | 
					  console.log("[Proxy] ", path);
 | 
				
			||||||
  console.log("[Base Url]", baseUrl);
 | 
					  console.log("[Base Url]", baseUrl);
 | 
				
			||||||
  console.log("[Org ID]", serverConfig.openaiOrgId);
 | 
					  console.log("[Org ID]", serverConfig.openaiOrgId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,14 +39,24 @@ export async function requestOpenai(req: NextRequest) {
 | 
				
			|||||||
    10 * 60 * 1000,
 | 
					    10 * 60 * 1000,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const fetchUrl = `${baseUrl}/${openaiPath}`;
 | 
					  if (serverConfig.isAzure) {
 | 
				
			||||||
 | 
					    if (!serverConfig.azureApiVersion) {
 | 
				
			||||||
 | 
					      return NextResponse.json({
 | 
				
			||||||
 | 
					        error: true,
 | 
				
			||||||
 | 
					        message: `missing AZURE_API_VERSION in server env vars`,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    path = makeAzurePath(path, serverConfig.azureApiVersion);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const fetchUrl = `${baseUrl}/${path}`;
 | 
				
			||||||
  const fetchOptions: RequestInit = {
 | 
					  const fetchOptions: RequestInit = {
 | 
				
			||||||
    headers: {
 | 
					    headers: {
 | 
				
			||||||
      "Content-Type": "application/json",
 | 
					      "Content-Type": "application/json",
 | 
				
			||||||
      "Cache-Control": "no-store",
 | 
					      "Cache-Control": "no-store",
 | 
				
			||||||
      Authorization: authValue,
 | 
					      [authHeaderName]: authValue,
 | 
				
			||||||
      ...(process.env.OPENAI_ORG_ID && {
 | 
					      ...(serverConfig.openaiOrgId && {
 | 
				
			||||||
        "OpenAI-Organization": process.env.OPENAI_ORG_ID,
 | 
					        "OpenAI-Organization": serverConfig.openaiOrgId,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    method: req.method,
 | 
					    method: req.method,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								app/azure.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/azure.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					export function makeAzurePath(path: string, apiVersion: string) {
 | 
				
			||||||
 | 
					  // should omit /v1 prefix
 | 
				
			||||||
 | 
					  path = path.replaceAll("v1/", "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // should add api-key to query string
 | 
				
			||||||
 | 
					  path += `${path.includes("?") ? "&" : "?"}api-version=${apiVersion}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return path;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { getClientConfig } from "../config/client";
 | 
					import { getClientConfig } from "../config/client";
 | 
				
			||||||
import { ACCESS_CODE_PREFIX } from "../constant";
 | 
					import { ACCESS_CODE_PREFIX, Azure, ServiceProvider } from "../constant";
 | 
				
			||||||
import { ChatMessage, ModelType, useAccessStore } from "../store";
 | 
					import { ChatMessage, ModelType, useAccessStore } from "../store";
 | 
				
			||||||
import { ChatGPTApi } from "./platforms/openai";
 | 
					import { ChatGPTApi } from "./platforms/openai";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -127,22 +127,26 @@ export const api = new ClientApi();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function getHeaders() {
 | 
					export function getHeaders() {
 | 
				
			||||||
  const accessStore = useAccessStore.getState();
 | 
					  const accessStore = useAccessStore.getState();
 | 
				
			||||||
  let headers: Record<string, string> = {
 | 
					  const headers: Record<string, string> = {
 | 
				
			||||||
    "Content-Type": "application/json",
 | 
					    "Content-Type": "application/json",
 | 
				
			||||||
    "x-requested-with": "XMLHttpRequest",
 | 
					    "x-requested-with": "XMLHttpRequest",
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const makeBearer = (token: string) => `Bearer ${token.trim()}`;
 | 
					  const isAzure = accessStore.provider === ServiceProvider.Azure;
 | 
				
			||||||
 | 
					  const authHeader = isAzure ? "api-key" : "Authorization";
 | 
				
			||||||
 | 
					  const apiKey = isAzure ? accessStore.azureApiKey : accessStore.openaiApiKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const makeBearer = (s: string) => `${isAzure ? "" : "Bearer "}${s.trim()}`;
 | 
				
			||||||
  const validString = (x: string) => x && x.length > 0;
 | 
					  const validString = (x: string) => x && x.length > 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // use user's api key first
 | 
					  // use user's api key first
 | 
				
			||||||
  if (validString(accessStore.token)) {
 | 
					  if (validString(apiKey)) {
 | 
				
			||||||
    headers.Authorization = makeBearer(accessStore.token);
 | 
					    headers[authHeader] = makeBearer(apiKey);
 | 
				
			||||||
  } else if (
 | 
					  } else if (
 | 
				
			||||||
    accessStore.enabledAccessControl() &&
 | 
					    accessStore.enabledAccessControl() &&
 | 
				
			||||||
    validString(accessStore.accessCode)
 | 
					    validString(accessStore.accessCode)
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    headers.Authorization = makeBearer(
 | 
					    headers[authHeader] = makeBearer(
 | 
				
			||||||
      ACCESS_CODE_PREFIX + accessStore.accessCode,
 | 
					      ACCESS_CODE_PREFIX + accessStore.accessCode,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,10 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  ApiPath,
 | 
				
			||||||
  DEFAULT_API_HOST,
 | 
					  DEFAULT_API_HOST,
 | 
				
			||||||
  DEFAULT_MODELS,
 | 
					  DEFAULT_MODELS,
 | 
				
			||||||
  OpenaiPath,
 | 
					  OpenaiPath,
 | 
				
			||||||
  REQUEST_TIMEOUT_MS,
 | 
					  REQUEST_TIMEOUT_MS,
 | 
				
			||||||
 | 
					  ServiceProvider,
 | 
				
			||||||
} from "@/app/constant";
 | 
					} from "@/app/constant";
 | 
				
			||||||
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
 | 
					import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,6 +16,7 @@ import {
 | 
				
			|||||||
} from "@fortaine/fetch-event-source";
 | 
					} from "@fortaine/fetch-event-source";
 | 
				
			||||||
import { prettyObject } from "@/app/utils/format";
 | 
					import { prettyObject } from "@/app/utils/format";
 | 
				
			||||||
import { getClientConfig } from "@/app/config/client";
 | 
					import { getClientConfig } from "@/app/config/client";
 | 
				
			||||||
 | 
					import { makeAzurePath } from "@/app/azure";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface OpenAIListModelResponse {
 | 
					export interface OpenAIListModelResponse {
 | 
				
			||||||
  object: string;
 | 
					  object: string;
 | 
				
			||||||
@@ -28,20 +31,35 @@ export class ChatGPTApi implements LLMApi {
 | 
				
			|||||||
  private disableListModels = true;
 | 
					  private disableListModels = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  path(path: string): string {
 | 
					  path(path: string): string {
 | 
				
			||||||
    let openaiUrl = useAccessStore.getState().openaiUrl;
 | 
					    const accessStore = useAccessStore.getState();
 | 
				
			||||||
    const apiPath = "/api/openai";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (openaiUrl.length === 0) {
 | 
					    const isAzure = accessStore.provider === ServiceProvider.Azure;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isAzure && !accessStore.isValidAzure()) {
 | 
				
			||||||
 | 
					      throw Error(
 | 
				
			||||||
 | 
					        "incomplete azure config, please check it in your settings page",
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let baseUrl = isAzure ? accessStore.azureUrl : accessStore.openaiUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (baseUrl.length === 0) {
 | 
				
			||||||
      const isApp = !!getClientConfig()?.isApp;
 | 
					      const isApp = !!getClientConfig()?.isApp;
 | 
				
			||||||
      openaiUrl = isApp ? DEFAULT_API_HOST : apiPath;
 | 
					      baseUrl = isApp ? DEFAULT_API_HOST : ApiPath.OpenAI;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (openaiUrl.endsWith("/")) {
 | 
					
 | 
				
			||||||
      openaiUrl = openaiUrl.slice(0, openaiUrl.length - 1);
 | 
					    if (baseUrl.endsWith("/")) {
 | 
				
			||||||
 | 
					      baseUrl = baseUrl.slice(0, baseUrl.length - 1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!openaiUrl.startsWith("http") && !openaiUrl.startsWith(apiPath)) {
 | 
					    if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.OpenAI)) {
 | 
				
			||||||
      openaiUrl = "https://" + openaiUrl;
 | 
					      baseUrl = "https://" + baseUrl;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return [openaiUrl, path].join("/");
 | 
					
 | 
				
			||||||
 | 
					    if (isAzure) {
 | 
				
			||||||
 | 
					      path = makeAzurePath(path, accessStore.azureApiVersion);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [baseUrl, path].join("/");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  extractMessage(res: any) {
 | 
					  extractMessage(res: any) {
 | 
				
			||||||
@@ -156,14 +174,20 @@ export class ChatGPTApi implements LLMApi {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            const text = msg.data;
 | 
					            const text = msg.data;
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
              const json = JSON.parse(text);
 | 
					              const json = JSON.parse(text) as {
 | 
				
			||||||
              const delta = json.choices[0].delta.content;
 | 
					                choices: Array<{
 | 
				
			||||||
 | 
					                  delta: {
 | 
				
			||||||
 | 
					                    content: string;
 | 
				
			||||||
 | 
					                  };
 | 
				
			||||||
 | 
					                }>;
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					              const delta = json.choices[0]?.delta?.content;
 | 
				
			||||||
              if (delta) {
 | 
					              if (delta) {
 | 
				
			||||||
                responseText += delta;
 | 
					                responseText += delta;
 | 
				
			||||||
                options.onUpdate?.(responseText, delta);
 | 
					                options.onUpdate?.(responseText, delta);
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            } catch (e) {
 | 
					            } catch (e) {
 | 
				
			||||||
              console.error("[Request] parse error", text, msg);
 | 
					              console.error("[Request] parse error", text);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          onclose() {
 | 
					          onclose() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ export function AuthPage() {
 | 
				
			|||||||
  const goChat = () => navigate(Path.Chat);
 | 
					  const goChat = () => navigate(Path.Chat);
 | 
				
			||||||
  const resetAccessCode = () => {
 | 
					  const resetAccessCode = () => {
 | 
				
			||||||
    accessStore.update((access) => {
 | 
					    accessStore.update((access) => {
 | 
				
			||||||
      access.token = "";
 | 
					      access.openaiApiKey = "";
 | 
				
			||||||
      access.accessCode = "";
 | 
					      access.accessCode = "";
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }; // Reset access code to empty string
 | 
					  }; // Reset access code to empty string
 | 
				
			||||||
@@ -57,10 +57,10 @@ export function AuthPage() {
 | 
				
			|||||||
            className={styles["auth-input"]}
 | 
					            className={styles["auth-input"]}
 | 
				
			||||||
            type="password"
 | 
					            type="password"
 | 
				
			||||||
            placeholder={Locale.Settings.Token.Placeholder}
 | 
					            placeholder={Locale.Settings.Token.Placeholder}
 | 
				
			||||||
            value={accessStore.token}
 | 
					            value={accessStore.openaiApiKey}
 | 
				
			||||||
            onChange={(e) => {
 | 
					            onChange={(e) => {
 | 
				
			||||||
              accessStore.update(
 | 
					              accessStore.update(
 | 
				
			||||||
                (access) => (access.token = e.currentTarget.value),
 | 
					                (access) => (access.openaiApiKey = e.currentTarget.value),
 | 
				
			||||||
              );
 | 
					              );
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -998,7 +998,9 @@ function _Chat() {
 | 
				
			|||||||
          ).then((res) => {
 | 
					          ).then((res) => {
 | 
				
			||||||
            if (!res) return;
 | 
					            if (!res) return;
 | 
				
			||||||
            if (payload.key) {
 | 
					            if (payload.key) {
 | 
				
			||||||
              accessStore.update((access) => (access.token = payload.key!));
 | 
					              accessStore.update(
 | 
				
			||||||
 | 
					                (access) => (access.openaiApiKey = payload.key!),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (payload.url) {
 | 
					            if (payload.url) {
 | 
				
			||||||
              accessStore.update((access) => (access.openaiUrl = payload.url!));
 | 
					              accessStore.update((access) => (access.openaiUrl = payload.url!));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,10 +51,13 @@ import Locale, {
 | 
				
			|||||||
import { copyToClipboard } from "../utils";
 | 
					import { copyToClipboard } from "../utils";
 | 
				
			||||||
import Link from "next/link";
 | 
					import Link from "next/link";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  Azure,
 | 
				
			||||||
  OPENAI_BASE_URL,
 | 
					  OPENAI_BASE_URL,
 | 
				
			||||||
  Path,
 | 
					  Path,
 | 
				
			||||||
  RELEASE_URL,
 | 
					  RELEASE_URL,
 | 
				
			||||||
  STORAGE_KEY,
 | 
					  STORAGE_KEY,
 | 
				
			||||||
 | 
					  ServiceProvider,
 | 
				
			||||||
 | 
					  SlotID,
 | 
				
			||||||
  UPDATE_URL,
 | 
					  UPDATE_URL,
 | 
				
			||||||
} from "../constant";
 | 
					} from "../constant";
 | 
				
			||||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
 | 
					import { Prompt, SearchService, usePromptStore } from "../store/prompt";
 | 
				
			||||||
@@ -580,8 +583,16 @@ export function Settings() {
 | 
				
			|||||||
  const accessStore = useAccessStore();
 | 
					  const accessStore = useAccessStore();
 | 
				
			||||||
  const shouldHideBalanceQuery = useMemo(() => {
 | 
					  const shouldHideBalanceQuery = useMemo(() => {
 | 
				
			||||||
    const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
 | 
					    const isOpenAiUrl = accessStore.openaiUrl.includes(OPENAI_BASE_URL);
 | 
				
			||||||
    return accessStore.hideBalanceQuery || isOpenAiUrl;
 | 
					    return (
 | 
				
			||||||
  }, [accessStore.hideBalanceQuery, accessStore.openaiUrl]);
 | 
					      accessStore.hideBalanceQuery ||
 | 
				
			||||||
 | 
					      isOpenAiUrl ||
 | 
				
			||||||
 | 
					      accessStore.provider === ServiceProvider.Azure
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }, [
 | 
				
			||||||
 | 
					    accessStore.hideBalanceQuery,
 | 
				
			||||||
 | 
					    accessStore.openaiUrl,
 | 
				
			||||||
 | 
					    accessStore.provider,
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const usage = {
 | 
					  const usage = {
 | 
				
			||||||
    used: updateStore.used,
 | 
					    used: updateStore.used,
 | 
				
			||||||
@@ -877,16 +888,16 @@ export function Settings() {
 | 
				
			|||||||
          </ListItem>
 | 
					          </ListItem>
 | 
				
			||||||
        </List>
 | 
					        </List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <List>
 | 
					        <List id={SlotID.CustomModel}>
 | 
				
			||||||
          {showAccessCode ? (
 | 
					          {showAccessCode && (
 | 
				
			||||||
            <ListItem
 | 
					            <ListItem
 | 
				
			||||||
              title={Locale.Settings.AccessCode.Title}
 | 
					              title={Locale.Settings.Access.AccessCode.Title}
 | 
				
			||||||
              subTitle={Locale.Settings.AccessCode.SubTitle}
 | 
					              subTitle={Locale.Settings.Access.AccessCode.SubTitle}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <PasswordInput
 | 
					              <PasswordInput
 | 
				
			||||||
                value={accessStore.accessCode}
 | 
					                value={accessStore.accessCode}
 | 
				
			||||||
                type="text"
 | 
					                type="text"
 | 
				
			||||||
                placeholder={Locale.Settings.AccessCode.Placeholder}
 | 
					                placeholder={Locale.Settings.Access.AccessCode.Placeholder}
 | 
				
			||||||
                onChange={(e) => {
 | 
					                onChange={(e) => {
 | 
				
			||||||
                  accessStore.update(
 | 
					                  accessStore.update(
 | 
				
			||||||
                    (access) => (access.accessCode = e.currentTarget.value),
 | 
					                    (access) => (access.accessCode = e.currentTarget.value),
 | 
				
			||||||
@@ -894,44 +905,152 @@ export function Settings() {
 | 
				
			|||||||
                }}
 | 
					                }}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </ListItem>
 | 
					            </ListItem>
 | 
				
			||||||
          ) : (
 | 
					 | 
				
			||||||
            <></>
 | 
					 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {!accessStore.hideUserApiKey ? (
 | 
					          {!accessStore.hideUserApiKey && (
 | 
				
			||||||
            <>
 | 
					            <>
 | 
				
			||||||
              <ListItem
 | 
					              <ListItem
 | 
				
			||||||
                title={Locale.Settings.Endpoint.Title}
 | 
					                title={Locale.Settings.Access.CustomEndpoint.Title}
 | 
				
			||||||
                subTitle={Locale.Settings.Endpoint.SubTitle}
 | 
					                subTitle={Locale.Settings.Access.CustomEndpoint.SubTitle}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                  type="text"
 | 
					                  type="checkbox"
 | 
				
			||||||
                  value={accessStore.openaiUrl}
 | 
					                  checked={accessStore.useCustomConfig}
 | 
				
			||||||
                  placeholder="https://api.openai.com/"
 | 
					 | 
				
			||||||
                  onChange={(e) =>
 | 
					                  onChange={(e) =>
 | 
				
			||||||
                    accessStore.update(
 | 
					                    accessStore.update(
 | 
				
			||||||
                      (access) => (access.openaiUrl = e.currentTarget.value),
 | 
					                      (access) =>
 | 
				
			||||||
 | 
					                        (access.useCustomConfig = e.currentTarget.checked),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                ></input>
 | 
					                ></input>
 | 
				
			||||||
              </ListItem>
 | 
					              </ListItem>
 | 
				
			||||||
              <ListItem
 | 
					              {accessStore.useCustomConfig && (
 | 
				
			||||||
                title={Locale.Settings.Token.Title}
 | 
					                <>
 | 
				
			||||||
                subTitle={Locale.Settings.Token.SubTitle}
 | 
					                  <ListItem
 | 
				
			||||||
              >
 | 
					                    title={Locale.Settings.Access.Provider.Title}
 | 
				
			||||||
                <PasswordInput
 | 
					                    subTitle={Locale.Settings.Access.Provider.SubTitle}
 | 
				
			||||||
                  value={accessStore.token}
 | 
					                  >
 | 
				
			||||||
                  type="text"
 | 
					                    <Select
 | 
				
			||||||
                  placeholder={Locale.Settings.Token.Placeholder}
 | 
					                      value={accessStore.provider}
 | 
				
			||||||
                  onChange={(e) => {
 | 
					                      onChange={(e) => {
 | 
				
			||||||
                    accessStore.update(
 | 
					                        accessStore.update(
 | 
				
			||||||
                      (access) => (access.token = e.currentTarget.value),
 | 
					                          (access) =>
 | 
				
			||||||
                    );
 | 
					                            (access.provider = e.target
 | 
				
			||||||
                  }}
 | 
					                              .value as ServiceProvider),
 | 
				
			||||||
                />
 | 
					                        );
 | 
				
			||||||
              </ListItem>
 | 
					                      }}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      {Object.entries(ServiceProvider).map(([k, v]) => (
 | 
				
			||||||
 | 
					                        <option value={v} key={k}>
 | 
				
			||||||
 | 
					                          {k}
 | 
				
			||||||
 | 
					                        </option>
 | 
				
			||||||
 | 
					                      ))}
 | 
				
			||||||
 | 
					                    </Select>
 | 
				
			||||||
 | 
					                  </ListItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  {accessStore.provider === "OpenAI" ? (
 | 
				
			||||||
 | 
					                    <>
 | 
				
			||||||
 | 
					                      <ListItem
 | 
				
			||||||
 | 
					                        title={Locale.Settings.Access.OpenAI.Endpoint.Title}
 | 
				
			||||||
 | 
					                        subTitle={
 | 
				
			||||||
 | 
					                          Locale.Settings.Access.OpenAI.Endpoint.SubTitle
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <input
 | 
				
			||||||
 | 
					                          type="text"
 | 
				
			||||||
 | 
					                          value={accessStore.openaiUrl}
 | 
				
			||||||
 | 
					                          placeholder={OPENAI_BASE_URL}
 | 
				
			||||||
 | 
					                          onChange={(e) =>
 | 
				
			||||||
 | 
					                            accessStore.update(
 | 
				
			||||||
 | 
					                              (access) =>
 | 
				
			||||||
 | 
					                                (access.openaiUrl = e.currentTarget.value),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                        ></input>
 | 
				
			||||||
 | 
					                      </ListItem>
 | 
				
			||||||
 | 
					                      <ListItem
 | 
				
			||||||
 | 
					                        title={Locale.Settings.Access.OpenAI.ApiKey.Title}
 | 
				
			||||||
 | 
					                        subTitle={Locale.Settings.Access.OpenAI.ApiKey.SubTitle}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <PasswordInput
 | 
				
			||||||
 | 
					                          value={accessStore.openaiApiKey}
 | 
				
			||||||
 | 
					                          type="text"
 | 
				
			||||||
 | 
					                          placeholder={
 | 
				
			||||||
 | 
					                            Locale.Settings.Access.OpenAI.ApiKey.Placeholder
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                          onChange={(e) => {
 | 
				
			||||||
 | 
					                            accessStore.update(
 | 
				
			||||||
 | 
					                              (access) =>
 | 
				
			||||||
 | 
					                                (access.openaiApiKey = e.currentTarget.value),
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                          }}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                      </ListItem>
 | 
				
			||||||
 | 
					                    </>
 | 
				
			||||||
 | 
					                  ) : (
 | 
				
			||||||
 | 
					                    <>
 | 
				
			||||||
 | 
					                      <ListItem
 | 
				
			||||||
 | 
					                        title={Locale.Settings.Access.Azure.Endpoint.Title}
 | 
				
			||||||
 | 
					                        subTitle={
 | 
				
			||||||
 | 
					                          Locale.Settings.Access.Azure.Endpoint.SubTitle +
 | 
				
			||||||
 | 
					                          Azure.ExampleEndpoint
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <input
 | 
				
			||||||
 | 
					                          type="text"
 | 
				
			||||||
 | 
					                          value={accessStore.azureUrl}
 | 
				
			||||||
 | 
					                          placeholder={Azure.ExampleEndpoint}
 | 
				
			||||||
 | 
					                          onChange={(e) =>
 | 
				
			||||||
 | 
					                            accessStore.update(
 | 
				
			||||||
 | 
					                              (access) =>
 | 
				
			||||||
 | 
					                                (access.azureUrl = e.currentTarget.value),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                        ></input>
 | 
				
			||||||
 | 
					                      </ListItem>
 | 
				
			||||||
 | 
					                      <ListItem
 | 
				
			||||||
 | 
					                        title={Locale.Settings.Access.Azure.ApiKey.Title}
 | 
				
			||||||
 | 
					                        subTitle={Locale.Settings.Access.Azure.ApiKey.SubTitle}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <PasswordInput
 | 
				
			||||||
 | 
					                          value={accessStore.azureApiKey}
 | 
				
			||||||
 | 
					                          type="text"
 | 
				
			||||||
 | 
					                          placeholder={
 | 
				
			||||||
 | 
					                            Locale.Settings.Access.Azure.ApiKey.Placeholder
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                          onChange={(e) => {
 | 
				
			||||||
 | 
					                            accessStore.update(
 | 
				
			||||||
 | 
					                              (access) =>
 | 
				
			||||||
 | 
					                                (access.azureApiKey = e.currentTarget.value),
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                          }}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                      </ListItem>
 | 
				
			||||||
 | 
					                      <ListItem
 | 
				
			||||||
 | 
					                        title={Locale.Settings.Access.Azure.ApiVerion.Title}
 | 
				
			||||||
 | 
					                        subTitle={
 | 
				
			||||||
 | 
					                          Locale.Settings.Access.Azure.ApiVerion.SubTitle
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        <input
 | 
				
			||||||
 | 
					                          type="text"
 | 
				
			||||||
 | 
					                          value={accessStore.azureApiVersion}
 | 
				
			||||||
 | 
					                          placeholder="2023-08-01-preview"
 | 
				
			||||||
 | 
					                          onChange={(e) =>
 | 
				
			||||||
 | 
					                            accessStore.update(
 | 
				
			||||||
 | 
					                              (access) =>
 | 
				
			||||||
 | 
					                                (access.azureApiVersion =
 | 
				
			||||||
 | 
					                                  e.currentTarget.value),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                        ></input>
 | 
				
			||||||
 | 
					                      </ListItem>
 | 
				
			||||||
 | 
					                    </>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
            </>
 | 
					            </>
 | 
				
			||||||
          ) : null}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {!shouldHideBalanceQuery ? (
 | 
					          {!shouldHideBalanceQuery ? (
 | 
				
			||||||
            <ListItem
 | 
					            <ListItem
 | 
				
			||||||
@@ -960,8 +1079,8 @@ export function Settings() {
 | 
				
			|||||||
          ) : null}
 | 
					          ) : null}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <ListItem
 | 
					          <ListItem
 | 
				
			||||||
            title={Locale.Settings.CustomModel.Title}
 | 
					            title={Locale.Settings.Access.CustomModel.Title}
 | 
				
			||||||
            subTitle={Locale.Settings.CustomModel.SubTitle}
 | 
					            subTitle={Locale.Settings.Access.CustomModel.SubTitle}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <input
 | 
					            <input
 | 
				
			||||||
              type="text"
 | 
					              type="text"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -235,7 +235,7 @@
 | 
				
			|||||||
  .select-with-icon-select {
 | 
					  .select-with-icon-select {
 | 
				
			||||||
    height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
    border: var(--border-in-light);
 | 
					    border: var(--border-in-light);
 | 
				
			||||||
    padding: 10px 25px 10px 10px;
 | 
					    padding: 10px 35px 10px 10px;
 | 
				
			||||||
    border-radius: 10px;
 | 
					    border-radius: 10px;
 | 
				
			||||||
    appearance: none;
 | 
					    appearance: none;
 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,14 +70,12 @@ export function ListItem(props: {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function List(props: {
 | 
					export function List(props: { children: React.ReactNode; id?: string }) {
 | 
				
			||||||
  children:
 | 
					  return (
 | 
				
			||||||
    | Array<JSX.Element | null | undefined>
 | 
					    <div className={styles.list} id={props.id}>
 | 
				
			||||||
    | JSX.Element
 | 
					      {props.children}
 | 
				
			||||||
    | null
 | 
					    </div>
 | 
				
			||||||
    | undefined;
 | 
					  );
 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  return <div className={styles.list}>{props.children}</div>;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Loading() {
 | 
					export function Loading() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,19 +4,28 @@ import { DEFAULT_MODELS } from "../constant";
 | 
				
			|||||||
declare global {
 | 
					declare global {
 | 
				
			||||||
  namespace NodeJS {
 | 
					  namespace NodeJS {
 | 
				
			||||||
    interface ProcessEnv {
 | 
					    interface ProcessEnv {
 | 
				
			||||||
 | 
					      PROXY_URL?: string; // docker only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      OPENAI_API_KEY?: string;
 | 
					      OPENAI_API_KEY?: string;
 | 
				
			||||||
      CODE?: string;
 | 
					      CODE?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      BASE_URL?: string;
 | 
					      BASE_URL?: string;
 | 
				
			||||||
      PROXY_URL?: string;
 | 
					      OPENAI_ORG_ID?: string; // openai only
 | 
				
			||||||
      OPENAI_ORG_ID?: string;
 | 
					
 | 
				
			||||||
      VERCEL?: string;
 | 
					      VERCEL?: string;
 | 
				
			||||||
      HIDE_USER_API_KEY?: string; // disable user's api key input
 | 
					 | 
				
			||||||
      DISABLE_GPT4?: string; // allow user to use gpt-4 or not
 | 
					 | 
				
			||||||
      BUILD_MODE?: "standalone" | "export";
 | 
					      BUILD_MODE?: "standalone" | "export";
 | 
				
			||||||
      BUILD_APP?: string; // is building desktop app
 | 
					      BUILD_APP?: string; // is building desktop app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      HIDE_USER_API_KEY?: string; // disable user's api key input
 | 
				
			||||||
 | 
					      DISABLE_GPT4?: string; // allow user to use gpt-4 or not
 | 
				
			||||||
      ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
 | 
					      ENABLE_BALANCE_QUERY?: string; // allow user to query balance or not
 | 
				
			||||||
      DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
 | 
					      DISABLE_FAST_LINK?: string; // disallow parse settings from url or not
 | 
				
			||||||
      CUSTOM_MODELS?: string; // to control custom models
 | 
					      CUSTOM_MODELS?: string; // to control custom models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // azure only
 | 
				
			||||||
 | 
					      AZURE_URL?: string; // https://{azure-url}/openai/deployments/{deploy-name}
 | 
				
			||||||
 | 
					      AZURE_API_KEY?: string;
 | 
				
			||||||
 | 
					      AZURE_API_VERSION?: string;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -41,7 +50,7 @@ export const getServerSideConfig = () => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let disableGPT4 = !!process.env.DISABLE_GPT4;
 | 
					  const disableGPT4 = !!process.env.DISABLE_GPT4;
 | 
				
			||||||
  let customModels = process.env.CUSTOM_MODELS ?? "";
 | 
					  let customModels = process.env.CUSTOM_MODELS ?? "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (disableGPT4) {
 | 
					  if (disableGPT4) {
 | 
				
			||||||
@@ -51,15 +60,25 @@ export const getServerSideConfig = () => {
 | 
				
			|||||||
      .join(",");
 | 
					      .join(",");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const isAzure = !!process.env.AZURE_URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
 | 
					    baseUrl: process.env.BASE_URL,
 | 
				
			||||||
    apiKey: process.env.OPENAI_API_KEY,
 | 
					    apiKey: process.env.OPENAI_API_KEY,
 | 
				
			||||||
 | 
					    openaiOrgId: process.env.OPENAI_ORG_ID,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isAzure,
 | 
				
			||||||
 | 
					    azureUrl: process.env.AZURE_URL,
 | 
				
			||||||
 | 
					    azureApiKey: process.env.AZURE_API_KEY,
 | 
				
			||||||
 | 
					    azureApiVersion: process.env.AZURE_API_VERSION,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    needCode: ACCESS_CODES.size > 0,
 | 
				
			||||||
    code: process.env.CODE,
 | 
					    code: process.env.CODE,
 | 
				
			||||||
    codes: ACCESS_CODES,
 | 
					    codes: ACCESS_CODES,
 | 
				
			||||||
    needCode: ACCESS_CODES.size > 0,
 | 
					
 | 
				
			||||||
    baseUrl: process.env.BASE_URL,
 | 
					 | 
				
			||||||
    proxyUrl: process.env.PROXY_URL,
 | 
					    proxyUrl: process.env.PROXY_URL,
 | 
				
			||||||
    openaiOrgId: process.env.OPENAI_ORG_ID,
 | 
					 | 
				
			||||||
    isVercel: !!process.env.VERCEL,
 | 
					    isVercel: !!process.env.VERCEL,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
 | 
					    hideUserApiKey: !!process.env.HIDE_USER_API_KEY,
 | 
				
			||||||
    disableGPT4,
 | 
					    disableGPT4,
 | 
				
			||||||
    hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
 | 
					    hideBalanceQuery: !process.env.ENABLE_BALANCE_QUERY,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,10 +23,12 @@ export enum Path {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export enum ApiPath {
 | 
					export enum ApiPath {
 | 
				
			||||||
  Cors = "/api/cors",
 | 
					  Cors = "/api/cors",
 | 
				
			||||||
 | 
					  OpenAI = "/api/openai",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum SlotID {
 | 
					export enum SlotID {
 | 
				
			||||||
  AppBody = "app-body",
 | 
					  AppBody = "app-body",
 | 
				
			||||||
 | 
					  CustomModel = "custom-model",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum FileName {
 | 
					export enum FileName {
 | 
				
			||||||
@@ -60,6 +62,11 @@ export const REQUEST_TIMEOUT_MS = 60000;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
 | 
					export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum ServiceProvider {
 | 
				
			||||||
 | 
					  OpenAI = "OpenAI",
 | 
				
			||||||
 | 
					  Azure = "Azure",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const OpenaiPath = {
 | 
					export const OpenaiPath = {
 | 
				
			||||||
  ChatPath: "v1/chat/completions",
 | 
					  ChatPath: "v1/chat/completions",
 | 
				
			||||||
  UsagePath: "dashboard/billing/usage",
 | 
					  UsagePath: "dashboard/billing/usage",
 | 
				
			||||||
@@ -67,6 +74,10 @@ export const OpenaiPath = {
 | 
				
			|||||||
  ListModelPath: "v1/models",
 | 
					  ListModelPath: "v1/models",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Azure = {
 | 
				
			||||||
 | 
					  ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
 | 
					export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
 | 
				
			||||||
export const DEFAULT_SYSTEM_TEMPLATE = `
 | 
					export const DEFAULT_SYSTEM_TEMPLATE = `
 | 
				
			||||||
You are ChatGPT, a large language model trained by OpenAI.
 | 
					You are ChatGPT, a large language model trained by OpenAI.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -258,11 +258,6 @@ const cn = {
 | 
				
			|||||||
      Title: "历史消息长度压缩阈值",
 | 
					      Title: "历史消息长度压缩阈值",
 | 
				
			||||||
      SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
 | 
					      SubTitle: "当未压缩的历史消息超过该值时,将进行压缩",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    Token: {
 | 
					 | 
				
			||||||
      Title: "API Key",
 | 
					 | 
				
			||||||
      SubTitle: "使用自己的 Key 可绕过密码访问限制",
 | 
					 | 
				
			||||||
      Placeholder: "OpenAI API Key",
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Usage: {
 | 
					    Usage: {
 | 
				
			||||||
      Title: "余额查询",
 | 
					      Title: "余额查询",
 | 
				
			||||||
@@ -273,19 +268,56 @@ const cn = {
 | 
				
			|||||||
      Check: "重新检查",
 | 
					      Check: "重新检查",
 | 
				
			||||||
      NoAccess: "输入 API Key 或访问密码查看余额",
 | 
					      NoAccess: "输入 API Key 或访问密码查看余额",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    AccessCode: {
 | 
					
 | 
				
			||||||
      Title: "访问密码",
 | 
					    Access: {
 | 
				
			||||||
      SubTitle: "管理员已开启加密访问",
 | 
					      AccessCode: {
 | 
				
			||||||
      Placeholder: "请输入访问密码",
 | 
					        Title: "访问密码",
 | 
				
			||||||
    },
 | 
					        SubTitle: "管理员已开启加密访问",
 | 
				
			||||||
    Endpoint: {
 | 
					        Placeholder: "请输入访问密码",
 | 
				
			||||||
      Title: "接口地址",
 | 
					      },
 | 
				
			||||||
      SubTitle: "除默认地址外,必须包含 http(s)://",
 | 
					      CustomEndpoint: {
 | 
				
			||||||
    },
 | 
					        Title: "自定义接口",
 | 
				
			||||||
    CustomModel: {
 | 
					        SubTitle: "是否使用自定义 Azure 或 OpenAI 服务",
 | 
				
			||||||
      Title: "自定义模型名",
 | 
					      },
 | 
				
			||||||
      SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
 | 
					      Provider: {
 | 
				
			||||||
 | 
					        Title: "模型服务商",
 | 
				
			||||||
 | 
					        SubTitle: "切换不同的服务商",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      OpenAI: {
 | 
				
			||||||
 | 
					        ApiKey: {
 | 
				
			||||||
 | 
					          Title: "API Key",
 | 
				
			||||||
 | 
					          SubTitle: "使用自定义 OpenAI Key 绕过密码访问限制",
 | 
				
			||||||
 | 
					          Placeholder: "OpenAI API Key",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Endpoint: {
 | 
				
			||||||
 | 
					          Title: "接口地址",
 | 
				
			||||||
 | 
					          SubTitle: "除默认地址外,必须包含 http(s)://",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      Azure: {
 | 
				
			||||||
 | 
					        ApiKey: {
 | 
				
			||||||
 | 
					          Title: "接口密钥",
 | 
				
			||||||
 | 
					          SubTitle: "使用自定义 Azure Key 绕过密码访问限制",
 | 
				
			||||||
 | 
					          Placeholder: "Azure API Key",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Endpoint: {
 | 
				
			||||||
 | 
					          Title: "接口地址",
 | 
				
			||||||
 | 
					          SubTitle: "样例:",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ApiVerion: {
 | 
				
			||||||
 | 
					          Title: "接口版本 (azure api version)",
 | 
				
			||||||
 | 
					          SubTitle: "选择指定的部分版本",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      CustomModel: {
 | 
				
			||||||
 | 
					        Title: "自定义模型名",
 | 
				
			||||||
 | 
					        SubTitle: "增加自定义模型可选项,使用英文逗号隔开",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Model: "模型 (model)",
 | 
					    Model: "模型 (model)",
 | 
				
			||||||
    Temperature: {
 | 
					    Temperature: {
 | 
				
			||||||
      Title: "随机性 (temperature)",
 | 
					      Title: "随机性 (temperature)",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -262,11 +262,7 @@ const en: LocaleType = {
 | 
				
			|||||||
      SubTitle:
 | 
					      SubTitle:
 | 
				
			||||||
        "Will compress if uncompressed messages length exceeds the value",
 | 
					        "Will compress if uncompressed messages length exceeds the value",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    Token: {
 | 
					
 | 
				
			||||||
      Title: "API Key",
 | 
					 | 
				
			||||||
      SubTitle: "Use your key to ignore access code limit",
 | 
					 | 
				
			||||||
      Placeholder: "OpenAI API Key",
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    Usage: {
 | 
					    Usage: {
 | 
				
			||||||
      Title: "Account Balance",
 | 
					      Title: "Account Balance",
 | 
				
			||||||
      SubTitle(used: any, total: any) {
 | 
					      SubTitle(used: any, total: any) {
 | 
				
			||||||
@@ -276,19 +272,55 @@ const en: LocaleType = {
 | 
				
			|||||||
      Check: "Check",
 | 
					      Check: "Check",
 | 
				
			||||||
      NoAccess: "Enter API Key to check balance",
 | 
					      NoAccess: "Enter API Key to check balance",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    AccessCode: {
 | 
					    Access: {
 | 
				
			||||||
      Title: "Access Code",
 | 
					      AccessCode: {
 | 
				
			||||||
      SubTitle: "Access control enabled",
 | 
					        Title: "Access Code",
 | 
				
			||||||
      Placeholder: "Need Access Code",
 | 
					        SubTitle: "Access control Enabled",
 | 
				
			||||||
    },
 | 
					        Placeholder: "Enter Code",
 | 
				
			||||||
    Endpoint: {
 | 
					      },
 | 
				
			||||||
      Title: "Endpoint",
 | 
					      CustomEndpoint: {
 | 
				
			||||||
      SubTitle: "Custom endpoint must start with http(s)://",
 | 
					        Title: "Custom Endpoint",
 | 
				
			||||||
    },
 | 
					        SubTitle: "Use custom Azure or OpenAI service",
 | 
				
			||||||
    CustomModel: {
 | 
					      },
 | 
				
			||||||
      Title: "Custom Models",
 | 
					      Provider: {
 | 
				
			||||||
      SubTitle: "Add extra model options, separate by comma",
 | 
					        Title: "Model Provider",
 | 
				
			||||||
 | 
					        SubTitle: "Select Azure or OpenAI",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      OpenAI: {
 | 
				
			||||||
 | 
					        ApiKey: {
 | 
				
			||||||
 | 
					          Title: "OpenAI API Key",
 | 
				
			||||||
 | 
					          SubTitle: "User custom OpenAI Api Key",
 | 
				
			||||||
 | 
					          Placeholder: "sk-xxx",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Endpoint: {
 | 
				
			||||||
 | 
					          Title: "OpenAI Endpoint",
 | 
				
			||||||
 | 
					          SubTitle: "Must starts with http(s):// or use /api/openai as default",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      Azure: {
 | 
				
			||||||
 | 
					        ApiKey: {
 | 
				
			||||||
 | 
					          Title: "Azure Api Key",
 | 
				
			||||||
 | 
					          SubTitle: "Check your api key from Azure console",
 | 
				
			||||||
 | 
					          Placeholder: "Azure Api Key",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Endpoint: {
 | 
				
			||||||
 | 
					          Title: "Azure Endpoint",
 | 
				
			||||||
 | 
					          SubTitle: "Example: ",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ApiVerion: {
 | 
				
			||||||
 | 
					          Title: "Azure Api Version",
 | 
				
			||||||
 | 
					          SubTitle: "Check your api version from azure console",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      CustomModel: {
 | 
				
			||||||
 | 
					        Title: "Custom Models",
 | 
				
			||||||
 | 
					        SubTitle: "Custom model options, seperated by comma",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Model: "Model",
 | 
					    Model: "Model",
 | 
				
			||||||
    Temperature: {
 | 
					    Temperature: {
 | 
				
			||||||
      Title: "Temperature",
 | 
					      Title: "Temperature",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,41 @@
 | 
				
			|||||||
import { DEFAULT_API_HOST, DEFAULT_MODELS, StoreKey } from "../constant";
 | 
					import {
 | 
				
			||||||
 | 
					  ApiPath,
 | 
				
			||||||
 | 
					  DEFAULT_API_HOST,
 | 
				
			||||||
 | 
					  ServiceProvider,
 | 
				
			||||||
 | 
					  StoreKey,
 | 
				
			||||||
 | 
					} from "../constant";
 | 
				
			||||||
import { getHeaders } from "../client/api";
 | 
					import { getHeaders } from "../client/api";
 | 
				
			||||||
import { getClientConfig } from "../config/client";
 | 
					import { getClientConfig } from "../config/client";
 | 
				
			||||||
import { createPersistStore } from "../utils/store";
 | 
					import { createPersistStore } from "../utils/store";
 | 
				
			||||||
 | 
					import { ensure } from "../utils/clone";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
 | 
					let fetchState = 0; // 0 not fetch, 1 fetching, 2 done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_OPENAI_URL =
 | 
					const DEFAULT_OPENAI_URL =
 | 
				
			||||||
  getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : "/api/openai/";
 | 
					  getClientConfig()?.buildMode === "export" ? DEFAULT_API_HOST : ApiPath.OpenAI;
 | 
				
			||||||
console.log("[API] default openai url", DEFAULT_OPENAI_URL);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DEFAULT_ACCESS_STATE = {
 | 
					const DEFAULT_ACCESS_STATE = {
 | 
				
			||||||
  token: "",
 | 
					 | 
				
			||||||
  accessCode: "",
 | 
					  accessCode: "",
 | 
				
			||||||
 | 
					  useCustomConfig: false,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  provider: ServiceProvider.OpenAI,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // openai
 | 
				
			||||||
 | 
					  openaiUrl: DEFAULT_OPENAI_URL,
 | 
				
			||||||
 | 
					  openaiApiKey: "",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // azure
 | 
				
			||||||
 | 
					  azureUrl: "",
 | 
				
			||||||
 | 
					  azureApiKey: "",
 | 
				
			||||||
 | 
					  azureApiVersion: "2023-08-01-preview",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // server config
 | 
				
			||||||
  needCode: true,
 | 
					  needCode: true,
 | 
				
			||||||
  hideUserApiKey: false,
 | 
					  hideUserApiKey: false,
 | 
				
			||||||
  hideBalanceQuery: false,
 | 
					  hideBalanceQuery: false,
 | 
				
			||||||
  disableGPT4: false,
 | 
					  disableGPT4: false,
 | 
				
			||||||
  disableFastLink: false,
 | 
					  disableFastLink: false,
 | 
				
			||||||
  customModels: "",
 | 
					  customModels: "",
 | 
				
			||||||
 | 
					 | 
				
			||||||
  openaiUrl: DEFAULT_OPENAI_URL,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useAccessStore = createPersistStore(
 | 
					export const useAccessStore = createPersistStore(
 | 
				
			||||||
@@ -31,12 +47,24 @@ export const useAccessStore = createPersistStore(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      return get().needCode;
 | 
					      return get().needCode;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isValidOpenAI() {
 | 
				
			||||||
 | 
					      return ensure(get(), ["openaiUrl", "openaiApiKey"]);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isValidAzure() {
 | 
				
			||||||
 | 
					      return ensure(get(), ["azureUrl", "azureApiKey", "azureApiVersion"]);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isAuthorized() {
 | 
					    isAuthorized() {
 | 
				
			||||||
      this.fetch();
 | 
					      this.fetch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // has token or has code or disabled access control
 | 
					      // has token or has code or disabled access control
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        !!get().token || !!get().accessCode || !this.enabledAccessControl()
 | 
					        this.isValidOpenAI() ||
 | 
				
			||||||
 | 
					        this.isValidAzure() ||
 | 
				
			||||||
 | 
					        !this.enabledAccessControl() ||
 | 
				
			||||||
 | 
					        (this.enabledAccessControl() && ensure(get(), ["accessCode"]))
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    fetch() {
 | 
					    fetch() {
 | 
				
			||||||
@@ -64,6 +92,19 @@ export const useAccessStore = createPersistStore(
 | 
				
			|||||||
  }),
 | 
					  }),
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: StoreKey.Access,
 | 
					    name: StoreKey.Access,
 | 
				
			||||||
    version: 1,
 | 
					    version: 2,
 | 
				
			||||||
 | 
					    migrate(persistedState, version) {
 | 
				
			||||||
 | 
					      if (version < 2) {
 | 
				
			||||||
 | 
					        const state = persistedState as {
 | 
				
			||||||
 | 
					          token: string;
 | 
				
			||||||
 | 
					          openaiApiKey: string;
 | 
				
			||||||
 | 
					          azureApiVersion: string;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        state.openaiApiKey = state.token;
 | 
				
			||||||
 | 
					        state.azureApiVersion = "2023-08-01-preview";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return persistedState as any;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,10 @@
 | 
				
			|||||||
export function deepClone<T>(obj: T) {
 | 
					export function deepClone<T>(obj: T) {
 | 
				
			||||||
  return JSON.parse(JSON.stringify(obj));
 | 
					  return JSON.parse(JSON.stringify(obj));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ensure<T extends object>(
 | 
				
			||||||
 | 
					  obj: T,
 | 
				
			||||||
 | 
					  keys: Array<[keyof T][number]>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  return keys.every((k) => obj[k] !== undefined && obj[k] !== null);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { create } from "zustand";
 | 
					import { create } from "zustand";
 | 
				
			||||||
import { persist } from "zustand/middleware";
 | 
					import { combine, persist } from "zustand/middleware";
 | 
				
			||||||
import { Updater } from "../typing";
 | 
					import { Updater } from "../typing";
 | 
				
			||||||
import { deepClone } from "./clone";
 | 
					import { deepClone } from "./clone";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,33 +23,42 @@ type SetStoreState<T> = (
 | 
				
			|||||||
  replace?: boolean | undefined,
 | 
					  replace?: boolean | undefined,
 | 
				
			||||||
) => void;
 | 
					) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function createPersistStore<T, M>(
 | 
					export function createPersistStore<T extends object, M>(
 | 
				
			||||||
  defaultState: T,
 | 
					  state: T,
 | 
				
			||||||
  methods: (
 | 
					  methods: (
 | 
				
			||||||
    set: SetStoreState<T & MakeUpdater<T>>,
 | 
					    set: SetStoreState<T & MakeUpdater<T>>,
 | 
				
			||||||
    get: () => T & MakeUpdater<T>,
 | 
					    get: () => T & MakeUpdater<T>,
 | 
				
			||||||
  ) => M,
 | 
					  ) => M,
 | 
				
			||||||
  persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
 | 
					  persistOptions: SecondParam<typeof persist<T & M & MakeUpdater<T>>>,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  return create<T & M & MakeUpdater<T>>()(
 | 
					  return create(
 | 
				
			||||||
    persist((set, get) => {
 | 
					    persist(
 | 
				
			||||||
      return {
 | 
					      combine(
 | 
				
			||||||
        ...defaultState,
 | 
					        {
 | 
				
			||||||
        ...methods(set as any, get),
 | 
					          ...state,
 | 
				
			||||||
 | 
					          lastUpdateTime: 0,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        (set, get) => {
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            ...methods(set, get as any),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lastUpdateTime: 0,
 | 
					            markUpdate() {
 | 
				
			||||||
        markUpdate() {
 | 
					              set({ lastUpdateTime: Date.now() } as Partial<
 | 
				
			||||||
          set({ lastUpdateTime: Date.now() } as Partial<
 | 
					                T & M & MakeUpdater<T>
 | 
				
			||||||
            T & M & MakeUpdater<T>
 | 
					              >);
 | 
				
			||||||
          >);
 | 
					            },
 | 
				
			||||||
 | 
					            update(updater) {
 | 
				
			||||||
 | 
					              const state = deepClone(get());
 | 
				
			||||||
 | 
					              updater(state);
 | 
				
			||||||
 | 
					              set({
 | 
				
			||||||
 | 
					                ...state,
 | 
				
			||||||
 | 
					                lastUpdateTime: Date.now(),
 | 
				
			||||||
 | 
					              });
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          } as M & MakeUpdater<T>;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        update(updater) {
 | 
					      ),
 | 
				
			||||||
          const state = deepClone(get());
 | 
					      persistOptions as any,
 | 
				
			||||||
          updater(state);
 | 
					    ),
 | 
				
			||||||
          get().markUpdate();
 | 
					 | 
				
			||||||
          set(state);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }, persistOptions),
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user