From d49ececcc58bf125bfad4deb4dbb161fd5f2339b Mon Sep 17 00:00:00 2001 From: frostime Date: Mon, 29 Jul 2024 19:10:00 +0800 Subject: [PATCH 01/20] =?UTF-8?q?=E2=9C=A8=20feat(markdown):=20wrap=20the?= =?UTF-8?q?=20plain=20text=20code=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/markdown.tsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index fc8df6fe3..c12daed64 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -96,6 +96,32 @@ export function PreCode(props: { children: any }) { [plugins], ); + //Wrap the paragraph for plain-text + useEffect(() => { + if (ref.current) { + const codeElements = ref.current.querySelectorAll( + "code", + ) as NodeListOf; + const wrapLanguages = [ + "", + "md", + "markdown", + "text", + "txt", + "plaintext", + "tex", + "latex", + ]; + codeElements.forEach((codeElement) => { + let languageClass = codeElement.className.match(/language-(\w+)/); + let name = languageClass ? languageClass[1] : ""; + if (wrapLanguages.includes(name)) { + codeElement.style.whiteSpace = "pre-wrap"; + } + }); + } + }, []); + return ( <>

From 8cb72d84526343146eca3a0f608b111d884321f1 Mon Sep 17 00:00:00 2001
From: Dogtiti <499960698@qq.com>
Date: Tue, 30 Jul 2024 21:25:33 +0800
Subject: [PATCH 02/20] fix: locales => Locale

---
 app/components/sd/sd.tsx | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/app/components/sd/sd.tsx b/app/components/sd/sd.tsx
index f35d0e0cd..4f5f86960 100644
--- a/app/components/sd/sd.tsx
+++ b/app/components/sd/sd.tsx
@@ -23,7 +23,6 @@ import CopyIcon from "@/app/icons/copy.svg";
 import PromptIcon from "@/app/icons/prompt.svg";
 import ResetIcon from "@/app/icons/reload.svg";
 import { useSdStore } from "@/app/store/sd";
-import locales from "@/app/locales";
 import LoadingIcon from "@/app/icons/three-dots.svg";
 import ErrorIcon from "@/app/icons/delete.svg";
 import SDIcon from "@/app/icons/sd.svg";
@@ -64,14 +63,14 @@ function getSdTaskStatus(item: any) {
   return (
     

- {locales.Sd.Status.Name}: {s} + {Locale.Sd.Status.Name}: {s} {item.status === "error" && ( { showModal({ - title: locales.Sd.Detail, + title: Locale.Sd.Detail, children: (

{item.error} @@ -189,13 +188,13 @@ export function Sd() { className={styles["sd-img-item-info"]} >

- {locales.SdPanel.Prompt}:{" "} + {Locale.SdPanel.Prompt}:{" "} { showModal({ - title: locales.Sd.Detail, + title: Locale.Sd.Detail, children: (

{item.params.prompt} @@ -208,7 +207,7 @@ export function Sd() {

- {locales.SdPanel.AIModel}: {item.model_name} + {Locale.SdPanel.AIModel}: {item.model_name}

{getSdTaskStatus(item)}

{item.created_at}

@@ -219,7 +218,7 @@ export function Sd() { icon={} onClick={() => { showModal({ - title: locales.Sd.GenerateParams, + title: Locale.Sd.GenerateParams, children: (
{Object.keys(item.params).map((key) => { @@ -325,7 +324,7 @@ export function Sd() { ); }) ) : ( -
{locales.Sd.EmptyRecord}
+
{Locale.Sd.EmptyRecord}
)}
From 99f3160aa26dde331999c0397547ae154e60d7c8 Mon Sep 17 00:00:00 2001 From: Dogtiti <499960698@qq.com> Date: Wed, 31 Jul 2024 22:10:24 +0800 Subject: [PATCH 03/20] fix: sidebar title style --- app/components/home.module.scss | 12 +++++++++--- app/components/sidebar.tsx | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/components/home.module.scss b/app/components/home.module.scss index b836d2bec..b31334568 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -137,12 +137,18 @@ position: relative; padding-top: 20px; padding-bottom: 20px; + display: flex; + justify-content: space-between; + align-items: center; } .sidebar-logo { - position: absolute; - right: 0; - bottom: 18px; + display: inline-flex; +} + +.sidebar-title-container { + display: inline-flex; + flex-direction: column; } .sidebar-title { diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index dbcf7c5db..d8def056e 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -171,10 +171,12 @@ export function SideBarHeader(props: { return (
-
- {title} +
+
+ {title} +
+
{subTitle}
-
{subTitle}
{logo}
{children} From 04a4e1b39afb066fc2ce62cbdf93e793660357c4 Mon Sep 17 00:00:00 2001 From: wuzhiqing Date: Mon, 29 Jul 2024 14:18:10 +0800 Subject: [PATCH 04/20] fix: replace '&' with concurrently for cross-platform compatibility - Used `concurrently` to run parallel commands in package.json scripts - Added `concurrently` as a devDependency in package.json --- package.json | 9 ++--- yarn.lock | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index ed5edb043..70820fdd4 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,14 @@ "license": "mit", "scripts": { "mask": "npx tsx app/masks/build.ts", - "mask:watch": "npx watch 'yarn mask' app/masks", - "dev": "yarn run mask:watch & next dev", + "mask:watch": "npx watch \"yarn mask\" app/masks", + "dev": "concurrently -r \"yarn run mask:watch\" \"next dev\"", "build": "yarn mask && cross-env BUILD_MODE=standalone next build", "start": "next start", "lint": "next lint", "export": "yarn mask && cross-env BUILD_MODE=export BUILD_APP=1 next build", - "export:dev": "yarn mask:watch & cross-env BUILD_MODE=export BUILD_APP=1 next dev", - "app:dev": "yarn mask:watch & yarn tauri dev", + "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 mask && yarn tauri build", "prompts": "node ./scripts/fetch-prompts.mjs", "prepare": "husky install", @@ -53,6 +53,7 @@ "@types/react-dom": "^18.2.7", "@types/react-katex": "^3.0.0", "@types/spark-md5": "^3.0.4", + "concurrently": "^8.2.2", "cross-env": "^7.0.3", "eslint": "^8.49.0", "eslint-config-next": "13.4.19", diff --git a/yarn.lock b/yarn.lock index c323a5c38..068f90dd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1035,6 +1035,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.21.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.18.10", "@babel/template@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" @@ -2269,7 +2276,7 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.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== @@ -2335,6 +2342,15 @@ client-only@0.0.1: resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -2394,6 +2410,21 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concurrently@^8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-8.2.2.tgz#353141985c198cfa5e4a3ef90082c336b5851784" + integrity sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg== + dependencies: + chalk "^4.1.2" + date-fns "^2.30.0" + lodash "^4.17.21" + rxjs "^7.8.1" + shell-quote "^1.8.1" + spawn-command "0.0.2" + supports-color "^8.1.1" + tree-kill "^1.2.2" + yargs "^17.7.2" + convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" @@ -2801,6 +2832,13 @@ data-uri-to-buffer@^4.0.0: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== +date-fns@^2.30.0: + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" + dayjs@^1.11.7: version "1.11.7" resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" @@ -3562,6 +3600,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" @@ -5480,6 +5523,11 @@ remark-rehype@^10.0.0: mdast-util-to-hast "^12.1.0" unified "^10.0.0" +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -5557,6 +5605,13 @@ rxjs@^7.8.0: dependencies: tslib "^2.1.0" +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + sade@^1.7.3: version "1.8.1" resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" @@ -5639,6 +5694,11 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +shell-quote@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -5717,6 +5777,11 @@ spark-md5@^3.0.2: resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== +spawn-command@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e" + integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -5739,7 +5804,7 @@ string-argv@^0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5860,7 +5925,7 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: +supports-color@^8.0.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -5956,6 +6021,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + trim-lines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" @@ -6355,6 +6425,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -6375,6 +6450,24 @@ yaml@^2.2.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 753bf3b924efe5814e426e69f6963bd6581f65ea Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Fri, 2 Aug 2024 15:55:26 +0800 Subject: [PATCH 05/20] hotfix for cf deploy error #5168 --- app/api/tencent/route.ts | 2 +- app/utils/tencent.ts | 9 ++++++--- package.json | 1 + yarn.lock | 15 ++++++++++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/api/tencent/route.ts b/app/api/tencent/route.ts index d506d1016..885909e7a 100644 --- a/app/api/tencent/route.ts +++ b/app/api/tencent/route.ts @@ -43,7 +43,7 @@ async function handle( export const GET = handle; export const POST = handle; -export const runtime = "nodejs"; +export const runtime = "edge"; export const preferredRegion = [ "arn1", "bom1", diff --git a/app/utils/tencent.ts b/app/utils/tencent.ts index 019d330b6..f0cdd21ee 100644 --- a/app/utils/tencent.ts +++ b/app/utils/tencent.ts @@ -1,14 +1,17 @@ -import { createHash, createHmac } from "node:crypto"; +import hash from "hash.js"; + // 使用 SHA-256 和 secret 进行 HMAC 加密 function sha256(message: any, secret = "", encoding?: string) { - return createHmac("sha256", secret) + return hash + .hmac(hash.sha256 as any, secret) .update(message) .digest(encoding as any); } // 使用 SHA-256 进行哈希 function getHash(message: any, encoding = "hex") { - return createHash("sha256") + return hash + .sha256() .update(message) .digest(encoding as any); } diff --git a/package.json b/package.json index ed5edb043..652d4c876 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@vercel/speed-insights": "^1.0.2", "emoji-picker-react": "^4.9.2", "fuse.js": "^7.0.0", + "hash.js": "^1.1.7", "heic2any": "^0.0.4", "html-to-image": "^1.11.11", "mermaid": "^10.6.1", diff --git a/yarn.lock b/yarn.lock index c323a5c38..ad3b4b4c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3744,6 +3744,14 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hash.js@^1.1.7: + version "1.1.7" + resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + hast-util-from-dom@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz#25836ddecc3cc0849d32749c2a7aec03e94b59a7" @@ -3907,7 +3915,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4899,6 +4907,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" From ac599aa47c49bde2d557a6f7317347c940b29b17 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Fri, 2 Aug 2024 18:00:42 +0800 Subject: [PATCH 06/20] add dalle3 model --- app/client/platforms/openai.ts | 90 ++++++++++++++++++++++++---------- app/components/chat.tsx | 34 +++++++++++++ app/constant.ts | 7 ++- app/store/chat.ts | 5 ++ app/utils.ts | 4 ++ 5 files changed, 113 insertions(+), 27 deletions(-) diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 680125fe6..28de30051 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -33,6 +33,7 @@ import { getMessageTextContent, getMessageImages, isVisionModel, + isDalle3 as _isDalle3, } from "@/app/utils"; export interface OpenAIListModelResponse { @@ -58,6 +59,13 @@ export interface RequestPayload { max_tokens?: number; } +export interface DalleRequestPayload { + model: string; + prompt: string; + n: number; + size: "1024x1024" | "1792x1024" | "1024x1792"; +} + export class ChatGPTApi implements LLMApi { private disableListModels = true; @@ -101,19 +109,25 @@ export class ChatGPTApi implements LLMApi { } extractMessage(res: any) { + if (res.error) { + return "```\n" + JSON.stringify(res, null, 4) + "\n```"; + } + // dalle3 model return url, just return + if (res.data) { + const url = res.data?.at(0)?.url ?? ""; + return [ + { + type: "image_url", + image_url: { + url, + }, + }, + ]; + } return res.choices?.at(0)?.message?.content ?? ""; } async chat(options: ChatOptions) { - const visionModel = isVisionModel(options.config.model); - const messages: ChatOptions["messages"] = []; - for (const v of options.messages) { - const content = visionModel - ? await preProcessImageContent(v.content) - : getMessageTextContent(v); - messages.push({ role: v.role, content }); - } - const modelConfig = { ...useAppConfig.getState().modelConfig, ...useChatStore.getState().currentSession().mask.modelConfig, @@ -123,26 +137,48 @@ export class ChatGPTApi implements LLMApi { }, }; - 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. - }; + let requestPayload: RequestPayload | DalleRequestPayload; - // add max_tokens to vision model - if (visionModel && modelConfig.model.includes("preview")) { - requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000); + const isDalle3 = _isDalle3(options.config.model); + if (isDalle3) { + const prompt = getMessageTextContent(options.messages.slice(-1)?.pop()); + requestPayload = { + model: options.config.model, + prompt, + n: 1, + size: options.config?.size ?? "1024x1024", + }; + } else { + const visionModel = isVisionModel(options.config.model); + const messages: ChatOptions["messages"] = []; + for (const v of options.messages) { + const content = visionModel + ? await preProcessImageContent(v.content) + : getMessageTextContent(v); + messages.push({ role: v.role, content }); + } + + 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. + }; + + // add max_tokens to vision model + if (visionModel && modelConfig.model.includes("preview")) { + requestPayload["max_tokens"] = Math.max(modelConfig.max_tokens, 4000); + } } console.log("[Request] openai payload: ", requestPayload); - const shouldStream = !!options.config.stream; + const shouldStream = !isDalle3 && !!options.config.stream; const controller = new AbortController(); options.onController?.(controller); @@ -168,13 +204,15 @@ export class ChatGPTApi implements LLMApi { model?.provider?.providerName === ServiceProvider.Azure, ); chatPath = this.path( - Azure.ChatPath( + (isDalle3 ? Azure.ImagePath : Azure.ChatPath)( (model?.displayName ?? model?.name) as string, useCustomConfig ? useAccessStore.getState().azureApiVersion : "", ), ); } else { - chatPath = this.path(OpenaiPath.ChatPath); + chatPath = this.path( + isDalle3 ? OpenaiPath.ImagePath : OpenaiPath.ChatPath, + ); } const chatPayload = { method: "POST", diff --git a/app/components/chat.tsx b/app/components/chat.tsx index bb4b611ad..b95e85d45 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -37,6 +37,7 @@ import AutoIcon from "../icons/auto.svg"; import BottomIcon from "../icons/bottom.svg"; import StopIcon from "../icons/pause.svg"; import RobotIcon from "../icons/robot.svg"; +import SizeIcon from "../icons/size.svg"; import PluginIcon from "../icons/plugin.svg"; import { @@ -60,6 +61,7 @@ import { getMessageTextContent, getMessageImages, isVisionModel, + isDalle3, } from "../utils"; import { uploadImage as uploadImageRemote } from "@/app/utils/chat"; @@ -481,6 +483,11 @@ export function ChatActions(props: { const [showPluginSelector, setShowPluginSelector] = useState(false); const [showUploadImage, setShowUploadImage] = useState(false); + const [showSizeSelector, setShowSizeSelector] = useState(false); + const dalle3Sizes = ["1024x1024", "1792x1024", "1024x1792"]; + const currentSize = + chatStore.currentSession().mask.modelConfig?.size || "1024x1024"; + useEffect(() => { const show = isVisionModel(currentModel); setShowUploadImage(show); @@ -624,6 +631,33 @@ export function ChatActions(props: { /> )} + {isDalle3(currentModel) && ( + setShowSizeSelector(true)} + text={currentSize} + icon={} + /> + )} + + {showSizeSelector && ( + ({ + title: m, + value: m, + }))} + onClose={() => setShowSizeSelector(false)} + onSelection={(s) => { + if (s.length === 0) return; + const size = s[0]; + chatStore.updateCurrentSession((session) => { + session.mask.modelConfig.size = size; + }); + showToast(size); + }} + /> + )} + setShowPluginSelector(true)} text={Locale.Plugin.Name} diff --git a/app/constant.ts b/app/constant.ts index 5251b5b4f..b777872c8 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -146,6 +146,7 @@ export const Anthropic = { export const OpenaiPath = { ChatPath: "v1/chat/completions", + ImagePath: "v1/images/generations", UsagePath: "dashboard/billing/usage", SubsPath: "dashboard/billing/subscription", ListModelPath: "v1/models", @@ -154,7 +155,10 @@ export const OpenaiPath = { export const Azure = { ChatPath: (deployName: string, apiVersion: string) => `deployments/${deployName}/chat/completions?api-version=${apiVersion}`, - ExampleEndpoint: "https://{resource-url}/openai/deployments/{deploy-id}", + // https://.openai.azure.com/openai/deployments//images/generations?api-version= + ImagePath: (deployName: string, apiVersion: string) => + `deployments/${deployName}/images/generations?api-version=${apiVersion}`, + ExampleEndpoint: "https://{resource-url}/openai", }; export const Google = { @@ -256,6 +260,7 @@ const openaiModels = [ "gpt-4-vision-preview", "gpt-4-turbo-2024-04-09", "gpt-4-1106-preview", + "dall-e-3", ]; const googleModels = [ diff --git a/app/store/chat.ts b/app/store/chat.ts index 5892ef0c8..7b47f3ec6 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -26,6 +26,7 @@ import { nanoid } from "nanoid"; import { createPersistStore } from "../utils/store"; import { collectModelsWithDefaultModel } from "../utils/model"; import { useAccessStore } from "./access"; +import { isDalle3 } from "../utils"; export type ChatMessage = RequestMessage & { date: string; @@ -541,6 +542,10 @@ export const useChatStore = createPersistStore( const config = useAppConfig.getState(); const session = get().currentSession(); const modelConfig = session.mask.modelConfig; + // skip summarize when using dalle3? + if (isDalle3(modelConfig.model)) { + return; + } const api: ClientApi = getClientApi(modelConfig.providerName); diff --git a/app/utils.ts b/app/utils.ts index 2f2c8ae95..a3c329b82 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -265,3 +265,7 @@ export function isVisionModel(model: string) { visionKeywords.some((keyword) => model.includes(keyword)) || isGpt4Turbo ); } + +export function isDalle3(model: string) { + return "dall-e-3" === model; +} From 1c24ca58c784775fb0d2cf9daa07949d329bd36a Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Fri, 2 Aug 2024 18:03:19 +0800 Subject: [PATCH 07/20] add dalle3 model --- app/icons/size.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 app/icons/size.svg diff --git a/app/icons/size.svg b/app/icons/size.svg new file mode 100644 index 000000000..3da4fadfe --- /dev/null +++ b/app/icons/size.svg @@ -0,0 +1 @@ + From 46cb48023e6b2ffa52a44775b58a83a97dcffac2 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Fri, 2 Aug 2024 18:50:48 +0800 Subject: [PATCH 08/20] fix typescript error --- app/client/api.ts | 3 ++- app/client/platforms/openai.ts | 7 +++++-- app/components/chat.tsx | 5 +++-- app/store/config.ts | 2 ++ app/typing.ts | 2 ++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/client/api.ts b/app/client/api.ts index f10e47618..88157e79c 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -6,7 +6,7 @@ import { ServiceProvider, } from "../constant"; import { ChatMessage, ModelType, useAccessStore, useChatStore } from "../store"; -import { ChatGPTApi } from "./platforms/openai"; +import { ChatGPTApi, DalleRequestPayload } from "./platforms/openai"; import { GeminiProApi } from "./platforms/google"; import { ClaudeApi } from "./platforms/anthropic"; import { ErnieApi } from "./platforms/baidu"; @@ -42,6 +42,7 @@ export interface LLMConfig { stream?: boolean; presence_penalty?: number; frequency_penalty?: number; + size?: DalleRequestPayload["size"]; } export interface ChatOptions { diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 28de30051..54309e29f 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -13,6 +13,7 @@ import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { collectModelsWithDefaultModel } from "@/app/utils/model"; import { preProcessImageContent } from "@/app/utils/chat"; import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare"; +import { DalleSize } from "@/app/typing"; import { ChatOptions, @@ -63,7 +64,7 @@ export interface DalleRequestPayload { model: string; prompt: string; n: number; - size: "1024x1024" | "1792x1024" | "1024x1792"; + size: DalleSize; } export class ChatGPTApi implements LLMApi { @@ -141,7 +142,9 @@ export class ChatGPTApi implements LLMApi { const isDalle3 = _isDalle3(options.config.model); if (isDalle3) { - const prompt = getMessageTextContent(options.messages.slice(-1)?.pop()); + const prompt = getMessageTextContent( + options.messages.slice(-1)?.pop() as any, + ); requestPayload = { model: options.config.model, prompt, diff --git a/app/components/chat.tsx b/app/components/chat.tsx index b95e85d45..67ea80c4a 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -69,6 +69,7 @@ import { uploadImage as uploadImageRemote } from "@/app/utils/chat"; import dynamic from "next/dynamic"; import { ChatControllerPool } from "../client/controller"; +import { DalleSize } from "../typing"; import { Prompt, usePromptStore } from "../store/prompt"; import Locale from "../locales"; @@ -484,9 +485,9 @@ export function ChatActions(props: { const [showUploadImage, setShowUploadImage] = useState(false); const [showSizeSelector, setShowSizeSelector] = useState(false); - const dalle3Sizes = ["1024x1024", "1792x1024", "1024x1792"]; + const dalle3Sizes: DalleSize[] = ["1024x1024", "1792x1024", "1024x1792"]; const currentSize = - chatStore.currentSession().mask.modelConfig?.size || "1024x1024"; + chatStore.currentSession().mask.modelConfig?.size ?? "1024x1024"; useEffect(() => { const show = isVisionModel(currentModel); diff --git a/app/store/config.ts b/app/store/config.ts index 1eaafe12b..705a9d87c 100644 --- a/app/store/config.ts +++ b/app/store/config.ts @@ -1,4 +1,5 @@ import { LLMModel } from "../client/api"; +import { DalleSize } from "../typing"; import { getClientConfig } from "../config/client"; import { DEFAULT_INPUT_TEMPLATE, @@ -60,6 +61,7 @@ export const DEFAULT_CONFIG = { compressMessageLengthThreshold: 1000, enableInjectSystemPrompts: true, template: config?.template ?? DEFAULT_INPUT_TEMPLATE, + size: "1024x1024" as DalleSize, }, }; diff --git a/app/typing.ts b/app/typing.ts index b09722ab9..863203581 100644 --- a/app/typing.ts +++ b/app/typing.ts @@ -7,3 +7,5 @@ export interface RequestMessage { role: MessageRole; content: string; } + +export type DalleSize = "1024x1024" | "1792x1024" | "1024x1792"; From 8c83fe23a1661d37644626e8d71130d96ce413f9 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Fri, 2 Aug 2024 20:58:21 +0800 Subject: [PATCH 09/20] using b64_json for dall-e-3 --- app/client/platforms/openai.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 54309e29f..ee9a70913 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -11,7 +11,11 @@ import { } from "@/app/constant"; import { useAccessStore, useAppConfig, useChatStore } from "@/app/store"; import { collectModelsWithDefaultModel } from "@/app/utils/model"; -import { preProcessImageContent } from "@/app/utils/chat"; +import { + preProcessImageContent, + uploadImage, + base64Image2Blob, +} from "@/app/utils/chat"; import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare"; import { DalleSize } from "@/app/typing"; @@ -63,6 +67,7 @@ export interface RequestPayload { export interface DalleRequestPayload { model: string; prompt: string; + response_format: "url" | "b64_json"; n: number; size: DalleSize; } @@ -109,13 +114,18 @@ export class ChatGPTApi implements LLMApi { return cloudflareAIGatewayUrl([baseUrl, path].join("/")); } - extractMessage(res: any) { + async extractMessage(res: any) { if (res.error) { return "```\n" + JSON.stringify(res, null, 4) + "\n```"; } - // dalle3 model return url, just return + // dalle3 model return url, using url create image message if (res.data) { - const url = res.data?.at(0)?.url ?? ""; + let url = res.data?.at(0)?.url ?? ""; + const b64_json = res.data?.at(0)?.b64_json ?? ""; + if (!url && b64_json) { + // uploadImage + url = await uploadImage(base64Image2Blob(b64_json, "image/png")); + } return [ { type: "image_url", @@ -148,6 +158,8 @@ export class ChatGPTApi implements LLMApi { requestPayload = { model: options.config.model, prompt, + // URLs are only valid for 60 minutes after the image has been generated. + response_format: "b64_json", // using b64_json, and save image in CacheStorage n: 1, size: options.config?.size ?? "1024x1024", }; @@ -227,7 +239,7 @@ export class ChatGPTApi implements LLMApi { // make a fetch request const requestTimeoutId = setTimeout( () => controller.abort(), - REQUEST_TIMEOUT_MS, + isDalle3 ? REQUEST_TIMEOUT_MS * 2 : REQUEST_TIMEOUT_MS, // dalle3 using b64_json is slow. ); if (shouldStream) { @@ -358,7 +370,7 @@ export class ChatGPTApi implements LLMApi { clearTimeout(requestTimeoutId); const resJson = await res.json(); - const message = this.extractMessage(resJson); + const message = await this.extractMessage(resJson); options.onFinish(message); } } catch (e) { From 4a8e85c28a293c765ce73af6afb34aaa4840290e Mon Sep 17 00:00:00 2001 From: Dogtiti <499960698@qq.com> Date: Fri, 2 Aug 2024 22:16:08 +0800 Subject: [PATCH 10/20] fix: empty response --- app/client/platforms/openai.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index ee9a70913..8b03d1397 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -135,7 +135,7 @@ export class ChatGPTApi implements LLMApi { }, ]; } - return res.choices?.at(0)?.message?.content ?? ""; + return res.choices?.at(0)?.message?.content ?? res; } async chat(options: ChatOptions) { From deb140de739ccda009fc93ffce1e33e53682b042 Mon Sep 17 00:00:00 2001 From: frostime Date: Fri, 2 Aug 2024 22:27:45 +0800 Subject: [PATCH 11/20] =?UTF-8?q?=E2=9C=A8=20feat:=20=E4=B8=BA=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=89=8D=E7=BC=80=EF=BC=88=20`:`=20=EF=BC=89=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=B8=AD=E6=96=87=E7=AC=A6=E5=8F=B7=20`=EF=BC=9A`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/command.ts | 13 ++++++++----- app/components/chat.tsx | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/command.ts b/app/command.ts index e515e5f0b..bea4e06f3 100644 --- a/app/command.ts +++ b/app/command.ts @@ -41,13 +41,16 @@ interface ChatCommands { del?: Command; } -export const ChatCommandPrefix = ":"; +// Compatible with Chinese colon character ":" +export const ChatCommandPrefix = /^[::]/; export function useChatCommand(commands: ChatCommands = {}) { function extract(userInput: string) { - return ( - userInput.startsWith(ChatCommandPrefix) ? userInput.slice(1) : userInput - ) as keyof ChatCommands; + const match = userInput.match(ChatCommandPrefix); + if (match) { + return userInput.slice(1) as keyof ChatCommands; + } + return userInput as keyof ChatCommands; } function search(userInput: string) { @@ -57,7 +60,7 @@ export function useChatCommand(commands: ChatCommands = {}) { .filter((c) => c.startsWith(input)) .map((c) => ({ title: desc[c as keyof ChatCommands], - content: ChatCommandPrefix + c, + content: ":" + c, })); } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index bb4b611ad..785b1f7cd 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -811,7 +811,7 @@ function _Chat() { // clear search results if (n === 0) { setPromptHints([]); - } else if (text.startsWith(ChatCommandPrefix)) { + } else if (text.match(ChatCommandPrefix)) { setPromptHints(chatCommands.search(text)); } else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) { // check if need to trigger auto completion From b6a022b0ef9dc7242b572089f3a1c8a08215bfd1 Mon Sep 17 00:00:00 2001 From: frostime Date: Sat, 3 Aug 2024 16:04:20 +0800 Subject: [PATCH 12/20] =?UTF-8?q?=E2=9C=A8=20feat(config):=20Set=20markdow?= =?UTF-8?q?n's=20font-family?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/chat.tsx | 3 +++ app/components/exporter.tsx | 1 + app/components/markdown.tsx | 2 ++ app/components/settings.tsx | 13 +++++++++++++ app/store/config.ts | 1 + app/utils.ts | 1 + 6 files changed, 21 insertions(+) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 785b1f7cd..ade309675 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -732,6 +732,7 @@ function _Chat() { const session = chatStore.currentSession(); const config = useAppConfig(); const fontSize = config.fontSize; + const fontFamily = config.fontFamily; const [showExport, setShowExport] = useState(false); @@ -1482,6 +1483,7 @@ function _Chat() { setUserInput(getMessageTextContent(message)); }} fontSize={fontSize} + fontFamily={fontFamily} parentRef={scrollRef} defaultShow={i >= messages.length - 6} /> @@ -1576,6 +1578,7 @@ function _Chat() { autoFocus={autoFocus} style={{ fontSize: config.fontSize, + fontFamily: config.fontFamily, }} /> {attachImages.length != 0 && ( diff --git a/app/components/exporter.tsx b/app/components/exporter.tsx index 8210f61fb..1771cc9b0 100644 --- a/app/components/exporter.tsx +++ b/app/components/exporter.tsx @@ -583,6 +583,7 @@ export function ImagePreviewer(props: { {getMessageImages(m).length == 1 && ( diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index c12daed64..1531d2ff0 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -232,6 +232,7 @@ export function Markdown( content: string; loading?: boolean; fontSize?: number; + fontFamily?: string; parentRef?: RefObject; defaultShow?: boolean; } & React.DOMAttributes, @@ -243,6 +244,7 @@ export function Markdown( className="markdown-body" style={{ fontSize: `${props.fontSize ?? 14}px`, + fontFamily: props.fontFamily || "inherit", }} ref={mdRef} onContextMenu={props.onContextMenu} diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 319781225..f3e3e1c67 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -1316,6 +1316,19 @@ export function Settings() { > + + + updateConfig( + (config) => (config.fontFamily = e.currentTarget.value), + ) + } + > + + Date: Sat, 3 Aug 2024 16:24:51 +0800 Subject: [PATCH 13/20] =?UTF-8?q?=F0=9F=8E=A8=20i18n:=20add=20locale=20abo?= =?UTF-8?q?ut=20"font=20family=20config"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note: The language is translated by GPT --- app/components/settings.tsx | 7 +++++-- app/locales/ar.ts | 5 +++++ app/locales/bn.ts | 6 ++++++ app/locales/cn.ts | 5 +++++ app/locales/cs.ts | 6 ++++++ app/locales/de.ts | 6 ++++++ app/locales/en.ts | 6 ++++++ app/locales/es.ts | 6 ++++++ app/locales/fr.ts | 6 ++++++ app/locales/id.ts | 10 ++++++++-- app/locales/it.ts | 6 ++++++ app/locales/jp.ts | 6 ++++++ app/locales/ko.ts | 5 +++++ app/locales/no.ts | 6 ++++++ app/locales/pt.ts | 6 ++++++ app/locales/ru.ts | 6 ++++++ app/locales/sk.ts | 6 ++++++ app/locales/tr.ts | 6 ++++++ app/locales/tw.ts | 5 +++++ app/locales/vi.ts | 6 ++++++ 20 files changed, 117 insertions(+), 4 deletions(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index f3e3e1c67..93976ac45 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -1316,11 +1316,14 @@ export function Settings() { > - + updateConfig( (config) => (config.fontFamily = e.currentTarget.value), diff --git a/app/locales/ar.ts b/app/locales/ar.ts index b58c3a2e8..0a35ba603 100644 --- a/app/locales/ar.ts +++ b/app/locales/ar.ts @@ -111,6 +111,11 @@ const ar: PartialLocaleType = { Title: "حجم الخط", SubTitle: "ضبط حجم الخط لمحتوى الدردشة", }, + FontFamily: { + Title: "خط الدردشة", + SubTitle: "خط محتوى الدردشة، اتركه فارغًا لتطبيق الخط الافتراضي العالمي", + Placeholder: "اسم الخط", + }, InjectSystemPrompts: { Title: "حقن تلميحات النظام", SubTitle: diff --git a/app/locales/bn.ts b/app/locales/bn.ts index 6dfb0da9b..cc650a3fe 100644 --- a/app/locales/bn.ts +++ b/app/locales/bn.ts @@ -136,6 +136,12 @@ const bn: PartialLocaleType = { Title: "ফন্ট সাইজ", SubTitle: "চ্যাট সামগ্রীর ফন্ট সাইজ সংশোধন করুন", }, + FontFamily: { + Title: "চ্যাট ফন্ট", + SubTitle: + "চ্যাট সামগ্রীর ফন্ট, বিশ্বব্যাপী ডিফল্ট ফন্ট প্রয়োগ করতে খালি রাখুন", + Placeholder: "ফন্টের নাম", + }, InjectSystemPrompts: { Title: "حقن تلميحات النظام", SubTitle: diff --git a/app/locales/cn.ts b/app/locales/cn.ts index dea7232fc..69ada8784 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -156,6 +156,11 @@ const cn = { Title: "字体大小", SubTitle: "聊天内容的字体大小", }, + FontFamily: { + Title: "聊天字体", + SubTitle: "聊天内容的字体,若置空则应用全局默认字体", + Placeholder: "字体名称", + }, InjectSystemPrompts: { Title: "注入系统级提示信息", SubTitle: "强制给每次请求的消息列表开头添加一个模拟 ChatGPT 的系统提示", diff --git a/app/locales/cs.ts b/app/locales/cs.ts index c1a84430f..e70a0e839 100644 --- a/app/locales/cs.ts +++ b/app/locales/cs.ts @@ -71,6 +71,12 @@ const cs: PartialLocaleType = { Title: "Velikost písma", SubTitle: "Nastavení velikosti písma obsahu chatu", }, + FontFamily: { + Title: "Chatové Písmo", + SubTitle: + "Písmo obsahu chatu, ponechejte prázdné pro použití globálního výchozího písma", + Placeholder: "Název Písma", + }, InjectSystemPrompts: { Title: "Vložit systémové prompty", SubTitle: diff --git a/app/locales/de.ts b/app/locales/de.ts index 2fe871bc9..e50760d88 100644 --- a/app/locales/de.ts +++ b/app/locales/de.ts @@ -71,6 +71,12 @@ const de: PartialLocaleType = { Title: "Schriftgröße", SubTitle: "Schriftgröße des Chat-Inhalts anpassen", }, + FontFamily: { + Title: "Chat-Schriftart", + SubTitle: + "Schriftart des Chat-Inhalts, leer lassen, um die globale Standardschriftart anzuwenden", + Placeholder: "Schriftartname", + }, InjectSystemPrompts: { Title: "System-Prompts einfügen", SubTitle: diff --git a/app/locales/en.ts b/app/locales/en.ts index d036ffce0..9a7410cbe 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -158,6 +158,12 @@ const en: LocaleType = { Title: "Font Size", SubTitle: "Adjust font size of chat content", }, + FontFamily: { + Title: "Chat Font Family", + SubTitle: + "Font Family of the chat content, leave empty to apply global default font", + Placeholder: "Font Family Name", + }, InjectSystemPrompts: { Title: "Inject System Prompts", SubTitle: "Inject a global system prompt for every request", diff --git a/app/locales/es.ts b/app/locales/es.ts index 7d742d536..f595fa3fc 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -71,6 +71,12 @@ const es: PartialLocaleType = { Title: "Tamaño de fuente", SubTitle: "Ajustar el tamaño de fuente del contenido del chat", }, + FontFamily: { + Title: "Fuente del Chat", + SubTitle: + "Fuente del contenido del chat, dejar vacío para aplicar la fuente predeterminada global", + Placeholder: "Nombre de la Fuente", + }, InjectSystemPrompts: { Title: "Inyectar Prompts del Sistema", SubTitle: diff --git a/app/locales/fr.ts b/app/locales/fr.ts index 944754d62..0fac154ed 100644 --- a/app/locales/fr.ts +++ b/app/locales/fr.ts @@ -111,6 +111,12 @@ const fr: PartialLocaleType = { Title: "Taille des polices", SubTitle: "Ajuste la taille de police du contenu de la conversation", }, + FontFamily: { + Title: "Police de Chat", + SubTitle: + "Police du contenu du chat, laissez vide pour appliquer la police par défaut globale", + Placeholder: "Nom de la Police", + }, InjectSystemPrompts: { Title: "Injecter des invites système", SubTitle: diff --git a/app/locales/id.ts b/app/locales/id.ts index 571156a57..0353433f9 100644 --- a/app/locales/id.ts +++ b/app/locales/id.ts @@ -140,6 +140,12 @@ const id: PartialLocaleType = { Title: "Ukuran Font", SubTitle: "Ubah ukuran font konten chat", }, + FontFamily: { + Title: "Font Obrolan", + SubTitle: + "Font dari konten obrolan, biarkan kosong untuk menerapkan font default global", + Placeholder: "Nama Font", + }, InjectSystemPrompts: { Title: "Suntikkan Petunjuk Sistem", SubTitle: @@ -369,8 +375,8 @@ const id: PartialLocaleType = { }, Exporter: { Description: { - Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan" - }, + Title: "Hanya pesan setelah menghapus konteks yang akan ditampilkan", + }, Model: "Model", Messages: "Pesan", Topic: "Topik", diff --git a/app/locales/it.ts b/app/locales/it.ts index 7f0a95846..5981c8d3d 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -71,6 +71,12 @@ const it: PartialLocaleType = { Title: "Dimensione carattere", SubTitle: "Regolare la dimensione dei caratteri del contenuto della chat", }, + FontFamily: { + Title: "Font della Chat", + SubTitle: + "Carattere del contenuto della chat, lascia vuoto per applicare il carattere predefinito globale", + Placeholder: "Nome del Font", + }, InjectSystemPrompts: { Title: "Inserisci Prompts di Sistema", SubTitle: diff --git a/app/locales/jp.ts b/app/locales/jp.ts index dcbd0f282..94ed8f8ac 100644 --- a/app/locales/jp.ts +++ b/app/locales/jp.ts @@ -118,6 +118,12 @@ const jp: PartialLocaleType = { Title: "フォントサイズ", SubTitle: "チャット内容のフォントサイズ", }, + FontFamily: { + Title: "チャットフォント", + SubTitle: + "チャットコンテンツのフォント、空白の場合はグローバルデフォルトフォントを適用します", + Placeholder: "フォント名", + }, InjectSystemPrompts: { Title: "システムプロンプトの挿入", SubTitle: diff --git a/app/locales/ko.ts b/app/locales/ko.ts index 844459fc4..b392e1c77 100644 --- a/app/locales/ko.ts +++ b/app/locales/ko.ts @@ -72,6 +72,11 @@ const ko: PartialLocaleType = { Title: "글꼴 크기", SubTitle: "채팅 내용의 글꼴 크기 조정", }, + FontFamily: { + Title: "채팅 폰트", + SubTitle: "채팅 내용의 폰트, 비워 두면 글로벌 기본 폰트를 적용", + Placeholder: "폰트 이름", + }, InjectSystemPrompts: { Title: "시스템 프롬프트 주입", SubTitle: diff --git a/app/locales/no.ts b/app/locales/no.ts index 3a0e61107..5e9dc252d 100644 --- a/app/locales/no.ts +++ b/app/locales/no.ts @@ -66,6 +66,12 @@ const no: PartialLocaleType = { Title: "Fontstørrelsen", SubTitle: "Juster fontstørrelsen for samtaleinnholdet.", }, + FontFamily: { + Title: "Chat-skrifttype", + SubTitle: + "Skrifttypen for chatinnhold, la stå tom for å bruke global standardskrifttype", + Placeholder: "Skriftnavn", + }, InjectSystemPrompts: { Title: "Sett inn systemprompter", SubTitle: diff --git a/app/locales/pt.ts b/app/locales/pt.ts index 8151b7aa4..10e915e92 100644 --- a/app/locales/pt.ts +++ b/app/locales/pt.ts @@ -153,6 +153,12 @@ const pt: PartialLocaleType = { Title: "Tamanho da Fonte", SubTitle: "Ajustar o tamanho da fonte do conteúdo do chat", }, + FontFamily: { + Title: "Fonte do Chat", + SubTitle: + "Fonte do conteúdo do chat, deixe vazio para aplicar a fonte padrão global", + Placeholder: "Nome da Fonte", + }, InjectSystemPrompts: { Title: "Inserir Prompts de Sistema", SubTitle: "Inserir um prompt de sistema global para cada requisição", diff --git a/app/locales/ru.ts b/app/locales/ru.ts index d12cf3e42..b547402ea 100644 --- a/app/locales/ru.ts +++ b/app/locales/ru.ts @@ -71,6 +71,12 @@ const ru: PartialLocaleType = { Title: "Размер шрифта", SubTitle: "Настроить размер шрифта контента чата", }, + FontFamily: { + Title: "Шрифт чата", + SubTitle: + "Шрифт содержимого чата, оставьте пустым для применения глобального шрифта по умолчанию", + Placeholder: "Название шрифта", + }, InjectSystemPrompts: { Title: "Вставить системные подсказки", SubTitle: diff --git a/app/locales/sk.ts b/app/locales/sk.ts index a97b7175c..9014f4f0c 100644 --- a/app/locales/sk.ts +++ b/app/locales/sk.ts @@ -155,6 +155,12 @@ const sk: PartialLocaleType = { Title: "Veľkosť písma", SubTitle: "Nastaviť veľkosť písma obsahu chatu", }, + FontFamily: { + Title: "Chatové Písmo", + SubTitle: + "Písmo obsahu chatu, ponechajte prázdne pre použitie globálneho predvoleného písma", + Placeholder: "Názov Písma", + }, InjectSystemPrompts: { Title: "Vložiť systémové výzvy", SubTitle: "Vložiť globálnu systémovú výzvu pre každú požiadavku", diff --git a/app/locales/tr.ts b/app/locales/tr.ts index 524c1b2c5..551e5e342 100644 --- a/app/locales/tr.ts +++ b/app/locales/tr.ts @@ -71,6 +71,12 @@ const tr: PartialLocaleType = { Title: "Yazı Boyutu", SubTitle: "Sohbet içeriğinin yazı boyutunu ayarlayın", }, + FontFamily: { + Title: "Sohbet Yazı Tipi", + SubTitle: + "Sohbet içeriğinin yazı tipi, boş bırakıldığında küresel varsayılan yazı tipi uygulanır", + Placeholder: "Yazı Tipi Adı", + }, InjectSystemPrompts: { Title: "Sistem İpucu Ekleyin", SubTitle: diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 8011de13f..03afd432c 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -153,6 +153,11 @@ const tw = { Title: "字型大小", SubTitle: "聊天內容的字型大小", }, + FontFamily: { + Title: "聊天字體", + SubTitle: "聊天內容的字體,若置空則應用全局默認字體", + Placeholder: "字體名稱", + }, InjectSystemPrompts: { Title: "匯入系統提示", SubTitle: "強制在每個請求的訊息列表開頭新增一個模擬 ChatGPT 的系統提示", diff --git a/app/locales/vi.ts b/app/locales/vi.ts index 3d95b5664..1f5c4fc68 100644 --- a/app/locales/vi.ts +++ b/app/locales/vi.ts @@ -71,6 +71,12 @@ const vi: PartialLocaleType = { Title: "Font chữ", SubTitle: "Thay đổi font chữ của nội dung trò chuyện", }, + FontFamily: { + Title: "Phông Chữ Trò Chuyện", + SubTitle: + "Phông chữ của nội dung trò chuyện, để trống để áp dụng phông chữ mặc định toàn cầu", + Placeholder: "Tên Phông Chữ", + }, InjectSystemPrompts: { Title: "Tiêm Prompt Hệ thống", SubTitle: From 8a4b8a84d67bb7431c5ce88046d94963dceebad7 Mon Sep 17 00:00:00 2001 From: frostime Date: Sat, 3 Aug 2024 17:16:05 +0800 Subject: [PATCH 14/20] =?UTF-8?q?=E2=9C=A8=20feat:=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E5=88=97=E8=A1=A8=EF=BC=8C=E5=B0=86=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=A8=A1=E5=9E=8B=E6=94=BE=E5=9C=A8=E5=89=8D?= =?UTF-8?q?=E9=9D=A2=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/model.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/utils/model.ts b/app/utils/model.ts index 4de0eb8d9..6b1485e32 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -22,15 +22,6 @@ export function collectModelTable( } > = {}; - // default models - models.forEach((m) => { - // using @ as fullName - modelTable[`${m.name}@${m?.provider?.id}`] = { - ...m, - displayName: m.name, // 'provider' is copied over if it exists - }; - }); - // server custom models customModels .split(",") @@ -89,6 +80,15 @@ export function collectModelTable( } }); + // default models + models.forEach((m) => { + // using @ as fullName + modelTable[`${m.name}@${m?.provider?.id}`] = { + ...m, + displayName: m.name, // 'provider' is copied over if it exists + }; + }); + return modelTable; } @@ -99,13 +99,16 @@ export function collectModelTableWithDefaultModel( ) { let modelTable = collectModelTable(models, customModels); if (defaultModel && defaultModel !== "") { - if (defaultModel.includes('@')) { + if (defaultModel.includes("@")) { if (defaultModel in modelTable) { modelTable[defaultModel].isDefault = true; } } else { for (const key of Object.keys(modelTable)) { - if (modelTable[key].available && key.split('@').shift() == defaultModel) { + if ( + modelTable[key].available && + key.split("@").shift() == defaultModel + ) { modelTable[key].isDefault = true; break; } From 1610675c8f956345b799be92fc1dbf4ba81c18f2 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Mon, 5 Aug 2024 11:36:35 +0800 Subject: [PATCH 15/20] remove hash.js --- app/utils/hmac.ts | 246 +++++++++++++++++++++++++++++++++++++++++++ app/utils/tencent.ts | 18 +--- package.json | 1 - yarn.lock | 15 +-- 4 files changed, 251 insertions(+), 29 deletions(-) create mode 100644 app/utils/hmac.ts diff --git a/app/utils/hmac.ts b/app/utils/hmac.ts new file mode 100644 index 000000000..96292dac3 --- /dev/null +++ b/app/utils/hmac.ts @@ -0,0 +1,246 @@ +// From https://gist.github.com/guillermodlpa/f6d955f838e9b10d1ef95b8e259b2c58 +// From https://gist.github.com/stevendesu/2d52f7b5e1f1184af3b667c0b5e054b8 + +// To ensure cross-browser support even without a proper SubtleCrypto +// impelmentation (or without access to the impelmentation, as is the case with +// Chrome loaded over HTTP instead of HTTPS), this library can create SHA-256 +// HMAC signatures using nothing but raw JavaScript + +/* eslint-disable no-magic-numbers, id-length, no-param-reassign, new-cap */ + +// By giving internal functions names that we can mangle, future calls to +// them are reduced to a single byte (minor space savings in minified file) +const uint8Array = Uint8Array; +const uint32Array = Uint32Array; +const pow = Math.pow; + +// Will be initialized below +// Using a Uint32Array instead of a simple array makes the minified code +// a bit bigger (we lose our `unshift()` hack), but comes with huge +// performance gains +const DEFAULT_STATE = new uint32Array(8); +const ROUND_CONSTANTS: number[] = []; + +// Reusable object for expanded message +// Using a Uint32Array instead of a simple array makes the minified code +// 7 bytes larger, but comes with huge performance gains +const M = new uint32Array(64); + +// After minification the code to compute the default state and round +// constants is smaller than the output. More importantly, this serves as a +// good educational aide for anyone wondering where the magic numbers come +// from. No magic numbers FTW! +function getFractionalBits(n: number) { + return ((n - (n | 0)) * pow(2, 32)) | 0; +} + +let n = 2; +let nPrime = 0; +while (nPrime < 64) { + // isPrime() was in-lined from its original function form to save + // a few bytes + let isPrime = true; + // Math.sqrt() was replaced with pow(n, 1/2) to save a few bytes + // var sqrtN = pow(n, 1 / 2); + // So technically to determine if a number is prime you only need to + // check numbers up to the square root. However this function only runs + // once and we're only computing the first 64 primes (up to 311), so on + // any modern CPU this whole function runs in a couple milliseconds. + // By going to n / 2 instead of sqrt(n) we net 8 byte savings and no + // scaling performance cost + for (let factor = 2; factor <= n / 2; factor++) { + if (n % factor === 0) { + isPrime = false; + } + } + if (isPrime) { + if (nPrime < 8) { + DEFAULT_STATE[nPrime] = getFractionalBits(pow(n, 1 / 2)); + } + ROUND_CONSTANTS[nPrime] = getFractionalBits(pow(n, 1 / 3)); + + nPrime++; + } + + n++; +} + +// For cross-platform support we need to ensure that all 32-bit words are +// in the same endianness. A UTF-8 TextEncoder will return BigEndian data, +// so upon reading or writing to our ArrayBuffer we'll only swap the bytes +// if our system is LittleEndian (which is about 99% of CPUs) +const LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0]; + +function convertEndian(word: number) { + if (LittleEndian) { + return ( + // byte 1 -> byte 4 + (word >>> 24) | + // byte 2 -> byte 3 + (((word >>> 16) & 0xff) << 8) | + // byte 3 -> byte 2 + ((word & 0xff00) << 8) | + // byte 4 -> byte 1 + (word << 24) + ); + } else { + return word; + } +} + +function rightRotate(word: number, bits: number) { + return (word >>> bits) | (word << (32 - bits)); +} + +function sha256(data: Uint8Array) { + // Copy default state + const STATE = DEFAULT_STATE.slice(); + + // Caching this reduces occurrences of ".length" in minified JavaScript + // 3 more byte savings! :D + const legth = data.length; + + // Pad data + const bitLength = legth * 8; + const newBitLength = 512 - ((bitLength + 64) % 512) - 1 + bitLength + 65; + + // "bytes" and "words" are stored BigEndian + const bytes = new uint8Array(newBitLength / 8); + const words = new uint32Array(bytes.buffer); + + bytes.set(data, 0); + // Append a 1 + bytes[legth] = 0b10000000; + // Store length in BigEndian + words[words.length - 1] = convertEndian(bitLength); + + // Loop iterator (avoid two instances of "var") -- saves 2 bytes + let round; + + // Process blocks (512 bits / 64 bytes / 16 words at a time) + for (let block = 0; block < newBitLength / 32; block += 16) { + const workingState = STATE.slice(); + + // Rounds + for (round = 0; round < 64; round++) { + let MRound; + // Expand message + if (round < 16) { + // Convert to platform Endianness for later math + MRound = convertEndian(words[block + round]); + } else { + const gamma0x = M[round - 15]; + const gamma1x = M[round - 2]; + MRound = + M[round - 7] + + M[round - 16] + + (rightRotate(gamma0x, 7) ^ + rightRotate(gamma0x, 18) ^ + (gamma0x >>> 3)) + + (rightRotate(gamma1x, 17) ^ + rightRotate(gamma1x, 19) ^ + (gamma1x >>> 10)); + } + + // M array matches platform endianness + M[round] = MRound |= 0; + + // Computation + const t1 = + (rightRotate(workingState[4], 6) ^ + rightRotate(workingState[4], 11) ^ + rightRotate(workingState[4], 25)) + + ((workingState[4] & workingState[5]) ^ + (~workingState[4] & workingState[6])) + + workingState[7] + + MRound + + ROUND_CONSTANTS[round]; + const t2 = + (rightRotate(workingState[0], 2) ^ + rightRotate(workingState[0], 13) ^ + rightRotate(workingState[0], 22)) + + ((workingState[0] & workingState[1]) ^ + (workingState[2] & (workingState[0] ^ workingState[1]))); + for (let i = 7; i > 0; i--) { + workingState[i] = workingState[i - 1]; + } + workingState[0] = (t1 + t2) | 0; + workingState[4] = (workingState[4] + t1) | 0; + } + + // Update state + for (round = 0; round < 8; round++) { + STATE[round] = (STATE[round] + workingState[round]) | 0; + } + } + + // Finally the state needs to be converted to BigEndian for output + // And we want to return a Uint8Array, not a Uint32Array + return new uint8Array( + new uint32Array( + STATE.map(function (val) { + return convertEndian(val); + }), + ).buffer, + ); +} + +function hmac(key: Uint8Array, data: ArrayLike) { + if (key.length > 64) key = sha256(key); + + if (key.length < 64) { + const tmp = new Uint8Array(64); + tmp.set(key, 0); + key = tmp; + } + + // Generate inner and outer keys + const innerKey = new Uint8Array(64); + const outerKey = new Uint8Array(64); + for (let i = 0; i < 64; i++) { + innerKey[i] = 0x36 ^ key[i]; + outerKey[i] = 0x5c ^ key[i]; + } + + // Append the innerKey + const msg = new Uint8Array(data.length + 64); + msg.set(innerKey, 0); + msg.set(data, 64); + + // Has the previous message and append the outerKey + const result = new Uint8Array(64 + 32); + result.set(outerKey, 0); + result.set(sha256(msg), 64); + + // Hash the previous message + return sha256(result); +} + +// Convert a string to a Uint8Array, SHA-256 it, and convert back to string +const encoder = new TextEncoder(); + +export function sign( + inputKey: string | Uint8Array, + inputData: string | Uint8Array, +) { + const key = + typeof inputKey === "string" ? encoder.encode(inputKey) : inputKey; + const data = + typeof inputData === "string" ? encoder.encode(inputData) : inputData; + return hmac(key, data); +} + +export function hex(bin: Uint8Array) { + return bin.reduce((acc, val) => { + const hexVal = "00" + val.toString(16); + return acc + hexVal.substring(hexVal.length - 2); + }, ""); +} + +export function hash(str: string) { + return hex(sha256(encoder.encode(str))); +} + +export function hashWithSecret(str: string, secret: string) { + return hex(sign(secret, str)).toString(); +} diff --git a/app/utils/tencent.ts b/app/utils/tencent.ts index f0cdd21ee..92772703c 100644 --- a/app/utils/tencent.ts +++ b/app/utils/tencent.ts @@ -1,19 +1,9 @@ -import hash from "hash.js"; +import { sign, hash as getHash, hex } from "./hmac"; // 使用 SHA-256 和 secret 进行 HMAC 加密 -function sha256(message: any, secret = "", encoding?: string) { - return hash - .hmac(hash.sha256 as any, secret) - .update(message) - .digest(encoding as any); -} - -// 使用 SHA-256 进行哈希 -function getHash(message: any, encoding = "hex") { - return hash - .sha256() - .update(message) - .digest(encoding as any); +function sha256(message: any, secret: any, encoding?: string) { + const result = sign(secret, message); + return encoding == "hex" ? hex(result).toString() : result; } function getDate(timestamp: number) { diff --git a/package.json b/package.json index 001b28eac..eb0a5ef67 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "@vercel/speed-insights": "^1.0.2", "emoji-picker-react": "^4.9.2", "fuse.js": "^7.0.0", - "hash.js": "^1.1.7", "heic2any": "^0.0.4", "html-to-image": "^1.11.11", "lodash-es": "^4.17.21", diff --git a/yarn.lock b/yarn.lock index 09bf32296..793c845d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3799,14 +3799,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hash.js@^1.1.7: - version "1.1.7" - resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - hast-util-from-dom@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/hast-util-from-dom/-/hast-util-from-dom-4.2.0.tgz#25836ddecc3cc0849d32749c2a7aec03e94b59a7" @@ -3970,7 +3962,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3: +inherits@2: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4962,11 +4954,6 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" From 4a95dcb6e96aa020ca2db1ea3c3175eb7d3fce84 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Mon, 5 Aug 2024 12:45:25 +0800 Subject: [PATCH 16/20] hotfix get wrong llm --- app/client/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/client/api.ts b/app/client/api.ts index f10e47618..abff459c5 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -118,6 +118,7 @@ export class ClientApi { break; case ModelProvider.Qwen: this.llm = new QwenApi(); + break; case ModelProvider.Hunyuan: this.llm = new HunyuanApi(); break; From 141ce2c99ae61e12dae21e34eba644bede70d310 Mon Sep 17 00:00:00 2001 From: lloydzhou Date: Mon, 5 Aug 2024 12:59:27 +0800 Subject: [PATCH 17/20] reduce cloudflare functions build size --- app/api/[provider]/[...path]/route.ts | 64 +++++++++++++++++++ .../[...path]/route.ts => alibaba.ts} | 26 +------- .../[...path]/route.ts => anthropic.ts} | 28 +------- .../{azure/[...path]/route.ts => azure.ts} | 30 +-------- .../{baidu/[...path]/route.ts => baidu.ts} | 26 +------- .../[...path]/route.ts => bytedance.ts} | 26 +------- .../{google/[...path]/route.ts => google.ts} | 6 +- .../[...path]/route.ts => moonshot.ts} | 26 +------- .../{openai/[...path]/route.ts => openai.ts} | 30 +-------- .../[...path]/route.ts => stability.ts} | 7 +- 10 files changed, 80 insertions(+), 189 deletions(-) create mode 100644 app/api/[provider]/[...path]/route.ts rename app/api/{alibaba/[...path]/route.ts => alibaba.ts} (91%) rename app/api/{anthropic/[...path]/route.ts => anthropic.ts} (92%) rename app/api/{azure/[...path]/route.ts => azure.ts} (66%) rename app/api/{baidu/[...path]/route.ts => baidu.ts} (91%) rename app/api/{bytedance/[...path]/route.ts => bytedance.ts} (90%) rename app/api/{google/[...path]/route.ts => google.ts} (96%) rename app/api/{moonshot/[...path]/route.ts => moonshot.ts} (91%) rename app/api/{openai/[...path]/route.ts => openai.ts} (82%) rename app/api/{stability/[...path]/route.ts => stability.ts} (95%) diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts new file mode 100644 index 000000000..6d028ac36 --- /dev/null +++ b/app/api/[provider]/[...path]/route.ts @@ -0,0 +1,64 @@ +import { ApiPath } from "@/app/constant"; +import { NextRequest, NextResponse } from "next/server"; +import { handle as openaiHandler } from "../../openai"; +import { handle as azureHandler } from "../../azure"; +import { handle as googleHandler } from "../../google"; +import { handle as anthropicHandler } from "../../anthropic"; +import { handle as baiduHandler } from "../../baidu"; +import { handle as bytedanceHandler } from "../../bytedance"; +import { handle as alibabaHandler } from "../../alibaba"; +import { handle as moonshotHandler } from "../../moonshot"; +import { handle as stabilityHandler } from "../../stability"; + +async function handle( + req: NextRequest, + { params }: { params: { provider: string; path: string[] } }, +) { + const apiPath = `/api/${params.provider}`; + console.log(`[${params.provider} Route] params `, params); + switch (apiPath) { + case ApiPath.Azure: + return azureHandler(req, { params }); + case ApiPath.Google: + return googleHandler(req, { params }); + case ApiPath.Anthropic: + return anthropicHandler(req, { params }); + case ApiPath.Baidu: + return baiduHandler(req, { params }); + case ApiPath.ByteDance: + return bytedanceHandler(req, { params }); + case ApiPath.Alibaba: + return alibabaHandler(req, { params }); + // case ApiPath.Tencent: using "/api/tencent" + case ApiPath.Moonshot: + return moonshotHandler(req, { params }); + case ApiPath.Stability: + return stabilityHandler(req, { params }); + default: + return openaiHandler(req, { params }); + } +} + +export const GET = handle; +export const POST = handle; + +export const runtime = "edge"; +export const preferredRegion = [ + "arn1", + "bom1", + "cdg1", + "cle1", + "cpt1", + "dub1", + "fra1", + "gru1", + "hnd1", + "iad1", + "icn1", + "kix1", + "lhr1", + "pdx1", + "sfo1", + "sin1", + "syd1", +]; diff --git a/app/api/alibaba/[...path]/route.ts b/app/api/alibaba.ts similarity index 91% rename from app/api/alibaba/[...path]/route.ts rename to app/api/alibaba.ts index c97ce5934..675d9f301 100644 --- a/app/api/alibaba/[...path]/route.ts +++ b/app/api/alibaba.ts @@ -14,7 +14,7 @@ import type { RequestPayload } from "@/app/client/platforms/openai"; const serverConfig = getServerSideConfig(); -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -40,30 +40,6 @@ async function handle( } } -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; -export const preferredRegion = [ - "arn1", - "bom1", - "cdg1", - "cle1", - "cpt1", - "dub1", - "fra1", - "gru1", - "hnd1", - "iad1", - "icn1", - "kix1", - "lhr1", - "pdx1", - "sfo1", - "sin1", - "syd1", -]; - async function request(req: NextRequest) { const controller = new AbortController(); diff --git a/app/api/anthropic/[...path]/route.ts b/app/api/anthropic.ts similarity index 92% rename from app/api/anthropic/[...path]/route.ts rename to app/api/anthropic.ts index 20f8d52e0..3d49f4c88 100644 --- a/app/api/anthropic/[...path]/route.ts +++ b/app/api/anthropic.ts @@ -9,13 +9,13 @@ import { } from "@/app/constant"; import { prettyObject } from "@/app/utils/format"; import { NextRequest, NextResponse } from "next/server"; -import { auth } from "../../auth"; +import { auth } from "./auth"; import { isModelAvailableInServer } from "@/app/utils/model"; import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare"; const ALLOWD_PATH = new Set([Anthropic.ChatPath, Anthropic.ChatPath1]); -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -56,30 +56,6 @@ async function handle( } } -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; -export const preferredRegion = [ - "arn1", - "bom1", - "cdg1", - "cle1", - "cpt1", - "dub1", - "fra1", - "gru1", - "hnd1", - "iad1", - "icn1", - "kix1", - "lhr1", - "pdx1", - "sfo1", - "sin1", - "syd1", -]; - const serverConfig = getServerSideConfig(); async function request(req: NextRequest) { diff --git a/app/api/azure/[...path]/route.ts b/app/api/azure.ts similarity index 66% rename from app/api/azure/[...path]/route.ts rename to app/api/azure.ts index 4a17de0c8..e2cb0c7e6 100644 --- a/app/api/azure/[...path]/route.ts +++ b/app/api/azure.ts @@ -2,10 +2,10 @@ import { getServerSideConfig } from "@/app/config/server"; import { ModelProvider } from "@/app/constant"; import { prettyObject } from "@/app/utils/format"; import { NextRequest, NextResponse } from "next/server"; -import { auth } from "../../auth"; -import { requestOpenai } from "../../common"; +import { auth } from "./auth"; +import { requestOpenai } from "./common"; -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -31,27 +31,3 @@ async function handle( return NextResponse.json(prettyObject(e)); } } - -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; -export const preferredRegion = [ - "arn1", - "bom1", - "cdg1", - "cle1", - "cpt1", - "dub1", - "fra1", - "gru1", - "hnd1", - "iad1", - "icn1", - "kix1", - "lhr1", - "pdx1", - "sfo1", - "sin1", - "syd1", -]; diff --git a/app/api/baidu/[...path]/route.ts b/app/api/baidu.ts similarity index 91% rename from app/api/baidu/[...path]/route.ts rename to app/api/baidu.ts index 94c9963c7..f4315d186 100644 --- a/app/api/baidu/[...path]/route.ts +++ b/app/api/baidu.ts @@ -14,7 +14,7 @@ import { getAccessToken } from "@/app/utils/baidu"; const serverConfig = getServerSideConfig(); -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -52,30 +52,6 @@ async function handle( } } -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; -export const preferredRegion = [ - "arn1", - "bom1", - "cdg1", - "cle1", - "cpt1", - "dub1", - "fra1", - "gru1", - "hnd1", - "iad1", - "icn1", - "kix1", - "lhr1", - "pdx1", - "sfo1", - "sin1", - "syd1", -]; - async function request(req: NextRequest) { const controller = new AbortController(); diff --git a/app/api/bytedance/[...path]/route.ts b/app/api/bytedance.ts similarity index 90% rename from app/api/bytedance/[...path]/route.ts rename to app/api/bytedance.ts index 336c837f0..cb65b1061 100644 --- a/app/api/bytedance/[...path]/route.ts +++ b/app/api/bytedance.ts @@ -12,7 +12,7 @@ import { isModelAvailableInServer } from "@/app/utils/model"; const serverConfig = getServerSideConfig(); -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -38,30 +38,6 @@ async function handle( } } -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; -export const preferredRegion = [ - "arn1", - "bom1", - "cdg1", - "cle1", - "cpt1", - "dub1", - "fra1", - "gru1", - "hnd1", - "iad1", - "icn1", - "kix1", - "lhr1", - "pdx1", - "sfo1", - "sin1", - "syd1", -]; - async function request(req: NextRequest) { const controller = new AbortController(); diff --git a/app/api/google/[...path]/route.ts b/app/api/google.ts similarity index 96% rename from app/api/google/[...path]/route.ts rename to app/api/google.ts index 83a7ce794..98fe469bf 100644 --- a/app/api/google/[...path]/route.ts +++ b/app/api/google.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; -import { auth } from "../../auth"; +import { auth } from "./auth"; import { getServerSideConfig } from "@/app/config/server"; import { ApiPath, @@ -11,9 +11,9 @@ import { prettyObject } from "@/app/utils/format"; const serverConfig = getServerSideConfig(); -async function handle( +export async function handle( req: NextRequest, - { params }: { params: { path: string[] } }, + { params }: { params: { provider: string; path: string[] } }, ) { console.log("[Google Route] params ", params); diff --git a/app/api/moonshot/[...path]/route.ts b/app/api/moonshot.ts similarity index 91% rename from app/api/moonshot/[...path]/route.ts rename to app/api/moonshot.ts index 14bc0a40d..247dd6183 100644 --- a/app/api/moonshot/[...path]/route.ts +++ b/app/api/moonshot.ts @@ -14,7 +14,7 @@ import type { RequestPayload } from "@/app/client/platforms/openai"; const serverConfig = getServerSideConfig(); -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -40,30 +40,6 @@ async function handle( } } -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; -export const preferredRegion = [ - "arn1", - "bom1", - "cdg1", - "cle1", - "cpt1", - "dub1", - "fra1", - "gru1", - "hnd1", - "iad1", - "icn1", - "kix1", - "lhr1", - "pdx1", - "sfo1", - "sin1", - "syd1", -]; - async function request(req: NextRequest) { const controller = new AbortController(); diff --git a/app/api/openai/[...path]/route.ts b/app/api/openai.ts similarity index 82% rename from app/api/openai/[...path]/route.ts rename to app/api/openai.ts index 77059c151..6d11d6792 100644 --- a/app/api/openai/[...path]/route.ts +++ b/app/api/openai.ts @@ -3,8 +3,8 @@ import { getServerSideConfig } from "@/app/config/server"; import { ModelProvider, OpenaiPath } from "@/app/constant"; import { prettyObject } from "@/app/utils/format"; import { NextRequest, NextResponse } from "next/server"; -import { auth } from "../../auth"; -import { requestOpenai } from "../../common"; +import { auth } from "./auth"; +import { requestOpenai } from "./common"; const ALLOWD_PATH = new Set(Object.values(OpenaiPath)); @@ -20,7 +20,7 @@ function getModels(remoteModelRes: OpenAIListModelResponse) { return remoteModelRes; } -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -70,27 +70,3 @@ async function handle( return NextResponse.json(prettyObject(e)); } } - -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; -export const preferredRegion = [ - "arn1", - "bom1", - "cdg1", - "cle1", - "cpt1", - "dub1", - "fra1", - "gru1", - "hnd1", - "iad1", - "icn1", - "kix1", - "lhr1", - "pdx1", - "sfo1", - "sin1", - "syd1", -]; diff --git a/app/api/stability/[...path]/route.ts b/app/api/stability.ts similarity index 95% rename from app/api/stability/[...path]/route.ts rename to app/api/stability.ts index 4b2bcc305..2646ace85 100644 --- a/app/api/stability/[...path]/route.ts +++ b/app/api/stability.ts @@ -3,7 +3,7 @@ import { getServerSideConfig } from "@/app/config/server"; import { ModelProvider, STABILITY_BASE_URL } from "@/app/constant"; import { auth } from "@/app/api/auth"; -async function handle( +export async function handle( req: NextRequest, { params }: { params: { path: string[] } }, ) { @@ -97,8 +97,3 @@ async function handle( clearTimeout(timeoutId); } } - -export const GET = handle; -export const POST = handle; - -export const runtime = "edge"; From b023a00445682fcb336fe231ffe7c667632c0d15 Mon Sep 17 00:00:00 2001 From: frostime Date: Mon, 5 Aug 2024 16:37:22 +0800 Subject: [PATCH 18/20] =?UTF-8?q?=F0=9F=94=A8=20refactor(model):=20?= =?UTF-8?q?=E6=9B=B4=E6=94=B9=E5=8E=9F=E5=85=88=E7=9A=84=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=8C=E5=9C=A8=20collect=20table=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=90=8E=E9=9D=A2=E5=A2=9E=E5=8A=A0=E9=A2=9D?= =?UTF-8?q?=E5=A4=96=E7=9A=84=20sort=20=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/model.ts | 50 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/app/utils/model.ts b/app/utils/model.ts index 6b1485e32..b117b5eb6 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -7,6 +7,29 @@ const customProvider = (providerName: string) => ({ providerType: "custom", }); +const sortModelTable = ( + models: ReturnType, + rule: "custom-first" | "default-first", +) => + models.sort((a, b) => { + if (a.provider === undefined && b.provider === undefined) { + return 0; + } + + let aIsCustom = a.provider?.providerType === "custom"; + let bIsCustom = b.provider?.providerType === "custom"; + + if (aIsCustom === bIsCustom) { + return 0; + } + + if (aIsCustom) { + return rule === "custom-first" ? -1 : 1; + } else { + return rule === "custom-first" ? 1 : -1; + } + }); + export function collectModelTable( models: readonly LLMModel[], customModels: string, @@ -22,6 +45,15 @@ export function collectModelTable( } > = {}; + // default models + models.forEach((m) => { + // using @ as fullName + modelTable[`${m.name}@${m?.provider?.id}`] = { + ...m, + displayName: m.name, // 'provider' is copied over if it exists + }; + }); + // server custom models customModels .split(",") @@ -80,15 +112,6 @@ export function collectModelTable( } }); - // default models - models.forEach((m) => { - // using @ as fullName - modelTable[`${m.name}@${m?.provider?.id}`] = { - ...m, - displayName: m.name, // 'provider' is copied over if it exists - }; - }); - return modelTable; } @@ -126,7 +149,9 @@ export function collectModels( customModels: string, ) { const modelTable = collectModelTable(models, customModels); - const allModels = Object.values(modelTable); + let allModels = Object.values(modelTable); + + allModels = sortModelTable(allModels, "custom-first"); return allModels; } @@ -141,7 +166,10 @@ export function collectModelsWithDefaultModel( customModels, defaultModel, ); - const allModels = Object.values(modelTable); + let allModels = Object.values(modelTable); + + allModels = sortModelTable(allModels, "custom-first"); + return allModels; } From 150fc84b9b55fe07da2fefa73b2cbee255d9de14 Mon Sep 17 00:00:00 2001 From: frostime Date: Mon, 5 Aug 2024 19:43:32 +0800 Subject: [PATCH 19/20] =?UTF-8?q?=E2=9C=A8=20feat(model):=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20sorted=20=E5=AD=97=E6=AE=B5=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=AF=A5=E5=AD=97=E6=AE=B5=E5=AF=B9=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=88=97=E8=A1=A8=E8=BF=9B=E8=A1=8C=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 在 Model 和 Provider 类型中增加 sorted 字段(api.ts) 2. 默认模型在初始化的时候,自动设置默认 sorted 字段,从 1000 开始自增长(constant.ts) 3. 自定义模型更新的时候,自动分配 sorted 字段(model.ts) --- app/client/api.ts | 2 ++ app/constant.ts | 19 ++++++++++++++++++ app/utils/model.ts | 49 +++++++++++++++++++++++++++------------------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/app/client/api.ts b/app/client/api.ts index f10e47618..b13e0f8a4 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -64,12 +64,14 @@ export interface LLMModel { displayName?: string; available: boolean; provider: LLMModelProvider; + sorted: number; } export interface LLMModelProvider { id: string; providerName: string; providerType: string; + sorted: number; } export abstract class LLMApi { diff --git a/app/constant.ts b/app/constant.ts index 5251b5b4f..8ca17c4b3 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -320,86 +320,105 @@ const tencentModels = [ const moonshotModes = ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"]; +let seq = 1000; // 内置的模型序号生成器从1000开始 export const DEFAULT_MODELS = [ ...openaiModels.map((name) => ({ name, available: true, + sorted: seq++, // Global sequence sort(index) provider: { id: "openai", providerName: "OpenAI", providerType: "openai", + sorted: 1, // 这里是固定的,确保顺序与之前内置的版本一致 }, })), ...openaiModels.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "azure", providerName: "Azure", providerType: "azure", + sorted: 2, }, })), ...googleModels.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "google", providerName: "Google", providerType: "google", + sorted: 3, }, })), ...anthropicModels.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "anthropic", providerName: "Anthropic", providerType: "anthropic", + sorted: 4, }, })), ...baiduModels.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "baidu", providerName: "Baidu", providerType: "baidu", + sorted: 5, }, })), ...bytedanceModels.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "bytedance", providerName: "ByteDance", providerType: "bytedance", + sorted: 6, }, })), ...alibabaModes.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "alibaba", providerName: "Alibaba", providerType: "alibaba", + sorted: 7, }, })), ...tencentModels.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "tencent", providerName: "Tencent", providerType: "tencent", + sorted: 8, }, })), ...moonshotModes.map((name) => ({ name, available: true, + sorted: seq++, provider: { id: "moonshot", providerName: "Moonshot", providerType: "moonshot", + sorted: 9, }, })), ] as const; diff --git a/app/utils/model.ts b/app/utils/model.ts index b117b5eb6..0b62b53be 100644 --- a/app/utils/model.ts +++ b/app/utils/model.ts @@ -1,32 +1,39 @@ import { DEFAULT_MODELS } from "../constant"; import { LLMModel } from "../client/api"; +const CustomSeq = { + val: -1000, //To ensure the custom model located at front, start from -1000, refer to constant.ts + cache: new Map(), + next: (id: string) => { + if (CustomSeq.cache.has(id)) { + return CustomSeq.cache.get(id) as number; + } else { + let seq = CustomSeq.val++; + CustomSeq.cache.set(id, seq); + return seq; + } + }, +}; + const customProvider = (providerName: string) => ({ id: providerName.toLowerCase(), providerName: providerName, providerType: "custom", + sorted: CustomSeq.next(providerName), }); -const sortModelTable = ( - models: ReturnType, - rule: "custom-first" | "default-first", -) => +/** + * Sorts an array of models based on specified rules. + * + * First, sorted by provider; if the same, sorted by model + */ +const sortModelTable = (models: ReturnType) => models.sort((a, b) => { - if (a.provider === undefined && b.provider === undefined) { - return 0; - } - - let aIsCustom = a.provider?.providerType === "custom"; - let bIsCustom = b.provider?.providerType === "custom"; - - if (aIsCustom === bIsCustom) { - return 0; - } - - if (aIsCustom) { - return rule === "custom-first" ? -1 : 1; + if (a.provider && b.provider) { + let cmp = a.provider.sorted - b.provider.sorted; + return cmp === 0 ? a.sorted - b.sorted : cmp; } else { - return rule === "custom-first" ? 1 : -1; + return a.sorted - b.sorted; } }); @@ -40,6 +47,7 @@ export function collectModelTable( available: boolean; name: string; displayName: string; + sorted: number; provider?: LLMModel["provider"]; // Marked as optional isDefault?: boolean; } @@ -107,6 +115,7 @@ export function collectModelTable( displayName: displayName || customModelName, available, provider, // Use optional chaining + sorted: CustomSeq.next(`${customModelName}@${provider?.id}`), }; } } @@ -151,7 +160,7 @@ export function collectModels( const modelTable = collectModelTable(models, customModels); let allModels = Object.values(modelTable); - allModels = sortModelTable(allModels, "custom-first"); + allModels = sortModelTable(allModels); return allModels; } @@ -168,7 +177,7 @@ export function collectModelsWithDefaultModel( ); let allModels = Object.values(modelTable); - allModels = sortModelTable(allModels, "custom-first"); + allModels = sortModelTable(allModels); return allModels; } From 3486954e073665b4bcaa4d41096b1341e4c497ff Mon Sep 17 00:00:00 2001 From: frostime Date: Mon, 5 Aug 2024 20:26:48 +0800 Subject: [PATCH 20/20] =?UTF-8?q?=F0=9F=90=9B=20fix(openai):=20=E4=B8=8A?= =?UTF-8?q?=E6=AC=A1=20commit=20=E5=90=8E=20openai.ts=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E5=87=BA=E7=8E=B0=E7=B1=BB=E5=9E=8B=E4=B8=8D=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/client/platforms/openai.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 680125fe6..d95aebe87 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -411,13 +411,17 @@ export class ChatGPTApi implements LLMApi { return []; } + //由于目前 OpenAI 的 disableListModels 默认为 true,所以当前实际不会运行到这场 + let seq = 1000; //同 Constant.ts 中的排序保持一致 return chatModels.map((m) => ({ name: m.id, available: true, + sorted: seq++, provider: { id: "openai", providerName: "OpenAI", providerType: "openai", + sorted: 1, }, })); }