@@ -1560,6 +1564,22 @@ function _Chat() {
event.preventDefault();
setShowShortcutKeyModal(true);
}
+ // 清除上下文 command + shift + delete
+ else if (
+ (event.metaKey || event.ctrlKey) &&
+ event.shiftKey &&
+ event.key.toLowerCase() === "delete"
+ ) {
+ event.preventDefault();
+ chatStore.updateCurrentSession((session) => {
+ if (session.clearContextIndex === session.messages.length) {
+ session.clearContextIndex = undefined;
+ } else {
+ session.clearContextIndex = session.messages.length;
+ session.memoryPrompt = ""; // will clear memory
+ }
+ });
+ }
};
window.addEventListener("keydown", handleKeyDown);
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 0017e8e42..0acf8c545 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -95,6 +95,7 @@ const cn = {
copyLastMessage: "复制最后一个回复",
copyLastCode: "复制最后一个代码块",
showShortcutKey: "显示快捷方式",
+ clearContext: "清除上下文",
},
},
Export: {
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 63e244b9a..559b93abd 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -97,6 +97,7 @@ const en: LocaleType = {
copyLastMessage: "Copy Last Reply",
copyLastCode: "Copy Last Code Block",
showShortcutKey: "Show Shortcuts",
+ clearContext: "Clear Context",
},
},
Export: {
diff --git a/app/locales/tw.ts b/app/locales/tw.ts
index b0602a081..b84d3bf1f 100644
--- a/app/locales/tw.ts
+++ b/app/locales/tw.ts
@@ -90,6 +90,7 @@ const tw = {
copyLastMessage: "複製最後一個回覆",
copyLastCode: "複製最後一個代碼塊",
showShortcutKey: "顯示快捷方式",
+ clearContext: "清除上下文",
},
},
Export: {
From 9e18cc260b6afd3e9e1a47625711ec7b296b5d05 Mon Sep 17 00:00:00 2001
From: endless-learner <35006844+endless-learner@users.noreply.github.com>
Date: Tue, 24 Sep 2024 13:55:00 -0700
Subject: [PATCH 003/236] Update README.md
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9ea61bb08..66e3a7ef2 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
-[

](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [

](https://zeabur.com/templates/ZBUEFA) [

](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [

](https://computenest.console.aliyun.com/service/detail/cn-hangzhou/service-f1c9b75e59814dc49d52?type=user&isRecommend=true)
+[

](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [

](https://zeabur.com/templates/ZBUEFA) [

](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [

](https://computenest.console.aliyun.com/service/detail/cn-hangzhou/service-f1c9b75e59814dc49d52?type=user&isRecommend=true)
[

](https://monica.im/?utm=nxcrp)
From 064e964d75b898e7867497da30832a833638f4e1 Mon Sep 17 00:00:00 2001
From: endless-learner <35006844+endless-learner@users.noreply.github.com>
Date: Tue, 24 Sep 2024 23:05:32 -0700
Subject: [PATCH 004/236] Updated link to deploy on Alibaba Cloud, readable
when not logged in, also, able to choose region.
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6fd75220c..a2c9ab817 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
-[

](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [

](https://zeabur.com/templates/ZBUEFA) [

](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [

](https://computenest.console.aliyun.com/service/detail/cn-hangzhou/service-f1c9b75e59814dc49d52?type=user&isRecommend=true)
+[

](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [

](https://zeabur.com/templates/ZBUEFA) [

](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [

](https://computenest.aliyun.com/market/service-f1c9b75e59814dc49d52)
[

](https://monica.im/?utm=nxcrp)
From 7d55a6d0e441bddaf9870c9adfa88f1f72c600a5 Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Tue, 8 Oct 2024 21:31:01 +0800
Subject: [PATCH 005/236] feat: allow send image only
---
app/components/chat.tsx | 7 +++++--
app/store/chat.ts | 22 ++++++++--------------
2 files changed, 13 insertions(+), 16 deletions(-)
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index 3d519dee7..77b48abee 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -115,11 +115,14 @@ import { getClientConfig } from "../config/client";
import { useAllModels } from "../utils/hooks";
import { MultimodalContent } from "../client/api";
-const localStorage = safeLocalStorage();
import { ClientApi } from "../client/api";
import { createTTSPlayer } from "../utils/audio";
import { MsEdgeTTS, OUTPUT_FORMAT } from "../utils/ms_edge_tts";
+import { isEmpty } from "lodash-es";
+
+const localStorage = safeLocalStorage();
+
const ttsPlayer = createTTSPlayer();
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
@@ -1015,7 +1018,7 @@ function _Chat() {
};
const doSubmit = (userInput: string) => {
- if (userInput.trim() === "") return;
+ if (userInput.trim() === "" && isEmpty(attachImages)) return;
const matchCommand = chatCommands.match(userInput);
if (matchCommand.matched) {
setUserInput("");
diff --git a/app/store/chat.ts b/app/store/chat.ts
index 968d8cb64..fc1fb23ba 100644
--- a/app/store/chat.ts
+++ b/app/store/chat.ts
@@ -337,22 +337,16 @@ export const useChatStore = createPersistStore(
if (attachImages && attachImages.length > 0) {
mContent = [
- {
- type: "text",
- text: userContent,
- },
+ ...(userContent
+ ? [{ type: "text" as const, text: userContent }]
+ : []),
+ ...attachImages.map((url) => ({
+ type: "image_url" as const,
+ image_url: { url },
+ })),
];
- mContent = mContent.concat(
- attachImages.map((url) => {
- return {
- type: "image_url",
- image_url: {
- url: url,
- },
- };
- }),
- );
}
+
let userMessage: ChatMessage = createMessage({
role: "user",
content: mContent,
From 2eebfcf6fe873595a69ac0de805811fa7ed94f12 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 11 Oct 2024 11:29:22 +0800
Subject: [PATCH 006/236] client app tauri updater #2966
---
app/components/settings.tsx | 16 ++++++++++++----
app/global.d.ts | 7 +++++++
app/locales/cn.ts | 2 ++
app/locales/en.ts | 2 ++
app/store/update.ts | 2 ++
app/utils.ts | 23 +++++++++++++++++++++++
6 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 9f338718e..9ee919b8d 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -49,7 +49,7 @@ import Locale, {
changeLang,
getLang,
} from "../locales";
-import { copyToClipboard } from "../utils";
+import { copyToClipboard, clientUpdate } from "../utils";
import Link from "next/link";
import {
Anthropic,
@@ -1357,9 +1357,17 @@ export function Settings() {
{checkingUpdate ? (
) : hasNewVersion ? (
-
- {Locale.Settings.Update.GoToUpdate}
-
+ clientConfig?.isApp ? (
+ }
+ text={Locale.Settings.Update.GoToUpdate}
+ onClick={() => clientUpdate()}
+ />
+ ) : (
+
+ {Locale.Settings.Update.GoToUpdate}
+
+ )
) : (
}
diff --git a/app/global.d.ts b/app/global.d.ts
index 8ee636bcd..897871fec 100644
--- a/app/global.d.ts
+++ b/app/global.d.ts
@@ -26,6 +26,13 @@ declare interface Window {
isPermissionGranted(): Promise;
sendNotification(options: string | Options): void;
};
+ updater: {
+ checkUpdate(): Promise;
+ installUpdate(): Promise;
+ onUpdaterEvent(
+ handler: (status: UpdateStatusResult) => void,
+ ): Promise;
+ };
http: {
fetch(
url: string,
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index e5bcca0ed..3086cc63e 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -205,6 +205,8 @@ const cn = {
IsChecking: "正在检查更新...",
FoundUpdate: (x: string) => `发现新版本:${x}`,
GoToUpdate: "前往更新",
+ Success: "Update Succesfull.",
+ Failed: "Update Failed.",
},
SendKey: "发送键",
Theme: "主题",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 120457522..a025a522f 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -207,6 +207,8 @@ const en: LocaleType = {
IsChecking: "Checking update...",
FoundUpdate: (x: string) => `Found new version: ${x}`,
GoToUpdate: "Update",
+ Success: "Update Succesfull.",
+ Failed: "Update Failed.",
},
SendKey: "Send Key",
Theme: "Theme",
diff --git a/app/store/update.ts b/app/store/update.ts
index e68fde369..327dc5e88 100644
--- a/app/store/update.ts
+++ b/app/store/update.ts
@@ -6,6 +6,7 @@ import {
} from "../constant";
import { getClientConfig } from "../config/client";
import { createPersistStore } from "../utils/store";
+import { clientUpdate } from "../utils";
import ChatGptIcon from "../icons/chatgpt.png";
import Locale from "../locales";
import { ClientApi } from "../client/api";
@@ -119,6 +120,7 @@ export const useUpdateStore = createPersistStore(
icon: `${ChatGptIcon.src}`,
sound: "Default",
});
+ clientUpdate();
}
}
});
diff --git a/app/utils.ts b/app/utils.ts
index 5d4501710..9e75ffc7f 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -386,3 +386,26 @@ export function getOperationId(operation: {
`${operation.method.toUpperCase()}${operation.path.replaceAll("/", "_")}`
);
}
+
+export function clientUpdate() {
+ // this a wild for updating client app
+ return window.__TAURI__?.updater
+ .checkUpdate()
+ .then((updateResult) => {
+ if (updateResult.shouldUpdate) {
+ window.__TAURI__?.updater
+ .installUpdate()
+ .then((result) => {
+ showToast(Locale.Settings.Update.Success);
+ })
+ .catch((e) => {
+ console.error("[Install Update Error]", e);
+ showToast(Locale.Settings.Update.Failed);
+ });
+ }
+ })
+ .catch((e) => {
+ console.error("[Check Update Error]", e);
+ showToast(Locale.Settings.Update.Failed);
+ });
+}
From bd9de4dc4db95a6d9aca4567d6147cb5b55b38ab Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 11 Oct 2024 11:42:36 +0800
Subject: [PATCH 007/236] fix version compare
---
app/components/settings.tsx | 4 ++--
app/utils.ts | 11 +++++++++++
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 9ee919b8d..c5653543c 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -49,7 +49,7 @@ import Locale, {
changeLang,
getLang,
} from "../locales";
-import { copyToClipboard, clientUpdate } from "../utils";
+import { copyToClipboard, clientUpdate, semverCompare } from "../utils";
import Link from "next/link";
import {
Anthropic,
@@ -585,7 +585,7 @@ export function Settings() {
const [checkingUpdate, setCheckingUpdate] = useState(false);
const currentVersion = updateStore.formatVersion(updateStore.version);
const remoteId = updateStore.formatVersion(updateStore.remoteVersion);
- const hasNewVersion = currentVersion !== remoteId;
+ const hasNewVersion = semverCompare(currentVersion, remoteId) === -1;
const updateUrl = getClientConfig()?.isApp ? RELEASE_URL : UPDATE_URL;
function checkUpdate(force = false) {
diff --git a/app/utils.ts b/app/utils.ts
index 9e75ffc7f..9e9e90f66 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -409,3 +409,14 @@ export function clientUpdate() {
showToast(Locale.Settings.Update.Failed);
});
}
+
+// https://gist.github.com/iwill/a83038623ba4fef6abb9efca87ae9ccb
+export function semverCompare(a, b) {
+ if (a.startsWith(b + "-")) return -1;
+ if (b.startsWith(a + "-")) return 1;
+ return a.localeCompare(b, undefined, {
+ numeric: true,
+ sensitivity: "case",
+ caseFirst: "upper",
+ });
+}
From a0d4a04192e2221f0ca969cab3236d4090a85955 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 11 Oct 2024 11:52:24 +0800
Subject: [PATCH 008/236] update
---
app/locales/en.ts | 2 +-
app/utils.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/locales/en.ts b/app/locales/en.ts
index a025a522f..40471536f 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -207,7 +207,7 @@ const en: LocaleType = {
IsChecking: "Checking update...",
FoundUpdate: (x: string) => `Found new version: ${x}`,
GoToUpdate: "Update",
- Success: "Update Succesfull.",
+ Success: "Update Successful.",
Failed: "Update Failed.",
},
SendKey: "Send Key",
diff --git a/app/utils.ts b/app/utils.ts
index 9e9e90f66..d8fc46330 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -411,7 +411,7 @@ export function clientUpdate() {
}
// https://gist.github.com/iwill/a83038623ba4fef6abb9efca87ae9ccb
-export function semverCompare(a, b) {
+export function semverCompare(a: string, b: string) {
if (a.startsWith(b + "-")) return -1;
if (b.startsWith(a + "-")) return 1;
return a.localeCompare(b, undefined, {
From be98aa20785c852ec8338090ed12798d34a50fba Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Fri, 11 Oct 2024 15:17:38 +0800
Subject: [PATCH 009/236] chore: improve test
---
.github/workflows/test.yml | 23 +++++++++++++++++++++++
package.json | 8 ++++----
2 files changed, 27 insertions(+), 4 deletions(-)
create mode 100644 .github/workflows/test.yml
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 000000000..b2f37cfb4
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,23 @@
+name: Run Tests
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ cache: "yarn"
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Run Jest tests
+ run: yarn test:ci
diff --git a/package.json b/package.json
index 6db49241f..e43344534 100644
--- a/package.json
+++ b/package.json
@@ -6,13 +6,13 @@
"mask": "npx tsx app/masks/build.ts",
"mask:watch": "npx watch \"yarn mask\" app/masks",
"dev": "concurrently -r \"yarn run mask:watch\" \"next dev\"",
- "build": "yarn test:ci && yarn mask && cross-env BUILD_MODE=standalone next build",
+ "build": "yarn mask && cross-env BUILD_MODE=standalone next build",
"start": "next start",
"lint": "next lint",
- "export": "yarn test:ci && yarn mask && cross-env BUILD_MODE=export BUILD_APP=1 next build",
+ "export": "yarn mask && cross-env BUILD_MODE=export BUILD_APP=1 next build",
"export:dev": "concurrently -r \"yarn mask:watch\" \"cross-env BUILD_MODE=export BUILD_APP=1 next dev\"",
"app:dev": "concurrently -r \"yarn mask:watch\" \"yarn tauri dev\"",
- "app:build": "yarn test:ci && yarn mask && yarn tauri build",
+ "app:build": "yarn mask && yarn tauri build",
"prompts": "node ./scripts/fetch-prompts.mjs",
"prepare": "husky install",
"proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev",
@@ -88,4 +88,4 @@
"lint-staged/yaml": "^2.2.2"
},
"packageManager": "yarn@1.22.19"
-}
\ No newline at end of file
+}
From bd43af3a8d57d06fd74ca6556641a7397e383684 Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Fri, 11 Oct 2024 15:41:46 +0800
Subject: [PATCH 010/236] chore: cache node_modules
---
.github/workflows/test.yml | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b2f37cfb4..fc885b293 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,6 +1,12 @@
name: Run Tests
-on: [push, pull_request]
+on:
+ push:
+ branches:
+ - main
+ tags:
+ - "!*"
+ pull_request:
jobs:
test:
@@ -16,6 +22,14 @@ jobs:
node-version: 18
cache: "yarn"
+ - name: Cache node_modules
+ uses: actions/cache@v4
+ with:
+ path: node_modules
+ key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-node_modules-
+
- name: Install dependencies
run: yarn install
From c98dc31cdfd8bb92d11cc19cb577cb4d99356a72 Mon Sep 17 00:00:00 2001
From: code-october <148516338+code-october@users.noreply.github.com>
Date: Fri, 11 Oct 2024 09:03:20 +0000
Subject: [PATCH 011/236] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BF=E9=97=AE?=
=?UTF-8?q?=E7=A0=81=E8=BE=93=E5=85=A5=E6=A1=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/auth.tsx | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/app/components/auth.tsx b/app/components/auth.tsx
index e19512d87..6c5e75d79 100644
--- a/app/components/auth.tsx
+++ b/app/components/auth.tsx
@@ -11,6 +11,7 @@ import Logo from "../icons/logo.svg";
import { useMobileScreen } from "@/app/utils";
import BotIcon from "../icons/bot.svg";
import { getClientConfig } from "../config/client";
+import { PasswordInput } from "./ui-lib";
import LeftIcon from "@/app/icons/left.svg";
import { safeLocalStorage } from "@/app/utils";
import {
@@ -60,17 +61,20 @@ export function AuthPage() {
{Locale.Auth.Title}
{Locale.Auth.Tips}
- {
accessStore.update(
(access) => (access.accessCode = e.currentTarget.value),
);
}}
/>
+
{!accessStore.hideUserApiKey ? (
<>
{Locale.Auth.SubTips}
From 4a7fd3a3803b229f6ecaeea03a0589017130470c Mon Sep 17 00:00:00 2001
From: code-october <148516338+code-october@users.noreply.github.com>
Date: Fri, 11 Oct 2024 10:36:11 +0000
Subject: [PATCH 012/236] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A6=96=E9=A1=B5=20?=
=?UTF-8?q?api=20=E8=BE=93=E5=85=A5=E6=A1=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/auth.tsx | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/app/components/auth.tsx b/app/components/auth.tsx
index 6c5e75d79..539a52eec 100644
--- a/app/components/auth.tsx
+++ b/app/components/auth.tsx
@@ -78,22 +78,26 @@ export function AuthPage() {
{!accessStore.hideUserApiKey ? (
<>
{Locale.Auth.SubTips}
- {
accessStore.update(
(access) => (access.openaiApiKey = e.currentTarget.value),
);
}}
/>
- {
accessStore.update(
(access) => (access.googleApiKey = e.currentTarget.value),
From 6792d6e475756b188f90c1f56d19188eabb7b55f Mon Sep 17 00:00:00 2001
From: code-october <148516338+code-october@users.noreply.github.com>
Date: Fri, 11 Oct 2024 11:19:36 +0000
Subject: [PATCH 013/236] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=89=8D=E7=AB=AF?=
=?UTF-8?q?=E4=BD=BF=E8=83=BD/=E7=A6=81=E7=94=A8=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E6=8A=98=E5=8F=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/markdown.tsx | 10 ++++++++--
app/components/mask.tsx | 17 +++++++++++++++++
app/components/settings.tsx | 15 +++++++++++++++
app/locales/cn.ts | 4 ++++
app/locales/en.ts | 5 +++++
app/store/config.ts | 2 ++
app/store/mask.ts | 1 +
7 files changed, 52 insertions(+), 2 deletions(-)
diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx
index a25b8537b..22664df8a 100644
--- a/app/components/markdown.tsx
+++ b/app/components/markdown.tsx
@@ -169,6 +169,12 @@ export function PreCode(props: { children: any }) {
}
function CustomCode(props: { children: any; className?: string }) {
+ const chatStore = useChatStore();
+ const session = chatStore.currentSession();
+ const config = useAppConfig();
+ const enableCodeFold =
+ session.mask?.enableCodeFold != false && config.enableCodeFold;
+
const ref = useRef(null);
const [collapsed, setCollapsed] = useState(true);
const [showToggle, setShowToggle] = useState(false);
@@ -190,13 +196,13 @@ function CustomCode(props: { children: any; className?: string }) {
className={props?.className}
ref={ref}
style={{
- maxHeight: collapsed ? "400px" : "none",
+ maxHeight: enableCodeFold && collapsed ? "400px" : "none",
overflowY: "hidden",
}}
>
{props.children}
- {showToggle && collapsed && (
+ {showToggle && enableCodeFold && collapsed && (
diff --git a/app/components/mask.tsx b/app/components/mask.tsx
index c60e7a528..12b19e335 100644
--- a/app/components/mask.tsx
+++ b/app/components/mask.tsx
@@ -183,6 +183,23 @@ export function MaskConfig(props: {
>
)}
+ {globalConfig.enableCodeFold && (
+
+ {
+ props.updateMask((mask) => {
+ mask.enableCodeFold = e.currentTarget.checked;
+ });
+ }}
+ >
+
+ )}
{!props.shouldSyncFromGlobal ? (
+
+
+ updateConfig(
+ (config) => (config.enableCodeFold = e.currentTarget.checked),
+ )
+ }
+ >
+
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index e5bcca0ed..3ba8dd80b 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -665,6 +665,10 @@ const cn = {
Title: "启用Artifacts",
SubTitle: "启用之后可以直接渲染HTML页面",
},
+ CodeFold: {
+ Title: "启用CodeFold",
+ SubTitle: "启用之后可以折叠/展开过长的代码块",
+ },
Share: {
Title: "分享此面具",
SubTitle: "生成此面具的直达链接",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 120457522..40782be7a 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -675,6 +675,11 @@ const en: LocaleType = {
Title: "Enable Artifacts",
SubTitle: "Can render HTML page when enable artifacts.",
},
+ CodeFold: {
+ Title: "Enable CodeFold",
+ SubTitle:
+ "Automatically collapse/expand overly long code block when enable CodeFold",
+ },
Share: {
Title: "Share This Mask",
SubTitle: "Generate a link to this mask",
diff --git a/app/store/config.ts b/app/store/config.ts
index f9ddce4a8..f14793c28 100644
--- a/app/store/config.ts
+++ b/app/store/config.ts
@@ -52,6 +52,8 @@ export const DEFAULT_CONFIG = {
enableArtifacts: true, // show artifacts config
+ enableCodeFold: true, // code fold config
+
disablePromptHint: false,
dontShowMaskSplashScreen: false, // dont show splash screen when create chat
diff --git a/app/store/mask.ts b/app/store/mask.ts
index 0c74a892e..850abeef6 100644
--- a/app/store/mask.ts
+++ b/app/store/mask.ts
@@ -19,6 +19,7 @@ export type Mask = {
builtin: boolean;
plugin?: string[];
enableArtifacts?: boolean;
+ enableCodeFold?: boolean;
};
export const DEFAULT_MASK_STATE = {
From 8fd843d228e75068c1233738d4ce6507e2bddc4c Mon Sep 17 00:00:00 2001
From: code-october <148516338+code-october@users.noreply.github.com>
Date: Fri, 11 Oct 2024 11:38:52 +0000
Subject: [PATCH 014/236] =?UTF-8?q?=E5=8F=82=E8=80=83coderabbitai=E5=BB=BA?=
=?UTF-8?q?=E8=AE=AE=E8=A7=84=E8=8C=83=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/markdown.tsx | 19 ++++++++++++-------
app/components/settings.tsx | 1 +
app/locales/cn.ts | 4 ++--
app/locales/en.ts | 2 +-
4 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx
index 22664df8a..8e744b441 100644
--- a/app/components/markdown.tsx
+++ b/app/components/markdown.tsx
@@ -190,6 +190,16 @@ function CustomCode(props: { children: any; className?: string }) {
const toggleCollapsed = () => {
setCollapsed((collapsed) => !collapsed);
};
+ const renderShowMoreButton = () => {
+ if (showToggle && enableCodeFold && collapsed) {
+ return (
+
+
+
+ );
+ }
+ return null;
+ };
return (
<>
{props.children}
- {showToggle && enableCodeFold && collapsed && (
-
-
-
- )}
+
+ {renderShowMoreButton()}
>
);
}
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 5f478374e..e24644813 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -1517,6 +1517,7 @@ export function Settings() {
aria-label={Locale.Mask.Config.CodeFold.Title}
type="checkbox"
checked={config.enableCodeFold}
+ data-testid="enable-code-fold-checkbox"
onChange={(e) =>
updateConfig(
(config) => (config.enableCodeFold = e.currentTarget.checked),
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 3ba8dd80b..09eafe492 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -666,8 +666,8 @@ const cn = {
SubTitle: "启用之后可以直接渲染HTML页面",
},
CodeFold: {
- Title: "启用CodeFold",
- SubTitle: "启用之后可以折叠/展开过长的代码块",
+ Title: "启用代码折叠",
+ SubTitle: "启用之后可以自动折叠/展开过长的代码块",
},
Share: {
Title: "分享此面具",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 40782be7a..8dfe8ed93 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -678,7 +678,7 @@ const en: LocaleType = {
CodeFold: {
Title: "Enable CodeFold",
SubTitle:
- "Automatically collapse/expand overly long code block when enable CodeFold",
+ "Automatically collapse/expand overly long code blocks when CodeFold is enabled",
},
Share: {
Title: "Share This Mask",
From 4a1319f2c0db25e5609553e2938761a4455ff6b8 Mon Sep 17 00:00:00 2001
From: code-october <148516338+code-october@users.noreply.github.com>
Date: Fri, 11 Oct 2024 11:57:23 +0000
Subject: [PATCH 015/236] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=AE=89=E5=85=A8?=
=?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/markdown.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx
index 8e744b441..9841a196d 100644
--- a/app/components/markdown.tsx
+++ b/app/components/markdown.tsx
@@ -173,7 +173,7 @@ function CustomCode(props: { children: any; className?: string }) {
const session = chatStore.currentSession();
const config = useAppConfig();
const enableCodeFold =
- session.mask?.enableCodeFold != false && config.enableCodeFold;
+ session.mask?.enableCodeFold !== false && config.enableCodeFold;
const ref = useRef
(null);
const [collapsed, setCollapsed] = useState(true);
@@ -212,7 +212,7 @@ function CustomCode(props: { children: any; className?: string }) {
>
{props.children}
-
+
{renderShowMoreButton()}
>
);
From 819238acaf5114329168f2c95da74d747795daa1 Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Fri, 11 Oct 2024 20:49:43 +0800
Subject: [PATCH 016/236] fix: i18n
---
app/locales/cn.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 3086cc63e..1e0116ec9 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -205,8 +205,8 @@ const cn = {
IsChecking: "正在检查更新...",
FoundUpdate: (x: string) => `发现新版本:${x}`,
GoToUpdate: "前往更新",
- Success: "Update Succesfull.",
- Failed: "Update Failed.",
+ Success: "更新成功!",
+ Failed: "更新失败",
},
SendKey: "发送键",
Theme: "主题",
From 9961b513cc0bfa1db8e1865af4099fdd9b78c15d Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Sat, 12 Oct 2024 14:54:22 +0800
Subject: [PATCH 017/236] fix: sidebar style
---
app/components/home.module.scss | 3 +++
app/components/sidebar.tsx | 11 +++++++++--
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/app/components/home.module.scss b/app/components/home.module.scss
index b31334568..381b6a9b9 100644
--- a/app/components/home.module.scss
+++ b/app/components/home.module.scss
@@ -140,6 +140,9 @@
display: flex;
justify-content: space-between;
align-items: center;
+ &-narrow {
+ justify-content: center;
+ }
}
.sidebar-logo {
diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx
index 493b1103b..2a5c308b7 100644
--- a/app/components/sidebar.tsx
+++ b/app/components/sidebar.tsx
@@ -165,11 +165,17 @@ export function SideBarHeader(props: {
subTitle?: string | React.ReactNode;
logo?: React.ReactNode;
children?: React.ReactNode;
+ shouldNarrow?: boolean;
}) {
- const { title, subTitle, logo, children } = props;
+ const { title, subTitle, logo, children, shouldNarrow } = props;
return (
-
+
{title}
@@ -227,6 +233,7 @@ export function SideBar(props: { className?: string }) {
title="NextChat"
subTitle="Build your own AI assistant."
logo={
}
+ shouldNarrow={shouldNarrow}
>
Date: Sat, 12 Oct 2024 16:49:24 +0000
Subject: [PATCH 018/236] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E2=80=9C=E5=8E=8B?=
=?UTF-8?q?=E7=BC=A9=E6=A8=A1=E5=9E=8B=E2=80=9D=E5=90=8D=E7=A7=B0=EF=BC=8C?=
=?UTF-8?q?=E5=A2=9E=E5=8A=A0=E2=80=9C=E7=94=9F=E6=88=90=E5=AF=B9=E8=AF=9D?=
=?UTF-8?q?=E6=A0=87=E9=A2=98=E2=80=9D=E7=9A=84=E5=8A=9F=E8=83=BD=E6=8F=90?=
=?UTF-8?q?=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/locales/cn.ts | 4 ++--
app/locales/en.ts | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 09eafe492..b7debe805 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -495,8 +495,8 @@ const cn = {
Model: "模型 (model)",
CompressModel: {
- Title: "压缩模型",
- SubTitle: "用于压缩历史记录的模型",
+ Title: "对话摘要模型",
+ SubTitle: "用于压缩历史记录、生成对话标题的模型",
},
Temperature: {
Title: "随机性 (temperature)",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 8dfe8ed93..5cc296f1e 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -500,8 +500,8 @@ const en: LocaleType = {
Model: "Model",
CompressModel: {
- Title: "Compression Model",
- SubTitle: "Model used to compress history",
+ Title: "Summary Model",
+ SubTitle: "Model used to compress history and generate title",
},
Temperature: {
Title: "Temperature",
From 12e7caa20977d8fbee2d16ec3d33ae3d94472603 Mon Sep 17 00:00:00 2001
From: ccq18 <348578429@qq.com>
Date: Mon, 14 Oct 2024 16:03:01 +0800
Subject: [PATCH 019/236] =?UTF-8?q?fix=20=E9=BB=98=E8=AE=A4=E8=B6=85?=
=?UTF-8?q?=E6=97=B6=E6=97=B6=E9=97=B4=E6=94=B9=E4=B8=BA3=E5=88=86?=
=?UTF-8?q?=E9=92=9F=EF=BC=8C=E6=94=AF=E6=8C=81o1-mini?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/constant.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/constant.ts b/app/constant.ts
index a06b8f050..856370511 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -95,7 +95,7 @@ export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id;
export const STORAGE_KEY = "chatgpt-next-web";
-export const REQUEST_TIMEOUT_MS = 60000;
+export const REQUEST_TIMEOUT_MS = 180000;
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
From 592f62005b474b78042a373cbfd7361f3de9b323 Mon Sep 17 00:00:00 2001
From: ccq18 <348578429@qq.com>
Date: Mon, 14 Oct 2024 16:31:17 +0800
Subject: [PATCH 020/236] =?UTF-8?q?=E4=BB=85=E4=BF=AE=E6=94=B9o1=E7=9A=84?=
=?UTF-8?q?=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4=E4=B8=BA4=E5=88=86?=
=?UTF-8?q?=E9=92=9F=EF=BC=8C=E5=87=8F=E5=B0=91o1=E7=B3=BB=E5=88=97?=
=?UTF-8?q?=E6=A8=A1=E5=9E=8B=E8=AF=B7=E6=B1=82=E5=A4=B1=E8=B4=A5=E7=9A=84?=
=?UTF-8?q?=E6=83=85=E5=86=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/client/platforms/openai.ts | 2 +-
app/constant.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts
index a22633611..76bac59e8 100644
--- a/app/client/platforms/openai.ts
+++ b/app/client/platforms/openai.ts
@@ -352,7 +352,7 @@ export class ChatGPTApi implements LLMApi {
// make a fetch request
const requestTimeoutId = setTimeout(
() => controller.abort(),
- isDalle3 || isO1 ? REQUEST_TIMEOUT_MS * 2 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow.
+ isDalle3 || isO1 ? REQUEST_TIMEOUT_MS * 4 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow.
);
const res = await fetch(chatPath, chatPayload);
diff --git a/app/constant.ts b/app/constant.ts
index 856370511..a06b8f050 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -95,7 +95,7 @@ export const UNFINISHED_INPUT = (id: string) => "unfinished-input-" + id;
export const STORAGE_KEY = "chatgpt-next-web";
-export const REQUEST_TIMEOUT_MS = 180000;
+export const REQUEST_TIMEOUT_MS = 60000;
export const EXPORT_MESSAGE_CLASS_NAME = "export-markdown";
From 8c39a687b5d8bb44dfcc103ac69910d97220bea7 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Mon, 14 Oct 2024 16:53:46 +0800
Subject: [PATCH 021/236] update deploy_preview run target
---
.github/workflows/deploy_preview.yml | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/.github/workflows/deploy_preview.yml b/.github/workflows/deploy_preview.yml
index 30d9b85b4..b98845243 100644
--- a/.github/workflows/deploy_preview.yml
+++ b/.github/workflows/deploy_preview.yml
@@ -3,9 +3,7 @@ name: VercelPreviewDeployment
on:
pull_request_target:
types:
- - opened
- - synchronize
- - reopened
+ - review_requested
env:
VERCEL_TEAM: ${{ secrets.VERCEL_TEAM }}
From 103106bb9371748b3f1ab3f304846546297657cc Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Mon, 14 Oct 2024 17:09:56 +0800
Subject: [PATCH 022/236] update test run target
---
.github/workflows/test.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fc885b293..faf7205d9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,6 +7,8 @@ on:
tags:
- "!*"
pull_request:
+ types:
+ - review_requested
jobs:
test:
From 7f454cbcec0d5c735726808ccbe75ba897142a31 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 14 Oct 2024 10:49:46 +0000
Subject: [PATCH 023/236] Bump @types/jest from 29.5.12 to 29.5.13
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.5.12 to 29.5.13.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)
---
updated-dependencies:
- dependency-name: "@types/jest"
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index e43344534..803c0d1a4 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"@tauri-apps/cli": "1.5.11",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
- "@types/jest": "^29.5.12",
+ "@types/jest": "^29.5.13",
"@types/js-yaml": "4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.30",
diff --git a/yarn.lock b/yarn.lock
index 0f6a29d6d..ef296924e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2263,10 +2263,10 @@
dependencies:
"@types/istanbul-lib-report" "*"
-"@types/jest@^29.5.12":
- version "29.5.12"
- resolved "https://registry.npmmirror.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544"
- integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==
+"@types/jest@^29.5.13":
+ version "29.5.13"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc"
+ integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==
dependencies:
expect "^29.0.0"
pretty-format "^29.0.0"
From 87d85c10c3b3dcd7a62e174dabb6338f005ce9b7 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Mon, 14 Oct 2024 21:48:36 +0800
Subject: [PATCH 024/236] update
---
src-tauri/tauri.conf.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index cc137ee8a..56a5b46a9 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -99,7 +99,7 @@
"endpoints": [
"https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/releases/latest/download/latest.json"
],
- "dialog": false,
+ "dialog": true,
"windows": {
"installMode": "passive"
},
From 463fa743e987624ab357c4335de0b133ccfa8fd0 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Tue, 15 Oct 2024 16:10:44 +0800
Subject: [PATCH 025/236] update version
---
src-tauri/tauri.conf.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 56a5b46a9..b2c3e04b0 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
- "version": "2.15.4"
+ "version": "2.15.5"
},
"tauri": {
"allowlist": {
From deb1e76c41ec156450db10872f88f84d2865d450 Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Wed, 16 Oct 2024 21:57:07 +0800
Subject: [PATCH 026/236] fix: use tauri fetch
---
app/client/platforms/anthropic.ts | 1 +
app/client/platforms/moonshot.ts | 1 +
app/client/platforms/openai.ts | 1 +
3 files changed, 3 insertions(+)
diff --git a/app/client/platforms/anthropic.ts b/app/client/platforms/anthropic.ts
index 1a83bd53a..3645cbe6e 100644
--- a/app/client/platforms/anthropic.ts
+++ b/app/client/platforms/anthropic.ts
@@ -13,6 +13,7 @@ import { getMessageTextContent, isVisionModel } from "@/app/utils";
import { preProcessImageContent, stream } from "@/app/utils/chat";
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
import { RequestPayload } from "./openai";
+import { fetch } from "@/app/utils/stream";
export type MultiBlockContent = {
type: "image" | "text";
diff --git a/app/client/platforms/moonshot.ts b/app/client/platforms/moonshot.ts
index e0ef3494f..22a34b2e2 100644
--- a/app/client/platforms/moonshot.ts
+++ b/app/client/platforms/moonshot.ts
@@ -24,6 +24,7 @@ import {
import { getClientConfig } from "@/app/config/client";
import { getMessageTextContent } from "@/app/utils";
import { RequestPayload } from "./openai";
+import { fetch } from "@/app/utils/stream";
export class MoonshotApi implements LLMApi {
private disableListModels = true;
diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts
index 76bac59e8..30f7415c1 100644
--- a/app/client/platforms/openai.ts
+++ b/app/client/platforms/openai.ts
@@ -42,6 +42,7 @@ import {
isVisionModel,
isDalle3 as _isDalle3,
} from "@/app/utils";
+import { fetch } from "@/app/utils/stream";
export interface OpenAIListModelResponse {
object: string;
From 8455fefc8aeada7e4204099f1548908320c4c1b7 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Wed, 23 Oct 2024 11:40:06 +0800
Subject: [PATCH 027/236] add xai
---
app/api/[provider]/[...path]/route.ts | 3 +
app/api/auth.ts | 5 +-
app/api/xai.ts | 128 +++++++++++++++++
app/client/api.ts | 10 ++
app/client/platforms/xai.ts | 195 ++++++++++++++++++++++++++
app/components/settings.tsx | 41 ++++++
app/config/server.ts | 9 ++
app/constant.ts | 23 +++
app/locales/cn.ts | 11 ++
app/locales/en.ts | 11 ++
app/store/access.ts | 12 ++
11 files changed, 447 insertions(+), 1 deletion(-)
create mode 100644 app/api/xai.ts
create mode 100644 app/client/platforms/xai.ts
diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts
index dffb3e9da..5ac248d0c 100644
--- a/app/api/[provider]/[...path]/route.ts
+++ b/app/api/[provider]/[...path]/route.ts
@@ -10,6 +10,7 @@ import { handle as alibabaHandler } from "../../alibaba";
import { handle as moonshotHandler } from "../../moonshot";
import { handle as stabilityHandler } from "../../stability";
import { handle as iflytekHandler } from "../../iflytek";
+import { handle as xaiHandler } from "../../xai";
import { handle as proxyHandler } from "../../proxy";
async function handle(
@@ -38,6 +39,8 @@ async function handle(
return stabilityHandler(req, { params });
case ApiPath.Iflytek:
return iflytekHandler(req, { params });
+ case ApiPath.XAI:
+ return xaiHandler(req, { params });
case ApiPath.OpenAI:
return openaiHandler(req, { params });
default:
diff --git a/app/api/auth.ts b/app/api/auth.ts
index 95965ceec..fb147cf51 100644
--- a/app/api/auth.ts
+++ b/app/api/auth.ts
@@ -92,6 +92,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
systemApiKey =
serverConfig.iflytekApiKey + ":" + serverConfig.iflytekApiSecret;
break;
+ case ModelProvider.XAI:
+ systemApiKey = serverConfig.xaiApiKey;
+ break;
case ModelProvider.GPT:
default:
if (req.nextUrl.pathname.includes("azure/deployments")) {
@@ -102,7 +105,7 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
}
if (systemApiKey) {
- console.log("[Auth] use system api key");
+ console.log("[Auth] use system api key", systemApiKey);
req.headers.set("Authorization", `Bearer ${systemApiKey}`);
} else {
console.log("[Auth] admin did not provide an api key");
diff --git a/app/api/xai.ts b/app/api/xai.ts
new file mode 100644
index 000000000..a4ee8b397
--- /dev/null
+++ b/app/api/xai.ts
@@ -0,0 +1,128 @@
+import { getServerSideConfig } from "@/app/config/server";
+import {
+ XAI_BASE_URL,
+ ApiPath,
+ ModelProvider,
+ ServiceProvider,
+} from "@/app/constant";
+import { prettyObject } from "@/app/utils/format";
+import { NextRequest, NextResponse } from "next/server";
+import { auth } from "@/app/api/auth";
+import { isModelAvailableInServer } from "@/app/utils/model";
+
+const serverConfig = getServerSideConfig();
+
+export async function handle(
+ req: NextRequest,
+ { params }: { params: { path: string[] } },
+) {
+ console.log("[XAI Route] params ", params);
+
+ if (req.method === "OPTIONS") {
+ return NextResponse.json({ body: "OK" }, { status: 200 });
+ }
+
+ const authResult = auth(req, ModelProvider.XAI);
+ if (authResult.error) {
+ return NextResponse.json(authResult, {
+ status: 401,
+ });
+ }
+
+ try {
+ const response = await request(req);
+ return response;
+ } catch (e) {
+ console.error("[XAI] ", e);
+ return NextResponse.json(prettyObject(e));
+ }
+}
+
+async function request(req: NextRequest) {
+ const controller = new AbortController();
+
+ // alibaba use base url or just remove the path
+ let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.XAI, "");
+
+ let baseUrl = serverConfig.xaiUrl || XAI_BASE_URL;
+
+ if (!baseUrl.startsWith("http")) {
+ baseUrl = `https://${baseUrl}`;
+ }
+
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.slice(0, -1);
+ }
+
+ console.log("[Proxy] ", path);
+ console.log("[Base Url]", baseUrl);
+
+ const timeoutId = setTimeout(
+ () => {
+ controller.abort();
+ },
+ 10 * 60 * 1000,
+ );
+
+ const fetchUrl = `${baseUrl}${path}`;
+ const fetchOptions: RequestInit = {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: req.headers.get("Authorization") ?? "",
+ },
+ method: req.method,
+ body: req.body,
+ redirect: "manual",
+ // @ts-ignore
+ duplex: "half",
+ signal: controller.signal,
+ };
+
+ // #1815 try to refuse some request to some models
+ if (serverConfig.customModels && req.body) {
+ try {
+ const clonedBody = await req.text();
+ fetchOptions.body = clonedBody;
+
+ const jsonBody = JSON.parse(clonedBody) as { model?: string };
+
+ // not undefined and is false
+ if (
+ isModelAvailableInServer(
+ serverConfig.customModels,
+ jsonBody?.model as string,
+ ServiceProvider.XAI as string,
+ )
+ ) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: `you are not allowed to use ${jsonBody?.model} model`,
+ },
+ {
+ status: 403,
+ },
+ );
+ }
+ } catch (e) {
+ console.error(`[XAI] filter`, e);
+ }
+ }
+ try {
+ const res = await fetch(fetchUrl, fetchOptions);
+
+ // to prevent browser prompt for credentials
+ const newHeaders = new Headers(res.headers);
+ newHeaders.delete("www-authenticate");
+ // to disable nginx buffering
+ newHeaders.set("X-Accel-Buffering", "no");
+
+ return new Response(res.body, {
+ status: res.status,
+ statusText: res.statusText,
+ headers: newHeaders,
+ });
+ } finally {
+ clearTimeout(timeoutId);
+ }
+}
diff --git a/app/client/api.ts b/app/client/api.ts
index 7a242ea99..4238c2a26 100644
--- a/app/client/api.ts
+++ b/app/client/api.ts
@@ -20,6 +20,7 @@ import { QwenApi } from "./platforms/alibaba";
import { HunyuanApi } from "./platforms/tencent";
import { MoonshotApi } from "./platforms/moonshot";
import { SparkApi } from "./platforms/iflytek";
+import { XAIApi } from "./platforms/xai";
export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
@@ -152,6 +153,9 @@ export class ClientApi {
case ModelProvider.Iflytek:
this.llm = new SparkApi();
break;
+ case ModelProvider.XAI:
+ this.llm = new XAIApi();
+ break;
default:
this.llm = new ChatGPTApi();
}
@@ -239,6 +243,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
const isAlibaba = modelConfig.providerName === ServiceProvider.Alibaba;
const isMoonshot = modelConfig.providerName === ServiceProvider.Moonshot;
const isIflytek = modelConfig.providerName === ServiceProvider.Iflytek;
+ const isXAI = modelConfig.providerName === ServiceProvider.XAI;
const isEnabledAccessControl = accessStore.enabledAccessControl();
const apiKey = isGoogle
? accessStore.googleApiKey
@@ -252,6 +257,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
? accessStore.alibabaApiKey
: isMoonshot
? accessStore.moonshotApiKey
+ : isXAI
+ ? accessStore.xaiApiKey
: isIflytek
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
@@ -266,6 +273,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
isAlibaba,
isMoonshot,
isIflytek,
+ isXAI,
apiKey,
isEnabledAccessControl,
};
@@ -328,6 +336,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
return new ClientApi(ModelProvider.Moonshot);
case ServiceProvider.Iflytek:
return new ClientApi(ModelProvider.Iflytek);
+ case ServiceProvider.XAI:
+ return new ClientApi(ModelProvider.XAI);
default:
return new ClientApi(ModelProvider.GPT);
}
diff --git a/app/client/platforms/xai.ts b/app/client/platforms/xai.ts
new file mode 100644
index 000000000..69f80e9fc
--- /dev/null
+++ b/app/client/platforms/xai.ts
@@ -0,0 +1,195 @@
+"use client";
+// azure and openai, using same models. so using same LLMApi.
+import { ApiPath, XAI_BASE_URL, XAI, REQUEST_TIMEOUT_MS } from "@/app/constant";
+import {
+ useAccessStore,
+ useAppConfig,
+ useChatStore,
+ ChatMessageTool,
+ usePluginStore,
+} from "@/app/store";
+import { stream } from "@/app/utils/chat";
+import {
+ ChatOptions,
+ getHeaders,
+ LLMApi,
+ LLMModel,
+ SpeechOptions,
+} from "../api";
+import { getClientConfig } from "@/app/config/client";
+import { getMessageTextContent } from "@/app/utils";
+import { RequestPayload } from "./openai";
+import { fetch } from "@/app/utils/stream";
+
+export class XAIApi implements LLMApi {
+ private disableListModels = true;
+
+ path(path: string): string {
+ const accessStore = useAccessStore.getState();
+
+ let baseUrl = "";
+
+ if (accessStore.useCustomConfig) {
+ baseUrl = accessStore.xaiUrl;
+ }
+
+ if (baseUrl.length === 0) {
+ const isApp = !!getClientConfig()?.isApp;
+ const apiPath = ApiPath.XAI;
+ baseUrl = isApp ? XAI_BASE_URL : apiPath;
+ }
+
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.slice(0, baseUrl.length - 1);
+ }
+ if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.XAI)) {
+ baseUrl = "https://" + baseUrl;
+ }
+
+ console.log("[Proxy Endpoint] ", baseUrl, path);
+
+ return [baseUrl, path].join("/");
+ }
+
+ extractMessage(res: any) {
+ return res.choices?.at(0)?.message?.content ?? "";
+ }
+
+ speech(options: SpeechOptions): Promise {
+ throw new Error("Method not implemented.");
+ }
+
+ async chat(options: ChatOptions) {
+ const messages: ChatOptions["messages"] = [];
+ for (const v of options.messages) {
+ const content = getMessageTextContent(v);
+ messages.push({ role: v.role, content });
+ }
+
+ const modelConfig = {
+ ...useAppConfig.getState().modelConfig,
+ ...useChatStore.getState().currentSession().mask.modelConfig,
+ ...{
+ model: options.config.model,
+ providerName: options.config.providerName,
+ },
+ };
+
+ const requestPayload: RequestPayload = {
+ messages,
+ 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] openai payload: ", requestPayload);
+
+ const shouldStream = !!options.config.stream;
+ const controller = new AbortController();
+ options.onController?.(controller);
+
+ try {
+ const chatPath = this.path(XAI.ChatPath);
+ const chatPayload = {
+ method: "POST",
+ body: JSON.stringify(requestPayload),
+ signal: controller.signal,
+ headers: getHeaders(),
+ };
+
+ // make a fetch request
+ const requestTimeoutId = setTimeout(
+ () => controller.abort(),
+ REQUEST_TIMEOUT_MS,
+ );
+
+ if (shouldStream) {
+ const [tools, funcs] = usePluginStore
+ .getState()
+ .getAsTools(
+ useChatStore.getState().currentSession().mask?.plugin || [],
+ );
+ return stream(
+ chatPath,
+ requestPayload,
+ getHeaders(),
+ tools as any,
+ funcs,
+ controller,
+ // parseSSE
+ (text: string, runTools: ChatMessageTool[]) => {
+ // console.log("parseSSE", text, runTools);
+ const json = JSON.parse(text);
+ const choices = json.choices as Array<{
+ delta: {
+ content: string;
+ tool_calls: ChatMessageTool[];
+ };
+ }>;
+ const tool_calls = choices[0]?.delta?.tool_calls;
+ if (tool_calls?.length > 0) {
+ const index = tool_calls[0]?.index;
+ const id = tool_calls[0]?.id;
+ const args = tool_calls[0]?.function?.arguments;
+ if (id) {
+ runTools.push({
+ id,
+ type: tool_calls[0]?.type,
+ function: {
+ name: tool_calls[0]?.function?.name as string,
+ arguments: args,
+ },
+ });
+ } else {
+ // @ts-ignore
+ runTools[index]["function"]["arguments"] += args;
+ }
+ }
+ return choices[0]?.delta?.content;
+ },
+ // processToolMessage, include tool_calls message and tool call results
+ (
+ requestPayload: RequestPayload,
+ toolCallMessage: any,
+ toolCallResult: any[],
+ ) => {
+ // @ts-ignore
+ requestPayload?.messages?.splice(
+ // @ts-ignore
+ requestPayload?.messages?.length,
+ 0,
+ toolCallMessage,
+ ...toolCallResult,
+ );
+ },
+ options,
+ );
+ } else {
+ const res = await fetch(chatPath, chatPayload);
+ clearTimeout(requestTimeoutId);
+
+ const resJson = await res.json();
+ const message = this.extractMessage(resJson);
+ options.onFinish(message);
+ }
+ } catch (e) {
+ console.log("[Request] failed to make a chat request", e);
+ options.onError?.(e as Error);
+ }
+ }
+ async usage() {
+ return {
+ used: 0,
+ total: 0,
+ };
+ }
+
+ async models(): Promise {
+ return [];
+ }
+}
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 82ce70e5a..6ce71b5ef 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -59,6 +59,7 @@ import {
ByteDance,
Alibaba,
Moonshot,
+ XAI,
Google,
GoogleSafetySettingsThreshold,
OPENAI_BASE_URL,
@@ -1194,6 +1195,45 @@ export function Settings() {
>
);
+ const XAIConfigComponent = accessStore.provider === ServiceProvider.XAI && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.moonshotUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.moonshotApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+ >
+ );
+
const stabilityConfigComponent = accessStore.provider ===
ServiceProvider.Stability && (
<>
@@ -1652,6 +1692,7 @@ export function Settings() {
{moonshotConfigComponent}
{stabilityConfigComponent}
{lflytekConfigComponent}
+ {XAIConfigComponent}
>
)}
>
diff --git a/app/config/server.ts b/app/config/server.ts
index 6544fe564..eac4ba0cf 100644
--- a/app/config/server.ts
+++ b/app/config/server.ts
@@ -71,6 +71,10 @@ declare global {
IFLYTEK_API_KEY?: string;
IFLYTEK_API_SECRET?: string;
+ // xai only
+ XAI_URL?: string;
+ XAI_API_KEY?: string;
+
// custom template for preprocessing user input
DEFAULT_INPUT_TEMPLATE?: string;
}
@@ -146,6 +150,7 @@ export const getServerSideConfig = () => {
const isAlibaba = !!process.env.ALIBABA_API_KEY;
const isMoonshot = !!process.env.MOONSHOT_API_KEY;
const isIflytek = !!process.env.IFLYTEK_API_KEY;
+ const isXAI = !!process.env.XAI_API_KEY;
// const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
// const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
// const randomIndex = Math.floor(Math.random() * apiKeys.length);
@@ -208,6 +213,10 @@ export const getServerSideConfig = () => {
iflytekApiKey: process.env.IFLYTEK_API_KEY,
iflytekApiSecret: process.env.IFLYTEK_API_SECRET,
+ isXAI,
+ xaiUrl: process.env.XAI_URL,
+ xaiApiKey: getApiKey(process.env.XAI_API_KEY),
+
cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID,
cloudflareKVNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID,
cloudflareKVApiKey: getApiKey(process.env.CLOUDFLARE_KV_API_KEY),
diff --git a/app/constant.ts b/app/constant.ts
index a06b8f050..9774bb594 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -28,6 +28,8 @@ export const TENCENT_BASE_URL = "https://hunyuan.tencentcloudapi.com";
export const MOONSHOT_BASE_URL = "https://api.moonshot.cn";
export const IFLYTEK_BASE_URL = "https://spark-api-open.xf-yun.com";
+export const XAI_BASE_URL = "https://api.x.ai";
+
export const CACHE_URL_PREFIX = "/api/cache";
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
@@ -59,6 +61,7 @@ export enum ApiPath {
Iflytek = "/api/iflytek",
Stability = "/api/stability",
Artifacts = "/api/artifacts",
+ XAI = "/api/xai",
}
export enum SlotID {
@@ -111,6 +114,7 @@ export enum ServiceProvider {
Moonshot = "Moonshot",
Stability = "Stability",
Iflytek = "Iflytek",
+ XAI = "XAI",
}
// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
@@ -133,6 +137,7 @@ export enum ModelProvider {
Hunyuan = "Hunyuan",
Moonshot = "Moonshot",
Iflytek = "Iflytek",
+ XAI = "XAI",
}
export const Stability = {
@@ -215,6 +220,11 @@ export const Iflytek = {
ChatPath: "v1/chat/completions",
};
+export const XAI = {
+ ExampleEndpoint: XAI_BASE_URL,
+ ChatPath: "v1/chat/completions",
+};
+
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
// export const DEFAULT_SYSTEM_TEMPLATE = `
// You are ChatGPT, a large language model trained by {{ServiceProvider}}.
@@ -364,6 +374,8 @@ const iflytekModels = [
"4.0Ultra",
];
+const xAIModes = ["grok-beta"];
+
let seq = 1000; // 内置的模型序号生成器从1000开始
export const DEFAULT_MODELS = [
...openaiModels.map((name) => ({
@@ -476,6 +488,17 @@ export const DEFAULT_MODELS = [
sorted: 10,
},
})),
+ ...xAIModes.map((name) => ({
+ name,
+ available: true,
+ sorted: seq++,
+ provider: {
+ id: "xai",
+ providerName: "XAI",
+ providerType: "xai",
+ sorted: 11,
+ },
+ })),
] as const;
export const CHAT_PAGE_SIZE = 15;
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index e514eb4fe..006fc8162 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -462,6 +462,17 @@ const cn = {
SubTitle: "样例:",
},
},
+ XAI: {
+ ApiKey: {
+ Title: "接口密钥",
+ SubTitle: "使用自定义XAI API Key",
+ Placeholder: "XAI API Key",
+ },
+ Endpoint: {
+ Title: "接口地址",
+ SubTitle: "样例:",
+ },
+ },
Stability: {
ApiKey: {
Title: "接口密钥",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index c86cc08f0..7204bd946 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -446,6 +446,17 @@ const en: LocaleType = {
SubTitle: "Example: ",
},
},
+ XAI: {
+ ApiKey: {
+ Title: "XAI API Key",
+ SubTitle: "Use a custom XAI API Key",
+ Placeholder: "XAI API Key",
+ },
+ Endpoint: {
+ Title: "Endpoint Address",
+ SubTitle: "Example: ",
+ },
+ },
Stability: {
ApiKey: {
Title: "Stability API Key",
diff --git a/app/store/access.ts b/app/store/access.ts
index dec3a7258..1a27deb1c 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -13,6 +13,7 @@ import {
MOONSHOT_BASE_URL,
STABILITY_BASE_URL,
IFLYTEK_BASE_URL,
+ XAI_BASE_URL,
} from "../constant";
import { getHeaders } from "../client/api";
import { getClientConfig } from "../config/client";
@@ -44,6 +45,8 @@ const DEFAULT_STABILITY_URL = isApp ? STABILITY_BASE_URL : ApiPath.Stability;
const DEFAULT_IFLYTEK_URL = isApp ? IFLYTEK_BASE_URL : ApiPath.Iflytek;
+const DEFAULT_XAI_URL = isApp ? XAI_BASE_URL : ApiPath.XAI;
+
const DEFAULT_ACCESS_STATE = {
accessCode: "",
useCustomConfig: false,
@@ -101,6 +104,10 @@ const DEFAULT_ACCESS_STATE = {
iflytekApiKey: "",
iflytekApiSecret: "",
+ // moonshot
+ xaiUrl: DEFAULT_XAI_URL,
+ xaiApiKey: "",
+
// server config
needCode: true,
hideUserApiKey: false,
@@ -169,6 +176,10 @@ export const useAccessStore = createPersistStore(
return ensure(get(), ["iflytekApiKey"]);
},
+ isValidXAI() {
+ return ensure(get(), ["xaiApiKey"]);
+ },
+
isAuthorized() {
this.fetch();
@@ -184,6 +195,7 @@ export const useAccessStore = createPersistStore(
this.isValidTencent() ||
this.isValidMoonshot() ||
this.isValidIflytek() ||
+ this.isValidXAI() ||
!this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
);
From e791cd441d544a18126ddb825651d0e6274020e9 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Wed, 23 Oct 2024 11:55:25 +0800
Subject: [PATCH 028/236] add xai
---
app/api/auth.ts | 2 +-
app/client/platforms/xai.ts | 4 +---
app/store/access.ts | 2 +-
3 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/app/api/auth.ts b/app/api/auth.ts
index fb147cf51..d4ac66a11 100644
--- a/app/api/auth.ts
+++ b/app/api/auth.ts
@@ -105,7 +105,7 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
}
if (systemApiKey) {
- console.log("[Auth] use system api key", systemApiKey);
+ console.log("[Auth] use system api key");
req.headers.set("Authorization", `Bearer ${systemApiKey}`);
} else {
console.log("[Auth] admin did not provide an api key");
diff --git a/app/client/platforms/xai.ts b/app/client/platforms/xai.ts
index 69f80e9fc..deb74e66c 100644
--- a/app/client/platforms/xai.ts
+++ b/app/client/platforms/xai.ts
@@ -83,11 +83,9 @@ export class XAIApi implements LLMApi {
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] openai payload: ", requestPayload);
+ console.log("[Request] xai payload: ", requestPayload);
const shouldStream = !!options.config.stream;
const controller = new AbortController();
diff --git a/app/store/access.ts b/app/store/access.ts
index 1a27deb1c..b3d412a2d 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -104,7 +104,7 @@ const DEFAULT_ACCESS_STATE = {
iflytekApiKey: "",
iflytekApiSecret: "",
- // moonshot
+ // xai
xaiUrl: DEFAULT_XAI_URL,
xaiApiKey: "",
From 65bb962fc0b6eaa0cb1e15451d954df216b1956f Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Wed, 23 Oct 2024 12:00:59 +0800
Subject: [PATCH 029/236] hotfix
---
app/components/settings.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 6ce71b5ef..666caece8 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -1206,11 +1206,11 @@ export function Settings() {
accessStore.update(
- (access) => (access.moonshotUrl = e.currentTarget.value),
+ (access) => (access.xaiUrl = e.currentTarget.value),
)
}
>
@@ -1221,12 +1221,12 @@ export function Settings() {
>
{
accessStore.update(
- (access) => (access.moonshotApiKey = e.currentTarget.value),
+ (access) => (access.xaiApiKey = e.currentTarget.value),
);
}}
/>
From 801dc412f99937dfd64a895309d9304429d94cac Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Thu, 24 Oct 2024 15:28:05 +0800
Subject: [PATCH 030/236] add claude-3.5-haiku
---
app/constant.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/constant.ts b/app/constant.ts
index 9774bb594..00b8d9dae 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -319,6 +319,7 @@ const anthropicModels = [
"claude-3-opus-20240229",
"claude-3-haiku-20240307",
"claude-3-5-sonnet-20240620",
+ "claude-3-5-haiku-latest",
];
const baiduModels = [
From 4745706c42a390117e5e0f700af3d5f06e18f312 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Thu, 24 Oct 2024 15:32:27 +0800
Subject: [PATCH 031/236] update version to v2.15.6
---
src-tauri/tauri.conf.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index b2c3e04b0..415825b13 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -9,7 +9,7 @@
},
"package": {
"productName": "NextChat",
- "version": "2.15.5"
+ "version": "2.15.6"
},
"tauri": {
"allowlist": {
From e3ca7e8b4433bea43376035b9417fe233fe5f6f0 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 25 Oct 2024 17:52:08 +0800
Subject: [PATCH 032/236] hotfix for statusText is non ISO-8859-1 #5717
---
src-tauri/src/stream.rs | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src-tauri/src/stream.rs b/src-tauri/src/stream.rs
index d2c0726b0..d31dd67c3 100644
--- a/src-tauri/src/stream.rs
+++ b/src-tauri/src/stream.rs
@@ -119,11 +119,20 @@ pub async fn stream_fetch(
}
}
Err(err) => {
- println!("Error response: {:?}", err.source().expect("REASON").to_string());
+ let error: String = err.source().expect("REASON").to_string();
+ println!("Error response: {:?}", error);
+ tauri::async_runtime::spawn( async move {
+ if let Err(e) = window.emit(event_name, ChunkPayload{ request_id, chunk: error.into() }) {
+ println!("Failed to emit chunk payload: {:?}", e);
+ }
+ if let Err(e) = window.emit(event_name, EndPayload{ request_id, status: 0 }) {
+ println!("Failed to emit end payload: {:?}", e);
+ }
+ });
StreamResponse {
request_id,
status: 599,
- status_text: err.source().expect("REASON").to_string(),
+ status_text: "Error".to_string(),
headers: HashMap::new(),
}
}
From 2c745590101b5201c677243f151616cb7023186e Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 25 Oct 2024 18:02:51 +0800
Subject: [PATCH 033/236] hitfix
---
app/utils/stream.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/utils/stream.ts b/app/utils/stream.ts
index 2eda768f3..782634595 100644
--- a/app/utils/stream.ts
+++ b/app/utils/stream.ts
@@ -100,7 +100,8 @@ export function fetch(url: string, options?: RequestInit): Promise {
})
.catch((e) => {
console.error("stream error", e);
- throw e;
+ // throw e;
+ return new Response("", { status: 599 });
});
}
return window.fetch(url, options);
From 90ced9287626492898f2eb9bfd3b079171faf6ea Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 25 Oct 2024 18:05:29 +0800
Subject: [PATCH 034/236] update
---
src-tauri/src/stream.rs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src-tauri/src/stream.rs b/src-tauri/src/stream.rs
index d31dd67c3..8320db3e4 100644
--- a/src-tauri/src/stream.rs
+++ b/src-tauri/src/stream.rs
@@ -119,7 +119,9 @@ pub async fn stream_fetch(
}
}
Err(err) => {
- let error: String = err.source().expect("REASON").to_string();
+ let error: String = err.source()
+ .map(|e| e.to_string())
+ .unwrap_or_else(|| "Unknown error occurred".to_string());
println!("Error response: {:?}", error);
tauri::async_runtime::spawn( async move {
if let Err(e) = window.emit(event_name, ChunkPayload{ request_id, chunk: error.into() }) {
From f89872b833d27c48b33281e60157640037e17a99 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 25 Oct 2024 18:12:09 +0800
Subject: [PATCH 035/236] hotfix for gemini invald argument #5715
---
app/client/platforms/google.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts
index 7265a500b..14fecb8f2 100644
--- a/app/client/platforms/google.ts
+++ b/app/client/platforms/google.ts
@@ -192,7 +192,9 @@ export class GeminiProApi implements LLMApi {
requestPayload,
getHeaders(),
// @ts-ignore
- [{ functionDeclarations: tools.map((tool) => tool.function) }],
+ tools.length > 0
+ ? [{ functionDeclarations: tools.map((tool) => tool.function) }]
+ : [],
funcs,
controller,
// parseSSE
From f0b3e10a6caf55bf91325183b5ad84de2a05db04 Mon Sep 17 00:00:00 2001
From: lloydzhou
Date: Fri, 25 Oct 2024 18:19:22 +0800
Subject: [PATCH 036/236] hotfix for gemini invald argument #5715
---
app/client/platforms/google.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts
index 14fecb8f2..a4b594ddf 100644
--- a/app/client/platforms/google.ts
+++ b/app/client/platforms/google.ts
@@ -193,7 +193,8 @@ export class GeminiProApi implements LLMApi {
getHeaders(),
// @ts-ignore
tools.length > 0
- ? [{ functionDeclarations: tools.map((tool) => tool.function) }]
+ ? // @ts-ignore
+ [{ functionDeclarations: tools.map((tool) => tool.function) }]
: [],
funcs,
controller,
From 45db20c1c37279ebfe610d75a80dc09a21a14c54 Mon Sep 17 00:00:00 2001
From: ElricLiu <20209191+ElricLiu@users.noreply.github.com>
Date: Sat, 26 Oct 2024 11:16:43 +0800
Subject: [PATCH 037/236] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d370000fa..b9e994e50 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[MacOS-image]: https://img.shields.io/badge/-MacOS-black?logo=apple
[Linux-image]: https://img.shields.io/badge/-Linux-333?logo=ubuntu
-[
](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [
](https://zeabur.com/templates/ZBUEFA) [
](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
+[
](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [
](https://zeabur.com/templates/ZBUEFA) [
](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [
](https://www.bt.cn/new/download.html)
[
](https://monica.im/?utm=nxcrp)
From 49d42bb45d50141c6f7321ea650b0c5d58697591 Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Mon, 28 Oct 2024 16:47:05 +0800
Subject: [PATCH 038/236] chore: improve jest
---
jest.setup.ts | 22 +++++++++++++++++++
package.json | 3 ++-
yarn.lock | 61 +++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 83 insertions(+), 3 deletions(-)
diff --git a/jest.setup.ts b/jest.setup.ts
index ee6ccea1a..bc515f9a1 100644
--- a/jest.setup.ts
+++ b/jest.setup.ts
@@ -1,2 +1,24 @@
// Learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";
+
+global.fetch = jest.fn(() =>
+ Promise.resolve({
+ ok: true,
+ status: 200,
+ json: () => Promise.resolve({}),
+ headers: new Headers(),
+ redirected: false,
+ statusText: "OK",
+ type: "basic",
+ url: "",
+ clone: function () {
+ return this;
+ },
+ body: null,
+ bodyUsed: false,
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
+ blob: () => Promise.resolve(new Blob()),
+ formData: () => Promise.resolve(new FormData()),
+ text: () => Promise.resolve(""),
+ }),
+);
diff --git a/package.json b/package.json
index 803c0d1a4..a984af688 100644
--- a/package.json
+++ b/package.json
@@ -33,8 +33,8 @@
"html-to-image": "^1.11.11",
"idb-keyval": "^6.2.1",
"lodash-es": "^4.17.21",
- "mermaid": "^10.6.1",
"markdown-to-txt": "^2.0.1",
+ "mermaid": "^10.6.1",
"nanoid": "^5.0.3",
"next": "^14.1.1",
"node-fetch": "^3.3.1",
@@ -56,6 +56,7 @@
"devDependencies": {
"@tauri-apps/api": "^1.6.0",
"@tauri-apps/cli": "1.5.11",
+ "@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.13",
diff --git a/yarn.lock b/yarn.lock
index ef296924e..3f32ae7d3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -27,6 +27,15 @@
dependencies:
"@babel/highlight" "^7.18.6"
+"@babel/code-frame@^7.10.4":
+ version "7.26.0"
+ resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.0.tgz#9374b5cd068d128dac0b94ff482594273b1c2815"
+ integrity sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.9"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7":
version "7.24.7"
resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465"
@@ -394,6 +403,11 @@
resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
+"@babel/helper-validator-identifier@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
+ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+
"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
@@ -2093,6 +2107,20 @@
"@tauri-apps/cli-win32-ia32-msvc" "1.5.11"
"@tauri-apps/cli-win32-x64-msvc" "1.5.11"
+"@testing-library/dom@^10.4.0":
+ version "10.4.0"
+ resolved "https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8"
+ integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/runtime" "^7.12.5"
+ "@types/aria-query" "^5.0.1"
+ aria-query "5.3.0"
+ chalk "^4.1.0"
+ dom-accessibility-api "^0.5.9"
+ lz-string "^1.5.0"
+ pretty-format "^27.0.2"
+
"@testing-library/jest-dom@^6.4.8":
version "6.4.8"
resolved "https://registry.npmmirror.com/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz#9c435742b20c6183d4e7034f2b329d562c079daa"
@@ -2144,6 +2172,11 @@
resolved "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
+"@types/aria-query@^5.0.1":
+ version "5.0.4"
+ resolved "https://registry.npmmirror.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708"
+ integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
+
"@types/babel__core@^7.1.14":
version "7.20.5"
resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -2738,7 +2771,7 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-aria-query@^5.0.0:
+aria-query@5.3.0, aria-query@^5.0.0:
version "5.3.0"
resolved "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e"
integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
@@ -3081,7 +3114,7 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@^4.0.0, chalk@^4.1.2:
+chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -3877,6 +3910,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dom-accessibility-api@^0.5.9:
+ version "0.5.16"
+ resolved "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
+ integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
+
dom-accessibility-api@^0.6.3:
version "0.6.3"
resolved "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8"
@@ -6052,6 +6090,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+lz-string@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.npmmirror.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
+ integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
+
make-dir@^4.0.0:
version "4.0.0"
resolved "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
@@ -7018,6 +7061,15 @@ prettier@^3.0.2:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
+pretty-format@^27.0.2:
+ version "27.5.1"
+ resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
+ integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
+ dependencies:
+ ansi-regex "^5.0.1"
+ ansi-styles "^5.0.0"
+ react-is "^17.0.1"
+
pretty-format@^29.0.0, pretty-format@^29.7.0:
version "29.7.0"
resolved "https://registry.npmmirror.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
@@ -7109,6 +7161,11 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-is@^17.0.1:
+ version "17.0.2"
+ resolved "https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+ integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
react-is@^18.0.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
From a4d7a2c6e3ef4d325a8039b5dd5bb9445d496c02 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 28 Oct 2024 10:31:27 +0000
Subject: [PATCH 039/236] Bump @testing-library/jest-dom from 6.4.8 to 6.6.2
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.4.8 to 6.6.2.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.4.8...v6.6.2)
---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
package.json | 2 +-
yarn.lock | 18 +++++-------------
2 files changed, 6 insertions(+), 14 deletions(-)
diff --git a/package.json b/package.json
index 803c0d1a4..108bf7083 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"devDependencies": {
"@tauri-apps/api": "^1.6.0",
"@tauri-apps/cli": "1.5.11",
- "@testing-library/jest-dom": "^6.4.8",
+ "@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.13",
"@types/js-yaml": "4.0.9",
diff --git a/yarn.lock b/yarn.lock
index ef296924e..b03c3c991 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2093,13 +2093,12 @@
"@tauri-apps/cli-win32-ia32-msvc" "1.5.11"
"@tauri-apps/cli-win32-x64-msvc" "1.5.11"
-"@testing-library/jest-dom@^6.4.8":
- version "6.4.8"
- resolved "https://registry.npmmirror.com/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz#9c435742b20c6183d4e7034f2b329d562c079daa"
- integrity sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==
+"@testing-library/jest-dom@^6.6.2":
+ version "6.6.2"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz#8186aa9a07263adef9cc5a59a4772db8c31f4a5b"
+ integrity sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==
dependencies:
"@adobe/css-tools" "^4.4.0"
- "@babel/runtime" "^7.9.2"
aria-query "^5.0.0"
chalk "^3.0.0"
css.escape "^1.5.1"
@@ -2738,20 +2737,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-aria-query@^5.0.0:
+aria-query@^5.0.0, aria-query@^5.1.3:
version "5.3.0"
resolved "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e"
integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==
dependencies:
dequal "^2.0.3"
-aria-query@^5.1.3:
- version "5.1.3"
- resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e"
- integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
- dependencies:
- deep-equal "^2.0.5"
-
array-buffer-byte-length@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
From 24df85cf9d3ab2a307baa1539922c9463949ffa9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 28 Oct 2024 10:31:34 +0000
Subject: [PATCH 040/236] Bump @types/jest from 29.5.13 to 29.5.14
Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.5.13 to 29.5.14.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)
---
updated-dependencies:
- dependency-name: "@types/jest"
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 803c0d1a4..bee434a63 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"@tauri-apps/cli": "1.5.11",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
- "@types/jest": "^29.5.13",
+ "@types/jest": "^29.5.14",
"@types/js-yaml": "4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.30",
diff --git a/yarn.lock b/yarn.lock
index ef296924e..7625b280e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2263,10 +2263,10 @@
dependencies:
"@types/istanbul-lib-report" "*"
-"@types/jest@^29.5.13":
- version "29.5.13"
- resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.13.tgz#8bc571659f401e6a719a7bf0dbcb8b78c71a8adc"
- integrity sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==
+"@types/jest@^29.5.14":
+ version "29.5.14"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5"
+ integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==
dependencies:
expect "^29.0.0"
pretty-format "^29.0.0"
From 736cbdbdd12d340e9b08b69724f9a1321befd645 Mon Sep 17 00:00:00 2001
From: hyiip
Date: Wed, 30 Oct 2024 02:18:41 +0800
Subject: [PATCH 041/236] add constant to claude 3.5 sonnet 20241022
---
app/constant.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/constant.ts b/app/constant.ts
index 9774bb594..f2021736a 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -319,6 +319,7 @@ const anthropicModels = [
"claude-3-opus-20240229",
"claude-3-haiku-20240307",
"claude-3-5-sonnet-20240620",
+ "claude-3-5-sonnet-20241022",
];
const baiduModels = [
From 86ffa1e6430b0a34893665bb284130c1f144e399 Mon Sep 17 00:00:00 2001
From: yuxuan-ctrl <714180720@qq.com>
Date: Wed, 30 Oct 2024 16:30:01 +0800
Subject: [PATCH 042/236] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=98=BF?=
=?UTF-8?q?=E9=87=8C=E7=B3=BB=E6=A8=A1=E5=9E=8B=E4=BB=A3=E7=A0=81=E9=85=8D?=
=?UTF-8?q?=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
next.config.mjs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/next.config.mjs b/next.config.mjs
index 26dadca4c..2bb6bc4f4 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -94,8 +94,12 @@ if (mode !== "export") {
source: "/sharegpt",
destination: "https://sharegpt.com/api/conversations",
},
+ {
+ source: "/api/proxy/alibaba/:path*",
+ destination: "https://dashscope.aliyuncs.com/api/:path*",
+ },
];
-
+
return {
beforeFiles: ret,
};
From d357b45e84eb773c2e0c142d0d849c4f20be2975 Mon Sep 17 00:00:00 2001
From: DDMeaqua
Date: Wed, 30 Oct 2024 19:24:03 +0800
Subject: [PATCH 043/236] =?UTF-8?q?feat:=20[#5714]=20=E6=94=AF=E6=8C=81GLM?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/api/[provider]/[...path]/route.ts | 3 +
app/api/auth.ts | 3 +
app/api/glm.ts | 129 +++++++++++++++++
app/client/api.ts | 10 ++
app/client/platforms/glm.ts | 192 ++++++++++++++++++++++++++
app/components/settings.tsx | 41 ++++++
app/config/server.ts | 9 ++
app/constant.ts | 32 +++++
app/locales/cn.ts | 11 ++
app/locales/en.ts | 11 ++
app/store/access.ts | 12 ++
11 files changed, 453 insertions(+)
create mode 100644 app/api/glm.ts
create mode 100644 app/client/platforms/glm.ts
diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts
index 5ac248d0c..78836cc52 100644
--- a/app/api/[provider]/[...path]/route.ts
+++ b/app/api/[provider]/[...path]/route.ts
@@ -11,6 +11,7 @@ import { handle as moonshotHandler } from "../../moonshot";
import { handle as stabilityHandler } from "../../stability";
import { handle as iflytekHandler } from "../../iflytek";
import { handle as xaiHandler } from "../../xai";
+import { handle as glmHandler } from "../../glm";
import { handle as proxyHandler } from "../../proxy";
async function handle(
@@ -41,6 +42,8 @@ async function handle(
return iflytekHandler(req, { params });
case ApiPath.XAI:
return xaiHandler(req, { params });
+ case ApiPath.GLM:
+ return glmHandler(req, { params });
case ApiPath.OpenAI:
return openaiHandler(req, { params });
default:
diff --git a/app/api/auth.ts b/app/api/auth.ts
index d4ac66a11..db920fc28 100644
--- a/app/api/auth.ts
+++ b/app/api/auth.ts
@@ -95,6 +95,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
case ModelProvider.XAI:
systemApiKey = serverConfig.xaiApiKey;
break;
+ case ModelProvider.GLM:
+ systemApiKey = serverConfig.glmApiKey;
+ break;
case ModelProvider.GPT:
default:
if (req.nextUrl.pathname.includes("azure/deployments")) {
diff --git a/app/api/glm.ts b/app/api/glm.ts
new file mode 100644
index 000000000..d40c4b6a8
--- /dev/null
+++ b/app/api/glm.ts
@@ -0,0 +1,129 @@
+import { getServerSideConfig } from "@/app/config/server";
+import {
+ GLM_BASE_URL,
+ ApiPath,
+ ModelProvider,
+ ServiceProvider,
+} from "@/app/constant";
+import { prettyObject } from "@/app/utils/format";
+import { NextRequest, NextResponse } from "next/server";
+import { auth } from "@/app/api/auth";
+import { isModelAvailableInServer } from "@/app/utils/model";
+
+const serverConfig = getServerSideConfig();
+
+export async function handle(
+ req: NextRequest,
+ { params }: { params: { path: string[] } },
+) {
+ console.log("[GLM Route] params ", params);
+
+ if (req.method === "OPTIONS") {
+ return NextResponse.json({ body: "OK" }, { status: 200 });
+ }
+
+ const authResult = auth(req, ModelProvider.GLM);
+ if (authResult.error) {
+ return NextResponse.json(authResult, {
+ status: 401,
+ });
+ }
+
+ try {
+ const response = await request(req);
+ return response;
+ } catch (e) {
+ console.error("[GLM] ", e);
+ return NextResponse.json(prettyObject(e));
+ }
+}
+
+async function request(req: NextRequest) {
+ const controller = new AbortController();
+
+ // alibaba use base url or just remove the path
+ let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.GLM, "");
+
+ let baseUrl = serverConfig.glmUrl || GLM_BASE_URL;
+
+ if (!baseUrl.startsWith("http")) {
+ baseUrl = `https://${baseUrl}`;
+ }
+
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.slice(0, -1);
+ }
+
+ console.log("[Proxy] ", path);
+ console.log("[Base Url]", baseUrl);
+
+ const timeoutId = setTimeout(
+ () => {
+ controller.abort();
+ },
+ 10 * 60 * 1000,
+ );
+
+ const fetchUrl = `${baseUrl}${path}`;
+ console.log("[Fetch Url] ", fetchUrl);
+ const fetchOptions: RequestInit = {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: req.headers.get("Authorization") ?? "",
+ },
+ method: req.method,
+ body: req.body,
+ redirect: "manual",
+ // @ts-ignore
+ duplex: "half",
+ signal: controller.signal,
+ };
+
+ // #1815 try to refuse some request to some models
+ if (serverConfig.customModels && req.body) {
+ try {
+ const clonedBody = await req.text();
+ fetchOptions.body = clonedBody;
+
+ const jsonBody = JSON.parse(clonedBody) as { model?: string };
+
+ // not undefined and is false
+ if (
+ isModelAvailableInServer(
+ serverConfig.customModels,
+ jsonBody?.model as string,
+ ServiceProvider.GLM as string,
+ )
+ ) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: `you are not allowed to use ${jsonBody?.model} model`,
+ },
+ {
+ status: 403,
+ },
+ );
+ }
+ } catch (e) {
+ console.error(`[GLM] filter`, e);
+ }
+ }
+ try {
+ const res = await fetch(fetchUrl, fetchOptions);
+
+ // to prevent browser prompt for credentials
+ const newHeaders = new Headers(res.headers);
+ newHeaders.delete("www-authenticate");
+ // to disable nginx buffering
+ newHeaders.set("X-Accel-Buffering", "no");
+
+ return new Response(res.body, {
+ status: res.status,
+ statusText: res.statusText,
+ headers: newHeaders,
+ });
+ } finally {
+ clearTimeout(timeoutId);
+ }
+}
diff --git a/app/client/api.ts b/app/client/api.ts
index 4238c2a26..4082d085c 100644
--- a/app/client/api.ts
+++ b/app/client/api.ts
@@ -21,6 +21,7 @@ import { HunyuanApi } from "./platforms/tencent";
import { MoonshotApi } from "./platforms/moonshot";
import { SparkApi } from "./platforms/iflytek";
import { XAIApi } from "./platforms/xai";
+import { GLMApi } from "./platforms/glm";
export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
@@ -156,6 +157,9 @@ export class ClientApi {
case ModelProvider.XAI:
this.llm = new XAIApi();
break;
+ case ModelProvider.GLM:
+ this.llm = new GLMApi();
+ break;
default:
this.llm = new ChatGPTApi();
}
@@ -244,6 +248,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
const isMoonshot = modelConfig.providerName === ServiceProvider.Moonshot;
const isIflytek = modelConfig.providerName === ServiceProvider.Iflytek;
const isXAI = modelConfig.providerName === ServiceProvider.XAI;
+ const isGLM = modelConfig.providerName === ServiceProvider.GLM;
const isEnabledAccessControl = accessStore.enabledAccessControl();
const apiKey = isGoogle
? accessStore.googleApiKey
@@ -259,6 +264,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
? accessStore.moonshotApiKey
: isXAI
? accessStore.xaiApiKey
+ : isGLM
+ ? accessStore.glmApiKey
: isIflytek
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
@@ -274,6 +281,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
isMoonshot,
isIflytek,
isXAI,
+ isGLM,
apiKey,
isEnabledAccessControl,
};
@@ -338,6 +346,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
return new ClientApi(ModelProvider.Iflytek);
case ServiceProvider.XAI:
return new ClientApi(ModelProvider.XAI);
+ case ServiceProvider.GLM:
+ return new ClientApi(ModelProvider.GLM);
default:
return new ClientApi(ModelProvider.GPT);
}
diff --git a/app/client/platforms/glm.ts b/app/client/platforms/glm.ts
new file mode 100644
index 000000000..b88272ae1
--- /dev/null
+++ b/app/client/platforms/glm.ts
@@ -0,0 +1,192 @@
+"use client";
+import { ApiPath, GLM_BASE_URL, GLM, REQUEST_TIMEOUT_MS } from "@/app/constant";
+import {
+ useAccessStore,
+ useAppConfig,
+ useChatStore,
+ ChatMessageTool,
+ usePluginStore,
+} from "@/app/store";
+import { stream } from "@/app/utils/chat";
+import {
+ ChatOptions,
+ getHeaders,
+ LLMApi,
+ LLMModel,
+ SpeechOptions,
+} from "../api";
+import { getClientConfig } from "@/app/config/client";
+import { getMessageTextContent } from "@/app/utils";
+import { RequestPayload } from "./openai";
+import { fetch } from "@/app/utils/stream";
+
+export class GLMApi implements LLMApi {
+ private disableListModels = true;
+
+ path(path: string): string {
+ const accessStore = useAccessStore.getState();
+
+ let baseUrl = "";
+
+ if (accessStore.useCustomConfig) {
+ baseUrl = accessStore.glmUrl;
+ }
+
+ if (baseUrl.length === 0) {
+ const isApp = !!getClientConfig()?.isApp;
+ const apiPath = ApiPath.GLM;
+ baseUrl = isApp ? GLM_BASE_URL : apiPath;
+ }
+
+ if (baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.slice(0, baseUrl.length - 1);
+ }
+ if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.GLM)) {
+ baseUrl = "https://" + baseUrl;
+ }
+
+ console.log("[Proxy Endpoint] ", baseUrl, path);
+
+ return [baseUrl, path].join("/");
+ }
+
+ extractMessage(res: any) {
+ return res.choices?.at(0)?.message?.content ?? "";
+ }
+
+ speech(options: SpeechOptions): Promise {
+ throw new Error("Method not implemented.");
+ }
+
+ async chat(options: ChatOptions) {
+ const messages: ChatOptions["messages"] = [];
+ for (const v of options.messages) {
+ const content = getMessageTextContent(v);
+ messages.push({ role: v.role, content });
+ }
+
+ const modelConfig = {
+ ...useAppConfig.getState().modelConfig,
+ ...useChatStore.getState().currentSession().mask.modelConfig,
+ ...{
+ model: options.config.model,
+ providerName: options.config.providerName,
+ },
+ };
+
+ const requestPayload: RequestPayload = {
+ messages,
+ 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,
+ };
+
+ console.log("[Request] glm payload: ", requestPayload);
+
+ const shouldStream = !!options.config.stream;
+ const controller = new AbortController();
+ options.onController?.(controller);
+
+ try {
+ const chatPath = this.path(GLM.ChatPath);
+ const chatPayload = {
+ method: "POST",
+ body: JSON.stringify(requestPayload),
+ signal: controller.signal,
+ headers: getHeaders(),
+ };
+
+ // make a fetch request
+ const requestTimeoutId = setTimeout(
+ () => controller.abort(),
+ REQUEST_TIMEOUT_MS,
+ );
+
+ if (shouldStream) {
+ const [tools, funcs] = usePluginStore
+ .getState()
+ .getAsTools(
+ useChatStore.getState().currentSession().mask?.plugin || [],
+ );
+ return stream(
+ chatPath,
+ requestPayload,
+ getHeaders(),
+ tools as any,
+ funcs,
+ controller,
+ // parseSSE
+ (text: string, runTools: ChatMessageTool[]) => {
+ // console.log("parseSSE", text, runTools);
+ const json = JSON.parse(text);
+ const choices = json.choices as Array<{
+ delta: {
+ content: string;
+ tool_calls: ChatMessageTool[];
+ };
+ }>;
+ const tool_calls = choices[0]?.delta?.tool_calls;
+ if (tool_calls?.length > 0) {
+ const index = tool_calls[0]?.index;
+ const id = tool_calls[0]?.id;
+ const args = tool_calls[0]?.function?.arguments;
+ if (id) {
+ runTools.push({
+ id,
+ type: tool_calls[0]?.type,
+ function: {
+ name: tool_calls[0]?.function?.name as string,
+ arguments: args,
+ },
+ });
+ } else {
+ // @ts-ignore
+ runTools[index]["function"]["arguments"] += args;
+ }
+ }
+ return choices[0]?.delta?.content;
+ },
+ // processToolMessage, include tool_calls message and tool call results
+ (
+ requestPayload: RequestPayload,
+ toolCallMessage: any,
+ toolCallResult: any[],
+ ) => {
+ // @ts-ignore
+ requestPayload?.messages?.splice(
+ // @ts-ignore
+ requestPayload?.messages?.length,
+ 0,
+ toolCallMessage,
+ ...toolCallResult,
+ );
+ },
+ options,
+ );
+ } else {
+ const res = await fetch(chatPath, chatPayload);
+ clearTimeout(requestTimeoutId);
+
+ const resJson = await res.json();
+ const message = this.extractMessage(resJson);
+ options.onFinish(message);
+ }
+ } catch (e) {
+ console.log("[Request] failed to make a chat request", e);
+ options.onError?.(e as Error);
+ }
+ }
+ async usage() {
+ return {
+ used: 0,
+ total: 0,
+ };
+ }
+
+ async models(): Promise {
+ return [];
+ }
+}
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index 666caece8..e5859e716 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -72,6 +72,7 @@ import {
Stability,
Iflytek,
SAAS_CHAT_URL,
+ GLM,
} from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
import { ErrorBoundary } from "./error";
@@ -1234,6 +1235,45 @@ export function Settings() {
>
);
+ const glmConfigComponent = accessStore.provider === ServiceProvider.GLM && (
+ <>
+
+
+ accessStore.update(
+ (access) => (access.glmUrl = e.currentTarget.value),
+ )
+ }
+ >
+
+
+ {
+ accessStore.update(
+ (access) => (access.glmApiKey = e.currentTarget.value),
+ );
+ }}
+ />
+
+ >
+ );
+
const stabilityConfigComponent = accessStore.provider ===
ServiceProvider.Stability && (
<>
@@ -1693,6 +1733,7 @@ export function Settings() {
{stabilityConfigComponent}
{lflytekConfigComponent}
{XAIConfigComponent}
+ {glmConfigComponent}
>
)}
>
diff --git a/app/config/server.ts b/app/config/server.ts
index eac4ba0cf..b9a68ce4d 100644
--- a/app/config/server.ts
+++ b/app/config/server.ts
@@ -75,6 +75,10 @@ declare global {
XAI_URL?: string;
XAI_API_KEY?: string;
+ // glm only
+ GLM_URL?: string;
+ GLM_API_KEY?: string;
+
// custom template for preprocessing user input
DEFAULT_INPUT_TEMPLATE?: string;
}
@@ -151,6 +155,7 @@ export const getServerSideConfig = () => {
const isMoonshot = !!process.env.MOONSHOT_API_KEY;
const isIflytek = !!process.env.IFLYTEK_API_KEY;
const isXAI = !!process.env.XAI_API_KEY;
+ const isGLM = !!process.env.GLM_API_KEY;
// const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
// const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
// const randomIndex = Math.floor(Math.random() * apiKeys.length);
@@ -217,6 +222,10 @@ export const getServerSideConfig = () => {
xaiUrl: process.env.XAI_URL,
xaiApiKey: getApiKey(process.env.XAI_API_KEY),
+ isGLM,
+ glmUrl: process.env.GLM_URL,
+ glmApiKey: getApiKey(process.env.GLM_API_KEY),
+
cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID,
cloudflareKVNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID,
cloudflareKVApiKey: getApiKey(process.env.CLOUDFLARE_KV_API_KEY),
diff --git a/app/constant.ts b/app/constant.ts
index 9774bb594..d58bc3935 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -30,6 +30,8 @@ export const IFLYTEK_BASE_URL = "https://spark-api-open.xf-yun.com";
export const XAI_BASE_URL = "https://api.x.ai";
+export const GLM_BASE_URL = "https://open.bigmodel.cn";
+
export const CACHE_URL_PREFIX = "/api/cache";
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
@@ -62,6 +64,7 @@ export enum ApiPath {
Stability = "/api/stability",
Artifacts = "/api/artifacts",
XAI = "/api/xai",
+ GLM = "/api/glm",
}
export enum SlotID {
@@ -115,6 +118,7 @@ export enum ServiceProvider {
Stability = "Stability",
Iflytek = "Iflytek",
XAI = "XAI",
+ GLM = "GLM",
}
// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
@@ -138,6 +142,7 @@ export enum ModelProvider {
Moonshot = "Moonshot",
Iflytek = "Iflytek",
XAI = "XAI",
+ GLM = "GLM",
}
export const Stability = {
@@ -225,6 +230,11 @@ export const XAI = {
ChatPath: "v1/chat/completions",
};
+export const GLM = {
+ ExampleEndpoint: GLM_BASE_URL,
+ ChatPath: "/api/paas/v4/chat/completions",
+};
+
export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang
// export const DEFAULT_SYSTEM_TEMPLATE = `
// You are ChatGPT, a large language model trained by {{ServiceProvider}}.
@@ -376,6 +386,17 @@ const iflytekModels = [
const xAIModes = ["grok-beta"];
+const glmModels = [
+ "glm-4-plus",
+ "glm-4-0520",
+ "glm-4",
+ "glm-4-air",
+ "glm-4-airx",
+ "glm-4-long",
+ "glm-4-flashx",
+ "glm-4-flash",
+];
+
let seq = 1000; // 内置的模型序号生成器从1000开始
export const DEFAULT_MODELS = [
...openaiModels.map((name) => ({
@@ -499,6 +520,17 @@ export const DEFAULT_MODELS = [
sorted: 11,
},
})),
+ ...glmModels.map((name) => ({
+ name,
+ available: true,
+ sorted: seq++,
+ provider: {
+ id: "glm",
+ providerName: "GLM",
+ providerType: "glm",
+ sorted: 12,
+ },
+ })),
] as const;
export const CHAT_PAGE_SIZE = 15;
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 006fc8162..92aaf6228 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -473,6 +473,17 @@ const cn = {
SubTitle: "样例:",
},
},
+ GLM: {
+ ApiKey: {
+ Title: "接口密钥",
+ SubTitle: "使用自定义 GLM API Key",
+ Placeholder: "GLM API Key",
+ },
+ Endpoint: {
+ Title: "接口地址",
+ SubTitle: "样例:",
+ },
+ },
Stability: {
ApiKey: {
Title: "接口密钥",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 7204bd946..d691925c4 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -457,6 +457,17 @@ const en: LocaleType = {
SubTitle: "Example: ",
},
},
+ GLM: {
+ ApiKey: {
+ Title: "GLM API Key",
+ SubTitle: "Use a custom GLM API Key",
+ Placeholder: "GLM API Key",
+ },
+ Endpoint: {
+ Title: "Endpoint Address",
+ SubTitle: "Example: ",
+ },
+ },
Stability: {
ApiKey: {
Title: "Stability API Key",
diff --git a/app/store/access.ts b/app/store/access.ts
index b3d412a2d..9cc420fdf 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -14,6 +14,7 @@ import {
STABILITY_BASE_URL,
IFLYTEK_BASE_URL,
XAI_BASE_URL,
+ GLM_BASE_URL,
} from "../constant";
import { getHeaders } from "../client/api";
import { getClientConfig } from "../config/client";
@@ -47,6 +48,8 @@ const DEFAULT_IFLYTEK_URL = isApp ? IFLYTEK_BASE_URL : ApiPath.Iflytek;
const DEFAULT_XAI_URL = isApp ? XAI_BASE_URL : ApiPath.XAI;
+const DEFAULT_GLM_URL = isApp ? GLM_BASE_URL : ApiPath.GLM;
+
const DEFAULT_ACCESS_STATE = {
accessCode: "",
useCustomConfig: false,
@@ -108,6 +111,10 @@ const DEFAULT_ACCESS_STATE = {
xaiUrl: DEFAULT_XAI_URL,
xaiApiKey: "",
+ // glm
+ glmUrl: DEFAULT_GLM_URL,
+ glmApiKey: "",
+
// server config
needCode: true,
hideUserApiKey: false,
@@ -180,6 +187,10 @@ export const useAccessStore = createPersistStore(
return ensure(get(), ["xaiApiKey"]);
},
+ isValidGLM() {
+ return ensure(get(), ["glmApiKey"]);
+ },
+
isAuthorized() {
this.fetch();
@@ -196,6 +207,7 @@ export const useAccessStore = createPersistStore(
this.isValidMoonshot() ||
this.isValidIflytek() ||
this.isValidXAI() ||
+ this.isValidGLM() ||
!this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
);
From 44383a8b331ed283f06213c5176bf60fe98bbcc0 Mon Sep 17 00:00:00 2001
From: Core
Date: Thu, 31 Oct 2024 11:00:45 +0800
Subject: [PATCH 044/236] add claude-3-5-sonnet-latest and
claude-3-opus-latest
add claude-3-5-sonnet-latest and claude-3-opus-latest
---
app/constant.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/constant.ts b/app/constant.ts
index f2021736a..23c164298 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -320,6 +320,8 @@ const anthropicModels = [
"claude-3-haiku-20240307",
"claude-3-5-sonnet-20240620",
"claude-3-5-sonnet-20241022",
+ "claude-3-5-sonnet-latest",
+ "claude-3-opus-latest",
];
const baiduModels = [
From d3f0a77830073684dd8da25e34d5d8eb0a94ecdb Mon Sep 17 00:00:00 2001
From: DDMeaqua
Date: Thu, 31 Oct 2024 11:23:06 +0800
Subject: [PATCH 045/236] chore: update Provider
---
app/constant.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/constant.ts b/app/constant.ts
index d58bc3935..b8b25b7ab 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -118,7 +118,7 @@ export enum ServiceProvider {
Stability = "Stability",
Iflytek = "Iflytek",
XAI = "XAI",
- GLM = "GLM",
+ GLM = "ChatGLM",
}
// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
@@ -142,7 +142,7 @@ export enum ModelProvider {
Moonshot = "Moonshot",
Iflytek = "Iflytek",
XAI = "XAI",
- GLM = "GLM",
+ GLM = "ChatGLM",
}
export const Stability = {
@@ -525,9 +525,9 @@ export const DEFAULT_MODELS = [
available: true,
sorted: seq++,
provider: {
- id: "glm",
- providerName: "GLM",
- providerType: "glm",
+ id: "chatglm",
+ providerName: "ChatGLM",
+ providerType: "chatglm",
sorted: 12,
},
})),
From 7a8d557ea37e9b02fc26d8416fc631f4b7adda56 Mon Sep 17 00:00:00 2001
From: DDMeaqua
Date: Thu, 31 Oct 2024 11:37:19 +0800
Subject: [PATCH 046/236] =?UTF-8?q?chore:=20=E5=BC=80=E5=90=AF=E6=8F=92?=
=?UTF-8?q?=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/utils.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/utils.ts b/app/utils.ts
index d8fc46330..91f11c0c2 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -278,7 +278,8 @@ export function showPlugins(provider: ServiceProvider, model: string) {
if (
provider == ServiceProvider.OpenAI ||
provider == ServiceProvider.Azure ||
- provider == ServiceProvider.Moonshot
+ provider == ServiceProvider.Moonshot ||
+ provider == ServiceProvider.GLM
) {
return true;
}
From afe12c212e51bd2d27c5db5700f881c32a0bd3ba Mon Sep 17 00:00:00 2001
From: DDMeaqua
Date: Fri, 1 Nov 2024 13:53:43 +0800
Subject: [PATCH 047/236] chore: update
---
app/api/[provider]/[...path]/route.ts | 6 ++---
app/api/auth.ts | 4 ++--
app/api/glm.ts | 8 +++----
app/client/api.ts | 18 +++++++--------
app/client/platforms/glm.ts | 19 ++++++++++------
app/components/settings.tsx | 32 ++++++++++++++-------------
app/config/server.ts | 14 ++++++------
app/constant.ts | 16 +++++++-------
app/locales/cn.ts | 6 ++---
app/locales/en.ts | 8 +++----
app/store/access.ts | 16 +++++++-------
app/utils.ts | 2 +-
12 files changed, 78 insertions(+), 71 deletions(-)
diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts
index 78836cc52..3017fd371 100644
--- a/app/api/[provider]/[...path]/route.ts
+++ b/app/api/[provider]/[...path]/route.ts
@@ -11,7 +11,7 @@ import { handle as moonshotHandler } from "../../moonshot";
import { handle as stabilityHandler } from "../../stability";
import { handle as iflytekHandler } from "../../iflytek";
import { handle as xaiHandler } from "../../xai";
-import { handle as glmHandler } from "../../glm";
+import { handle as chatglmHandler } from "../../glm";
import { handle as proxyHandler } from "../../proxy";
async function handle(
@@ -42,8 +42,8 @@ async function handle(
return iflytekHandler(req, { params });
case ApiPath.XAI:
return xaiHandler(req, { params });
- case ApiPath.GLM:
- return glmHandler(req, { params });
+ case ApiPath.ChatGLM:
+ return chatglmHandler(req, { params });
case ApiPath.OpenAI:
return openaiHandler(req, { params });
default:
diff --git a/app/api/auth.ts b/app/api/auth.ts
index db920fc28..6703b64bd 100644
--- a/app/api/auth.ts
+++ b/app/api/auth.ts
@@ -95,8 +95,8 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
case ModelProvider.XAI:
systemApiKey = serverConfig.xaiApiKey;
break;
- case ModelProvider.GLM:
- systemApiKey = serverConfig.glmApiKey;
+ case ModelProvider.ChatGLM:
+ systemApiKey = serverConfig.chatglmApiKey;
break;
case ModelProvider.GPT:
default:
diff --git a/app/api/glm.ts b/app/api/glm.ts
index d40c4b6a8..ea7a766bd 100644
--- a/app/api/glm.ts
+++ b/app/api/glm.ts
@@ -1,6 +1,6 @@
import { getServerSideConfig } from "@/app/config/server";
import {
- GLM_BASE_URL,
+ CHATGLM_BASE_URL,
ApiPath,
ModelProvider,
ServiceProvider,
@@ -42,9 +42,9 @@ async function request(req: NextRequest) {
const controller = new AbortController();
// alibaba use base url or just remove the path
- let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.GLM, "");
+ let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.ChatGLM, "");
- let baseUrl = serverConfig.glmUrl || GLM_BASE_URL;
+ let baseUrl = serverConfig.chatglmUrl || CHATGLM_BASE_URL;
if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`;
@@ -92,7 +92,7 @@ async function request(req: NextRequest) {
isModelAvailableInServer(
serverConfig.customModels,
jsonBody?.model as string,
- ServiceProvider.GLM as string,
+ ServiceProvider.ChatGLM as string,
)
) {
return NextResponse.json(
diff --git a/app/client/api.ts b/app/client/api.ts
index 4082d085c..8fecf841f 100644
--- a/app/client/api.ts
+++ b/app/client/api.ts
@@ -21,7 +21,7 @@ import { HunyuanApi } from "./platforms/tencent";
import { MoonshotApi } from "./platforms/moonshot";
import { SparkApi } from "./platforms/iflytek";
import { XAIApi } from "./platforms/xai";
-import { GLMApi } from "./platforms/glm";
+import { ChatGLMApi } from "./platforms/glm";
export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
@@ -157,8 +157,8 @@ export class ClientApi {
case ModelProvider.XAI:
this.llm = new XAIApi();
break;
- case ModelProvider.GLM:
- this.llm = new GLMApi();
+ case ModelProvider.ChatGLM:
+ this.llm = new ChatGLMApi();
break;
default:
this.llm = new ChatGPTApi();
@@ -248,7 +248,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
const isMoonshot = modelConfig.providerName === ServiceProvider.Moonshot;
const isIflytek = modelConfig.providerName === ServiceProvider.Iflytek;
const isXAI = modelConfig.providerName === ServiceProvider.XAI;
- const isGLM = modelConfig.providerName === ServiceProvider.GLM;
+ const isChatGLM = modelConfig.providerName === ServiceProvider.ChatGLM;
const isEnabledAccessControl = accessStore.enabledAccessControl();
const apiKey = isGoogle
? accessStore.googleApiKey
@@ -264,8 +264,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
? accessStore.moonshotApiKey
: isXAI
? accessStore.xaiApiKey
- : isGLM
- ? accessStore.glmApiKey
+ : isChatGLM
+ ? accessStore.chatglmApiKey
: isIflytek
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
@@ -281,7 +281,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
isMoonshot,
isIflytek,
isXAI,
- isGLM,
+ isChatGLM,
apiKey,
isEnabledAccessControl,
};
@@ -346,8 +346,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
return new ClientApi(ModelProvider.Iflytek);
case ServiceProvider.XAI:
return new ClientApi(ModelProvider.XAI);
- case ServiceProvider.GLM:
- return new ClientApi(ModelProvider.GLM);
+ case ServiceProvider.ChatGLM:
+ return new ClientApi(ModelProvider.ChatGLM);
default:
return new ClientApi(ModelProvider.GPT);
}
diff --git a/app/client/platforms/glm.ts b/app/client/platforms/glm.ts
index b88272ae1..10696ee82 100644
--- a/app/client/platforms/glm.ts
+++ b/app/client/platforms/glm.ts
@@ -1,5 +1,10 @@
"use client";
-import { ApiPath, GLM_BASE_URL, GLM, REQUEST_TIMEOUT_MS } from "@/app/constant";
+import {
+ ApiPath,
+ CHATGLM_BASE_URL,
+ ChatGLM,
+ REQUEST_TIMEOUT_MS,
+} from "@/app/constant";
import {
useAccessStore,
useAppConfig,
@@ -20,7 +25,7 @@ import { getMessageTextContent } from "@/app/utils";
import { RequestPayload } from "./openai";
import { fetch } from "@/app/utils/stream";
-export class GLMApi implements LLMApi {
+export class ChatGLMApi implements LLMApi {
private disableListModels = true;
path(path: string): string {
@@ -29,19 +34,19 @@ export class GLMApi implements LLMApi {
let baseUrl = "";
if (accessStore.useCustomConfig) {
- baseUrl = accessStore.glmUrl;
+ baseUrl = accessStore.chatglmUrl;
}
if (baseUrl.length === 0) {
const isApp = !!getClientConfig()?.isApp;
- const apiPath = ApiPath.GLM;
- baseUrl = isApp ? GLM_BASE_URL : apiPath;
+ const apiPath = ApiPath.ChatGLM;
+ baseUrl = isApp ? CHATGLM_BASE_URL : apiPath;
}
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, baseUrl.length - 1);
}
- if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.GLM)) {
+ if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.ChatGLM)) {
baseUrl = "https://" + baseUrl;
}
@@ -91,7 +96,7 @@ export class GLMApi implements LLMApi {
options.onController?.(controller);
try {
- const chatPath = this.path(GLM.ChatPath);
+ const chatPath = this.path(ChatGLM.ChatPath);
const chatPayload = {
method: "POST",
body: JSON.stringify(requestPayload),
diff --git a/app/components/settings.tsx b/app/components/settings.tsx
index e5859e716..e2666b551 100644
--- a/app/components/settings.tsx
+++ b/app/components/settings.tsx
@@ -72,7 +72,7 @@ import {
Stability,
Iflytek,
SAAS_CHAT_URL,
- GLM,
+ ChatGLM,
} from "../constant";
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
import { ErrorBoundary } from "./error";
@@ -1235,38 +1235,40 @@ export function Settings() {
>
);
- const glmConfigComponent = accessStore.provider === ServiceProvider.GLM && (
+ const chatglmConfigComponent = accessStore.provider ===
+ ServiceProvider.ChatGLM && (
<>
accessStore.update(
- (access) => (access.glmUrl = e.currentTarget.value),
+ (access) => (access.chatglmUrl = e.currentTarget.value),
)
}
>
{
accessStore.update(
- (access) => (access.glmApiKey = e.currentTarget.value),
+ (access) => (access.chatglmApiKey = e.currentTarget.value),
);
}}
/>
@@ -1733,7 +1735,7 @@ export function Settings() {
{stabilityConfigComponent}
{lflytekConfigComponent}
{XAIConfigComponent}
- {glmConfigComponent}
+ {chatglmConfigComponent}
>
)}
>
diff --git a/app/config/server.ts b/app/config/server.ts
index b9a68ce4d..485f950da 100644
--- a/app/config/server.ts
+++ b/app/config/server.ts
@@ -75,9 +75,9 @@ declare global {
XAI_URL?: string;
XAI_API_KEY?: string;
- // glm only
- GLM_URL?: string;
- GLM_API_KEY?: string;
+ // chatglm only
+ CHATGLM_URL?: string;
+ CHATGLM_API_KEY?: string;
// custom template for preprocessing user input
DEFAULT_INPUT_TEMPLATE?: string;
@@ -155,7 +155,7 @@ export const getServerSideConfig = () => {
const isMoonshot = !!process.env.MOONSHOT_API_KEY;
const isIflytek = !!process.env.IFLYTEK_API_KEY;
const isXAI = !!process.env.XAI_API_KEY;
- const isGLM = !!process.env.GLM_API_KEY;
+ const isChatGLM = !!process.env.CHATGLM_API_KEY;
// const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? "";
// const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim());
// const randomIndex = Math.floor(Math.random() * apiKeys.length);
@@ -222,9 +222,9 @@ export const getServerSideConfig = () => {
xaiUrl: process.env.XAI_URL,
xaiApiKey: getApiKey(process.env.XAI_API_KEY),
- isGLM,
- glmUrl: process.env.GLM_URL,
- glmApiKey: getApiKey(process.env.GLM_API_KEY),
+ isChatGLM,
+ chatglmUrl: process.env.CHATGLM_URL,
+ chatglmApiKey: getApiKey(process.env.CHATGLM_API_KEY),
cloudflareAccountId: process.env.CLOUDFLARE_ACCOUNT_ID,
cloudflareKVNamespaceId: process.env.CLOUDFLARE_KV_NAMESPACE_ID,
diff --git a/app/constant.ts b/app/constant.ts
index b8b25b7ab..1a84e5c84 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -30,7 +30,7 @@ export const IFLYTEK_BASE_URL = "https://spark-api-open.xf-yun.com";
export const XAI_BASE_URL = "https://api.x.ai";
-export const GLM_BASE_URL = "https://open.bigmodel.cn";
+export const CHATGLM_BASE_URL = "https://open.bigmodel.cn";
export const CACHE_URL_PREFIX = "/api/cache";
export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`;
@@ -64,7 +64,7 @@ export enum ApiPath {
Stability = "/api/stability",
Artifacts = "/api/artifacts",
XAI = "/api/xai",
- GLM = "/api/glm",
+ ChatGLM = "/api/chatglm",
}
export enum SlotID {
@@ -118,7 +118,7 @@ export enum ServiceProvider {
Stability = "Stability",
Iflytek = "Iflytek",
XAI = "XAI",
- GLM = "ChatGLM",
+ ChatGLM = "ChatGLM",
}
// Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings
@@ -142,7 +142,7 @@ export enum ModelProvider {
Moonshot = "Moonshot",
Iflytek = "Iflytek",
XAI = "XAI",
- GLM = "ChatGLM",
+ ChatGLM = "ChatGLM",
}
export const Stability = {
@@ -230,8 +230,8 @@ export const XAI = {
ChatPath: "v1/chat/completions",
};
-export const GLM = {
- ExampleEndpoint: GLM_BASE_URL,
+export const ChatGLM = {
+ ExampleEndpoint: CHATGLM_BASE_URL,
ChatPath: "/api/paas/v4/chat/completions",
};
@@ -386,7 +386,7 @@ const iflytekModels = [
const xAIModes = ["grok-beta"];
-const glmModels = [
+const chatglmModels = [
"glm-4-plus",
"glm-4-0520",
"glm-4",
@@ -520,7 +520,7 @@ export const DEFAULT_MODELS = [
sorted: 11,
},
})),
- ...glmModels.map((name) => ({
+ ...chatglmModels.map((name) => ({
name,
available: true,
sorted: seq++,
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 92aaf6228..9712593c6 100644
--- a/app/locales/cn.ts
+++ b/app/locales/cn.ts
@@ -473,11 +473,11 @@ const cn = {
SubTitle: "样例:",
},
},
- GLM: {
+ ChatGLM: {
ApiKey: {
Title: "接口密钥",
- SubTitle: "使用自定义 GLM API Key",
- Placeholder: "GLM API Key",
+ SubTitle: "使用自定义 ChatGLM API Key",
+ Placeholder: "ChatGLM API Key",
},
Endpoint: {
Title: "接口地址",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index d691925c4..ac8d3aed2 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -457,11 +457,11 @@ const en: LocaleType = {
SubTitle: "Example: ",
},
},
- GLM: {
+ ChatGLM: {
ApiKey: {
- Title: "GLM API Key",
- SubTitle: "Use a custom GLM API Key",
- Placeholder: "GLM API Key",
+ Title: "ChatGLM API Key",
+ SubTitle: "Use a custom ChatGLM API Key",
+ Placeholder: "ChatGLM API Key",
},
Endpoint: {
Title: "Endpoint Address",
diff --git a/app/store/access.ts b/app/store/access.ts
index 9cc420fdf..3b0e6357b 100644
--- a/app/store/access.ts
+++ b/app/store/access.ts
@@ -14,7 +14,7 @@ import {
STABILITY_BASE_URL,
IFLYTEK_BASE_URL,
XAI_BASE_URL,
- GLM_BASE_URL,
+ CHATGLM_BASE_URL,
} from "../constant";
import { getHeaders } from "../client/api";
import { getClientConfig } from "../config/client";
@@ -48,7 +48,7 @@ const DEFAULT_IFLYTEK_URL = isApp ? IFLYTEK_BASE_URL : ApiPath.Iflytek;
const DEFAULT_XAI_URL = isApp ? XAI_BASE_URL : ApiPath.XAI;
-const DEFAULT_GLM_URL = isApp ? GLM_BASE_URL : ApiPath.GLM;
+const DEFAULT_CHATGLM_URL = isApp ? CHATGLM_BASE_URL : ApiPath.ChatGLM;
const DEFAULT_ACCESS_STATE = {
accessCode: "",
@@ -111,9 +111,9 @@ const DEFAULT_ACCESS_STATE = {
xaiUrl: DEFAULT_XAI_URL,
xaiApiKey: "",
- // glm
- glmUrl: DEFAULT_GLM_URL,
- glmApiKey: "",
+ // chatglm
+ chatglmUrl: DEFAULT_CHATGLM_URL,
+ chatglmApiKey: "",
// server config
needCode: true,
@@ -187,8 +187,8 @@ export const useAccessStore = createPersistStore(
return ensure(get(), ["xaiApiKey"]);
},
- isValidGLM() {
- return ensure(get(), ["glmApiKey"]);
+ isValidChatGLM() {
+ return ensure(get(), ["chatglmApiKey"]);
},
isAuthorized() {
@@ -207,7 +207,7 @@ export const useAccessStore = createPersistStore(
this.isValidMoonshot() ||
this.isValidIflytek() ||
this.isValidXAI() ||
- this.isValidGLM() ||
+ this.isValidChatGLM() ||
!this.enabledAccessControl() ||
(this.enabledAccessControl() && ensure(get(), ["accessCode"]))
);
diff --git a/app/utils.ts b/app/utils.ts
index 91f11c0c2..c444f8ef4 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -279,7 +279,7 @@ export function showPlugins(provider: ServiceProvider, model: string) {
provider == ServiceProvider.OpenAI ||
provider == ServiceProvider.Azure ||
provider == ServiceProvider.Moonshot ||
- provider == ServiceProvider.GLM
+ provider == ServiceProvider.ChatGLM
) {
return true;
}
From 4d75b23ed1b41a042e28805e46ad2b5c8111cc3d Mon Sep 17 00:00:00 2001
From: DDMeaqua
Date: Fri, 1 Nov 2024 14:15:12 +0800
Subject: [PATCH 048/236] fix: ts error
---
app/api/glm.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/api/glm.ts b/app/api/glm.ts
index ea7a766bd..3625b9f7b 100644
--- a/app/api/glm.ts
+++ b/app/api/glm.ts
@@ -22,7 +22,7 @@ export async function handle(
return NextResponse.json({ body: "OK" }, { status: 200 });
}
- const authResult = auth(req, ModelProvider.GLM);
+ const authResult = auth(req, ModelProvider.ChatGLM);
if (authResult.error) {
return NextResponse.json(authResult, {
status: 401,
From 17d5209738a114db34484838c18786924430cc5c Mon Sep 17 00:00:00 2001
From: weige <772752726@qq.com>
Date: Fri, 1 Nov 2024 17:28:20 +0800
Subject: [PATCH 049/236] add bt install doc
---
docs/bt-cn.md | 29 +++++++++++++++++++++++++++++
docs/images/bt/bt-install-1.jpeg | Bin 0 -> 164721 bytes
docs/images/bt/bt-install-2.jpeg | Bin 0 -> 200257 bytes
docs/images/bt/bt-install-3.jpeg | Bin 0 -> 119602 bytes
docs/images/bt/bt-install-4.jpeg | Bin 0 -> 162832 bytes
docs/images/bt/bt-install-5.jpeg | Bin 0 -> 75420 bytes
docs/images/bt/bt-install-6.jpeg | Bin 0 -> 149223 bytes
7 files changed, 29 insertions(+)
create mode 100644 docs/bt-cn.md
create mode 100644 docs/images/bt/bt-install-1.jpeg
create mode 100644 docs/images/bt/bt-install-2.jpeg
create mode 100644 docs/images/bt/bt-install-3.jpeg
create mode 100644 docs/images/bt/bt-install-4.jpeg
create mode 100644 docs/images/bt/bt-install-5.jpeg
create mode 100644 docs/images/bt/bt-install-6.jpeg
diff --git a/docs/bt-cn.md b/docs/bt-cn.md
new file mode 100644
index 000000000..7fe946db0
--- /dev/null
+++ b/docs/bt-cn.md
@@ -0,0 +1,29 @@
+# 宝塔面板 的部署说明
+
+## 拥有自己的宝塔
+当你需要通过 宝塔面板 部署本项目之前,需要在服务器上先安装好 宝塔面板工具。 接下来的 部署流程 都建立在已有宝塔面板的前提下。宝塔安装请参考 ([宝塔官网](https://www.bt.cn/new/download.html))
+
+> 注意:本项目需要宝塔面板版本 9.2.0 以上
+
+## 一键安装
+
+1. 在 宝塔面板 -> Docker -> 应用商店 页面,搜索 ChatGPT-Next-Web 找到本项目的docker应用;
+2. 点击 安装 开始部署本项目
+
+
+1. 在项目配置页,根据要求开始配置环境变量;
+2. 如勾选 允许外部访问 配置,请注意为配置的 web端口 开放安全组端口访问权限;
+3. 请确保你添加了正确的 Open Api Key,否则无法使用;当配置 OpenAI官方 提供的key(国内无法访问),请配置代理地址;
+4. 建议配置 访问权限密码,否则部署后所有人均可使用已配置的 Open Api Key(当允许外部访问时);
+5. 点击 确认 开始自动部署。
+
+## 如何访问
+
+通过根据服务器IP地址和配置的web端口(http://host:port),在浏览器中打开 ChatGPT-Next-Web。
+
+
+若配置了 访问权限密码,访问大模型前需要登录,请点击 登录,获取访问权限。
+
+
+
+
diff --git a/docs/images/bt/bt-install-1.jpeg b/docs/images/bt/bt-install-1.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..fff3406d6564350f89d0b72b60408731f7f3404f
GIT binary patch
literal 164721
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