From 35471a41c89cdfdd2b6a59c25b24d415049556fc Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Mon, 25 Dec 2023 00:30:23 +0700 Subject: [PATCH 01/13] Fix & Feat [Auth] Api Key Variable - [+] fix(auth.ts): fix variable name from serverApiKey to systemApiKey - [+] feat(auth.ts): add support for Google API key in addition to Azure API key --- app/api/auth.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/api/auth.ts b/app/api/auth.ts index b41e34e05..043ad0d31 100644 --- a/app/api/auth.ts +++ b/app/api/auth.ts @@ -55,15 +55,18 @@ export function auth(req: NextRequest) { // if user does not provide an api key, inject system api key if (!apiKey) { - const serverApiKey = serverConfig.isAzure + const serverConfig = getServerSideConfig(); + const systemApiKey = serverConfig.isAzure ? serverConfig.azureApiKey + : serverConfig.isGoogle + ? serverConfig.googleApiKey : serverConfig.apiKey; - if (serverApiKey) { + if (systemApiKey) { console.log("[Auth] use system api key"); req.headers.set( "Authorization", - `${serverConfig.isAzure ? "" : "Bearer "}${serverApiKey}`, + `Bearer ${systemApiKey}`, ); } else { console.log("[Auth] admin did not provide an api key"); From 281fe6927a297bf561032dcc5e80f96a90441a84 Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Mon, 25 Dec 2023 00:35:33 +0700 Subject: [PATCH 02/13] Feat [Server Side] Google Api Configuration - [+] feat(server.ts): add support for Google API configuration variables --- app/config/server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/config/server.ts b/app/config/server.ts index 83c711242..bd2cee656 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -65,6 +65,7 @@ export const getServerSideConfig = () => { } const isAzure = !!process.env.AZURE_URL; + const isGoogle = !!process.env.GOOGLE_URL; const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? ""; const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim()); @@ -84,6 +85,7 @@ export const getServerSideConfig = () => { azureApiKey: process.env.AZURE_API_KEY, azureApiVersion: process.env.AZURE_API_VERSION, + isGoogle, googleApiKey: process.env.GOOGLE_API_KEY, googleUrl: process.env.GOOGLE_URL, From 8ca525dc7adce7860bc79ac8dfe54ab0e896a4ab Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Mon, 25 Dec 2023 01:09:21 +0700 Subject: [PATCH 03/13] Fix [TypesScript] [LLM Api] Chaining Model - [+] fix(api.ts): make provider property optional in LLMModel interface --- app/client/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/api.ts b/app/client/api.ts index 30a220ea4..28ff7b162 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -46,7 +46,7 @@ export interface LLMUsage { export interface LLMModel { name: string; available: boolean; - provider: LLMModelProvider; + provider?: LLMModelProvider; } export interface LLMModelProvider { From e9def2cdc5bd8be91c06572de879bdeb4ddd28fb Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Mon, 25 Dec 2023 01:10:28 +0700 Subject: [PATCH 04/13] Refactor [Model] [LLM Api] Chaining Model - [+] refactor(model.ts): change forEach loop to arrow function for readability and consistency - [+] fix(model.ts): mark 'provider' property as optional in modelTable type - [+] fix(model.ts): use optional chaining when assigning provider property in modelTable --- app/utils/model.ts | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/app/utils/model.ts b/app/utils/model.ts index c4a4833ed..b2a42ef02 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -10,24 +10,23 @@ export function collectModelTable( available: boolean; name: string; displayName: string; - provider: LLMModel["provider"]; + provider?: LLMModel["provider"]; // Marked as optional } > = {}; // default models - models.forEach( - (m) => - (modelTable[m.name] = { - ...m, - displayName: m.name, - }), - ); + models.forEach((m) => { + modelTable[m.name] = { + ...m, + displayName: m.name, // 'provider' is copied over if it exists + }; + }); // server custom models customModels .split(",") .filter((v) => !!v && v.length > 0) - .map((m) => { + .forEach((m) => { const available = !m.startsWith("-"); const nameConfig = m.startsWith("+") || m.startsWith("-") ? m.slice(1) : m; @@ -35,15 +34,15 @@ export function collectModelTable( // enable or disable all models if (name === "all") { - Object.values(modelTable).forEach((m) => (m.available = available)); + Object.values(modelTable).forEach((model) => (model.available = available)); + } else { + modelTable[name] = { + name, + displayName: displayName || name, + available, + provider: modelTable[name]?.provider, // Use optional chaining + }; } - - modelTable[name] = { - name, - displayName: displayName || name, - available, - provider: modelTable[name].provider, - }; }); return modelTable; } From 0c116251b1c51d16e3e9b3d025c4feed8d7c069e Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Mon, 25 Dec 2023 01:45:24 +0700 Subject: [PATCH 05/13] Revert "Fix [TypesScript] [LLM Api] Chaining Model" This reverts commit 8ca525dc7adce7860bc79ac8dfe54ab0e896a4ab. Reason: It's suddenly stopped lmao --- app/client/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/api.ts b/app/client/api.ts index 28ff7b162..30a220ea4 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -46,7 +46,7 @@ export interface LLMUsage { export interface LLMModel { name: string; available: boolean; - provider?: LLMModelProvider; + provider: LLMModelProvider; } export interface LLMModelProvider { From 753c518d33240ee3abd75152f3b11b6dc4e463b9 Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 03:46:35 +0800 Subject: [PATCH 06/13] chore: update how to identify google model --- app/config/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/server.ts b/app/config/server.ts index bd2cee656..c6251a5c2 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -65,7 +65,7 @@ export const getServerSideConfig = () => { } const isAzure = !!process.env.AZURE_URL; - const isGoogle = !!process.env.GOOGLE_URL; + const isGoogle = !!process.env.GOOGLE_API_KEY; const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? ""; const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim()); From 5af68ac545902e80465235051c39f068baaf9160 Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 04:07:35 +0800 Subject: [PATCH 07/13] fix: fix issue https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/3616 --- app/api/auth.ts | 13 +++++-------- app/api/google/[...path]/route.ts | 13 +++++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/api/auth.ts b/app/api/auth.ts index 043ad0d31..ed2b67271 100644 --- a/app/api/auth.ts +++ b/app/api/auth.ts @@ -16,11 +16,11 @@ function getIP(req: NextRequest) { function parseApiKey(bearToken: string) { const token = bearToken.trim().replaceAll("Bearer ", "").trim(); - const isOpenAiKey = !token.startsWith(ACCESS_CODE_PREFIX); + const isApiKey = !token.startsWith(ACCESS_CODE_PREFIX); return { - accessCode: isOpenAiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length), - apiKey: isOpenAiKey ? token : "", + accessCode: isApiKey ? "" : token.slice(ACCESS_CODE_PREFIX.length), + apiKey: isApiKey ? token : "", }; } @@ -49,7 +49,7 @@ export function auth(req: NextRequest) { if (serverConfig.hideUserApiKey && !!apiKey) { return { error: true, - msg: "you are not allowed to access openai with your own api key", + msg: "you are not allowed to access with your own api key", }; } @@ -64,10 +64,7 @@ export function auth(req: NextRequest) { if (systemApiKey) { console.log("[Auth] use system api key"); - req.headers.set( - "Authorization", - `Bearer ${systemApiKey}`, - ); + req.headers.set("Authorization", `Bearer ${systemApiKey}`); } else { console.log("[Auth] admin did not provide an api key"); } diff --git a/app/api/google/[...path]/route.ts b/app/api/google/[...path]/route.ts index 217556784..28b9822a5 100644 --- a/app/api/google/[...path]/route.ts +++ b/app/api/google/[...path]/route.ts @@ -39,9 +39,22 @@ async function handle( 10 * 60 * 1000, ); + const authResult = auth(req); + if (authResult.error) { + return NextResponse.json(authResult, { + status: 401, + }); + } + const bearToken = req.headers.get("Authorization") ?? ""; const token = bearToken.trim().replaceAll("Bearer ", "").trim(); + console.log( + bearToken, + serverConfig.googleApiKey, + token ? token : serverConfig.googleApiKey, + ); + const key = token ? token : serverConfig.googleApiKey; if (!key) { return NextResponse.json( From cad461b1218e1d3668d4d7005032d46d2986f5d3 Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 04:09:38 +0800 Subject: [PATCH 08/13] chore: remove console log --- app/api/google/[...path]/route.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/api/google/[...path]/route.ts b/app/api/google/[...path]/route.ts index 28b9822a5..7911f8483 100644 --- a/app/api/google/[...path]/route.ts +++ b/app/api/google/[...path]/route.ts @@ -49,12 +49,6 @@ async function handle( const bearToken = req.headers.get("Authorization") ?? ""; const token = bearToken.trim().replaceAll("Bearer ", "").trim(); - console.log( - bearToken, - serverConfig.googleApiKey, - token ? token : serverConfig.googleApiKey, - ); - const key = token ? token : serverConfig.googleApiKey; if (!key) { return NextResponse.json( From 199f29e63cd80cd97c530ed5e47a22f6106a1372 Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 04:33:47 +0800 Subject: [PATCH 09/13] chore: auto concat messages --- app/client/platforms/google.ts | 24 +++++++++++++++--------- app/store/chat.ts | 32 +++++++++++++++----------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts index ec7d79564..c35e93cb3 100644 --- a/app/client/platforms/google.ts +++ b/app/client/platforms/google.ts @@ -21,10 +21,24 @@ export class GeminiProApi implements LLMApi { } async chat(options: ChatOptions): Promise { const messages = options.messages.map((v) => ({ - role: v.role.replace("assistant", "model").replace("system", "model"), + role: v.role.replace("assistant", "model").replace("system", "user"), parts: [{ text: v.content }], })); + // google requires that role in neighboring messages must not be the same + for (let i = 0; i < messages.length - 1; ) { + // Check if current and next item both have the role "model" + if (messages[i].role === messages[i + 1].role) { + // Concatenate the 'parts' of the current and next item + messages[i].parts = messages[i].parts.concat(messages[i + 1].parts); + // Remove the next item + messages.splice(i + 1, 1); + } else { + // Move to the next item + i++; + } + } + const modelConfig = { ...useAppConfig.getState().modelConfig, ...useChatStore.getState().currentSession().mask.modelConfig, @@ -43,14 +57,6 @@ export class GeminiProApi implements LLMApi { topP: modelConfig.top_p, // "topK": modelConfig.top_k, }, - // stream: options.config.stream, - // model: modelConfig.model, - // temperature: modelConfig.temperature, - // presence_penalty: modelConfig.presence_penalty, - // frequency_penalty: modelConfig.frequency_penalty, - // top_p: modelConfig.top_p, - // max_tokens: Math.max(modelConfig.max_tokens, 1024), - // Please do not ask me why not send max_tokens, no reason, this param is just shit, I dont want to explain anymore. }; console.log("[Request] google payload: ", requestPayload); diff --git a/app/store/chat.ts b/app/store/chat.ts index 1dcf4e646..4af5a52ac 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -389,24 +389,22 @@ export const useChatStore = createPersistStore( const shouldInjectSystemPrompts = modelConfig.enableInjectSystemPrompts; var systemPrompts: ChatMessage[] = []; - if (modelConfig.model !== "gemini-pro") { - systemPrompts = shouldInjectSystemPrompts - ? [ - createMessage({ - role: "system", - content: fillTemplateWith("", { - ...modelConfig, - template: DEFAULT_SYSTEM_TEMPLATE, - }), + systemPrompts = shouldInjectSystemPrompts + ? [ + createMessage({ + role: "system", + content: fillTemplateWith("", { + ...modelConfig, + template: DEFAULT_SYSTEM_TEMPLATE, }), - ] - : []; - if (shouldInjectSystemPrompts) { - console.log( - "[Global System Prompt] ", - systemPrompts.at(0)?.content ?? "empty", - ); - } + }), + ] + : []; + if (shouldInjectSystemPrompts) { + console.log( + "[Global System Prompt] ", + systemPrompts.at(0)?.content ?? "empty", + ); } // long term memory From 5c638251f866e51d629c5e25cbe1ee11433c08f6 Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 05:12:21 +0800 Subject: [PATCH 10/13] fix: fix using different model --- app/api/auth.ts | 21 ++++++++++++++------- app/api/google/[...path]/route.ts | 6 +++--- app/api/openai/[...path]/route.ts | 24 +++++++++++++++++++++--- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/app/api/auth.ts b/app/api/auth.ts index ed2b67271..874401a32 100644 --- a/app/api/auth.ts +++ b/app/api/auth.ts @@ -1,7 +1,7 @@ import { NextRequest } from "next/server"; import { getServerSideConfig } from "../config/server"; import md5 from "spark-md5"; -import { ACCESS_CODE_PREFIX } from "../constant"; +import { ACCESS_CODE_PREFIX, ModelProvider } from "../constant"; function getIP(req: NextRequest) { let ip = req.ip ?? req.headers.get("x-real-ip"); @@ -24,7 +24,7 @@ function parseApiKey(bearToken: string) { }; } -export function auth(req: NextRequest) { +export function auth(req: NextRequest, modelProvider: ModelProvider) { const authToken = req.headers.get("Authorization") ?? ""; // check if it is openai api key or user token @@ -56,12 +56,19 @@ export function auth(req: NextRequest) { // if user does not provide an api key, inject system api key if (!apiKey) { const serverConfig = getServerSideConfig(); - const systemApiKey = serverConfig.isAzure - ? serverConfig.azureApiKey - : serverConfig.isGoogle - ? serverConfig.googleApiKey - : serverConfig.apiKey; + // const systemApiKey = serverConfig.isAzure + // ? serverConfig.azureApiKey + // : serverConfig.isGoogle + // ? serverConfig.googleApiKey + // : serverConfig.apiKey; + + const systemApiKey = + modelProvider === ModelProvider.GeminiPro + ? serverConfig.googleApiKey + : serverConfig.isAzure + ? serverConfig.azureApiKey + : serverConfig.apiKey; if (systemApiKey) { console.log("[Auth] use system api key"); req.headers.set("Authorization", `Bearer ${systemApiKey}`); diff --git a/app/api/google/[...path]/route.ts b/app/api/google/[...path]/route.ts index 7911f8483..869bd5076 100644 --- a/app/api/google/[...path]/route.ts +++ b/app/api/google/[...path]/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { auth } from "../../auth"; import { getServerSideConfig } from "@/app/config/server"; -import { GEMINI_BASE_URL, Google } from "@/app/constant"; +import { GEMINI_BASE_URL, Google, ModelProvider } from "@/app/constant"; async function handle( req: NextRequest, @@ -39,7 +39,7 @@ async function handle( 10 * 60 * 1000, ); - const authResult = auth(req); + const authResult = auth(req, ModelProvider.GeminiPro); if (authResult.error) { return NextResponse.json(authResult, { status: 401, @@ -50,6 +50,7 @@ async function handle( const token = bearToken.trim().replaceAll("Bearer ", "").trim(); const key = token ? token : serverConfig.googleApiKey; + if (!key) { return NextResponse.json( { @@ -63,7 +64,6 @@ async function handle( } const fetchUrl = `${baseUrl}/${path}?key=${key}`; - const fetchOptions: RequestInit = { headers: { "Content-Type": "application/json", diff --git a/app/api/openai/[...path]/route.ts b/app/api/openai/[...path]/route.ts index 2addd53a5..77059c151 100644 --- a/app/api/openai/[...path]/route.ts +++ b/app/api/openai/[...path]/route.ts @@ -1,6 +1,6 @@ import { type OpenAIListModelResponse } from "@/app/client/platforms/openai"; import { getServerSideConfig } from "@/app/config/server"; -import { OpenaiPath } from "@/app/constant"; +import { ModelProvider, OpenaiPath } from "@/app/constant"; import { prettyObject } from "@/app/utils/format"; import { NextRequest, NextResponse } from "next/server"; import { auth } from "../../auth"; @@ -45,7 +45,7 @@ async function handle( ); } - const authResult = auth(req); + const authResult = auth(req, ModelProvider.GPT); if (authResult.error) { return NextResponse.json(authResult, { status: 401, @@ -75,4 +75,22 @@ export const GET = handle; export const POST = handle; export const runtime = "edge"; -export const preferredRegion = ['arn1', 'bom1', 'cdg1', 'cle1', 'cpt1', 'dub1', 'fra1', 'gru1', 'hnd1', 'iad1', 'icn1', 'kix1', 'lhr1', 'pdx1', 'sfo1', 'sin1', 'syd1']; +export const preferredRegion = [ + "arn1", + "bom1", + "cdg1", + "cle1", + "cpt1", + "dub1", + "fra1", + "gru1", + "hnd1", + "iad1", + "icn1", + "kix1", + "lhr1", + "pdx1", + "sfo1", + "sin1", + "syd1", +]; From f5ed1604aa0b3b60a8fcac1cecb03f75a0a65cdb Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 05:24:01 +0800 Subject: [PATCH 11/13] fix: fix removing bearer header --- app/api/auth.ts | 6 ------ app/api/common.ts | 4 +++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/api/auth.ts b/app/api/auth.ts index 874401a32..16c8034eb 100644 --- a/app/api/auth.ts +++ b/app/api/auth.ts @@ -57,12 +57,6 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) { if (!apiKey) { const serverConfig = getServerSideConfig(); - // const systemApiKey = serverConfig.isAzure - // ? serverConfig.azureApiKey - // : serverConfig.isGoogle - // ? serverConfig.googleApiKey - // : serverConfig.apiKey; - const systemApiKey = modelProvider === ModelProvider.GeminiPro ? serverConfig.googleApiKey diff --git a/app/api/common.ts b/app/api/common.ts index 13cfab03c..8e029c35b 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -9,7 +9,9 @@ const serverConfig = getServerSideConfig(); export async function requestOpenai(req: NextRequest) { const controller = new AbortController(); - const authValue = req.headers.get("Authorization") ?? ""; + const authValue = + req.headers.get("Authorization")?.trim().replaceAll("Bearer ", "").trim() ?? + ""; const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization"; let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll( From 19137b79bcf17d1b1be01740dd5ed0238c784680 Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 09:56:51 +0800 Subject: [PATCH 12/13] fix: return bearer header when using openai --- app/api/common.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/api/common.ts b/app/api/common.ts index 8e029c35b..a6f4c5721 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -9,9 +9,16 @@ const serverConfig = getServerSideConfig(); export async function requestOpenai(req: NextRequest) { const controller = new AbortController(); - const authValue = - req.headers.get("Authorization")?.trim().replaceAll("Bearer ", "").trim() ?? - ""; + if (serverConfig.isAzure) { + const authValue = + req.headers + .get("Authorization") + ?.trim() + .replaceAll("Bearer ", "") + .trim() ?? ""; + } else { + const authValue = req.headers.get("Authorization") ?? ""; + } const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization"; let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll( From 422d70d928fbd30f69789cd4f05bac4204dc6c8c Mon Sep 17 00:00:00 2001 From: Fred Liang Date: Mon, 25 Dec 2023 10:25:43 +0800 Subject: [PATCH 13/13] chore: update auth value logic (#3630) --- app/api/common.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/api/common.ts b/app/api/common.ts index a6f4c5721..2d89ea1e5 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -9,17 +9,21 @@ const serverConfig = getServerSideConfig(); export async function requestOpenai(req: NextRequest) { const controller = new AbortController(); + var authValue, + authHeaderName = ""; if (serverConfig.isAzure) { - const authValue = + authValue = req.headers .get("Authorization") ?.trim() .replaceAll("Bearer ", "") .trim() ?? ""; + + authHeaderName = "api-key"; } else { - const authValue = req.headers.get("Authorization") ?? ""; + authValue = req.headers.get("Authorization") ?? ""; + authHeaderName = "Authorization"; } - const authHeaderName = serverConfig.isAzure ? "api-key" : "Authorization"; let path = `${req.nextUrl.pathname}${req.nextUrl.search}`.replaceAll( "/api/openai/",