From 1db210097c431fa460aea5b8a1bb697fb0f2db6d Mon Sep 17 00:00:00 2001 From: RugerMc <550279039@qq.com> Date: Fri, 31 Mar 2023 13:16:12 +0800 Subject: [PATCH 01/91] feat: add switch of send preview bubble --- app/components/home.tsx | 27 ++++++++++++++------------- app/components/settings.tsx | 12 ++++++++++++ app/locales/cn.ts | 1 + app/locales/en.ts | 1 + app/locales/tw.ts | 1 + app/store/app.ts | 2 ++ 6 files changed, 31 insertions(+), 13 deletions(-) diff --git a/app/components/home.tsx b/app/components/home.tsx index 2f09aa27..2da10196 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -292,6 +292,8 @@ export function Chat(props: { const latestMessageRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); + const config = useChatStore((state) => state.config); + // preview messages const messages = (session.messages as RenderMessage[]) .concat( @@ -305,19 +307,18 @@ export function Chat(props: { }, ] : [], - ) - .concat( - userInput.length > 0 - ? [ - { - role: "user", - content: userInput, - date: new Date().toLocaleString(), - preview: true, - }, - ] - : [], - ); + ).concat( + userInput.length > 0 && config.sendPreviewBubble + ? [ + { + role: "user", + content: userInput, + date: new Date().toLocaleString(), + preview: false, + }, + ] + : [], + ); // auto scroll useLayoutEffect(() => { diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 711cb954..06ff76e1 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -278,6 +278,18 @@ export function Settings(props: { closeSettings: () => void }) { } > + + + + updateConfig( + (config) => (config.sendPreviewBubble = e.currentTarget.checked), + ) + } + > + Date: Sat, 1 Apr 2023 02:34:33 -0700 Subject: [PATCH 02/91] api: set Content-Type to json This avoids issues in browsers like WeChat where the encoding is incorrect and the summary feature does not work if it contains zh-CN characters. --- app/api/openai/route.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/api/openai/route.ts b/app/api/openai/route.ts index 5bc317e5..5ddb0f4c 100644 --- a/app/api/openai/route.ts +++ b/app/api/openai/route.ts @@ -3,8 +3,10 @@ import { requestOpenai } from "../common"; async function makeRequest(req: NextRequest) { try { - const res = await requestOpenai(req); - return new Response(res.body); + const api = await requestOpenai(req); + const res = new NextResponse(api.body); + res.headers.set('Content-Type', 'application/json'); + return res; } catch (e) { console.error("[OpenAI] ", req.body, e); return NextResponse.json( From 327ac765df9413da68c1407e88050c1d2c4b351b Mon Sep 17 00:00:00 2001 From: Jun Wu Date: Sat, 1 Apr 2023 03:28:29 -0700 Subject: [PATCH 03/91] utils: simplify trimTopic Also avoid using Array.prototype.at, which does not seem to exist in the Wexin builtin webview (Android Wexin 8.0.30). --- app/utils.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/utils.ts b/app/utils.ts index 64120df4..1fb3d316 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -2,15 +2,7 @@ import { showToast } from "./components/ui-lib"; import Locale from "./locales"; export function trimTopic(topic: string) { - const s = topic.split(""); - let lastChar = s.at(-1); // 获取 s 的最后一个字符 - let pattern = /[,。!?、,.!?]/; // 定义匹配中文和英文标点符号的正则表达式 - while (lastChar && pattern.test(lastChar!)) { - s.pop(); - lastChar = s.at(-1); - } - - return s.join(""); + return topic.replace(/[,。!?、,.!?]*$/, ""); } export function copyToClipboard(text: string) { From 00a282214e60ad29a3041fc35fa2196b84751d7b Mon Sep 17 00:00:00 2001 From: linqirong <609413692@qq.com> Date: Sun, 2 Apr 2023 00:12:31 +0800 Subject: [PATCH 04/91] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=B3=95=E4=B8=8Benter=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/home.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/components/home.tsx b/app/components/home.tsx index de93510d..210e4d74 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -132,9 +132,9 @@ function useSubmitHandler() { const config = useChatStore((state) => state.config); const submitKey = config.submitKey; - const shouldSubmit = (e: KeyboardEvent) => { + const shouldSubmit = (e: React.KeyboardEvent) => { if (e.key !== "Enter") return false; - + if(e.key==='Enter' && e.nativeEvent.isComposing) return false return ( (config.submitKey === SubmitKey.AltEnter && e.altKey) || (config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) || @@ -256,7 +256,7 @@ export function Chat(props: { }; // check if should send message - const onInputKeyDown = (e: KeyboardEvent) => { + const onInputKeyDown = (e: React.KeyboardEvent) => { if (shouldSubmit(e)) { onUserSubmit(); e.preventDefault(); @@ -488,7 +488,7 @@ export function Chat(props: { rows={4} onInput={(e) => onInput(e.currentTarget.value)} value={userInput} - onKeyDown={(e) => onInputKeyDown(e as any)} + onKeyDown={onInputKeyDown} onFocus={() => setAutoScroll(true)} onBlur={() => { setAutoScroll(false); From cd5f8f74070e4e95b2eaff3f87051649f98d33d8 Mon Sep 17 00:00:00 2001 From: Jun Wu Date: Sat, 1 Apr 2023 11:38:52 -0700 Subject: [PATCH 05/91] app: polyfill Array.at This fixes compatibility issue with older browsers like WeChat webview. The summary feature now works as expected. --- app/requests.ts | 4 ++++ app/store/app.ts | 4 ++++ package.json | 3 ++- yarn.lock | 10 ++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/requests.ts b/app/requests.ts index f4db7a1b..56fd6cb5 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -2,6 +2,10 @@ import type { ChatRequest, ChatReponse } from "./api/openai/typing"; import { filterConfig, Message, ModelConfig, useAccessStore } from "./store"; import Locale from "./locales"; +if (!Array.prototype.at) { + require('array.prototype.at/auto'); +} + const TIME_OUT_MS = 30000; const makeRequestParam = ( diff --git a/app/store/app.ts b/app/store/app.ts index 6ab3229a..cc52a3c7 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -11,6 +11,10 @@ import { trimTopic } from "../utils"; import Locale from "../locales"; +if (!Array.prototype.at) { + require('array.prototype.at/auto'); +} + export type Message = ChatCompletionResponseMessage & { date: string; streaming?: boolean; diff --git a/package.json b/package.json index eb17000e..7c6832ed 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,9 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", - "remark-breaks": "^3.0.2", "rehype-katex": "^6.0.2", "rehype-prism-plus": "^1.5.1", + "remark-breaks": "^3.0.2", "remark-gfm": "^3.0.1", "remark-math": "^5.1.1", "sass": "^1.59.2", @@ -39,6 +39,7 @@ "@types/react-dom": "^18.0.11", "@types/react-katex": "^3.0.0", "@types/spark-md5": "^3.0.2", + "array.prototype.at": "^1.1.1", "cross-env": "^7.0.3", "eslint": "^8.36.0", "eslint-config-next": "13.2.3", diff --git a/yarn.lock b/yarn.lock index 9f98a244..246b818b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1570,6 +1570,16 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.at@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array.prototype.at/-/array.prototype.at-1.1.1.tgz#6deda3cd3c704afa16361387ea344e0b8d8831b5" + integrity sha512-n/wYNLJy/fVEU9EGPt2ww920hy1XX3XB2yTREFy1QsxctBgQV/tZIwg1G8jVxELna4pLCzg/xvvS/DDXtI4NNg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + array.prototype.flat@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" From 506cdbc83c83feeabf6c427418ce04916bd3a8d6 Mon Sep 17 00:00:00 2001 From: AprilNEA Date: Sun, 2 Apr 2023 13:42:47 +0800 Subject: [PATCH 06/91] feat: clear session only --- app/components/settings.tsx | 10 +++++----- app/store/app.ts | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index eb9bc6d4..c2bdb86e 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -46,14 +46,14 @@ function SettingItem(props: { export function Settings(props: { closeSettings: () => void }) { const [showEmojiPicker, setShowEmojiPicker] = useState(false); - const [config, updateConfig, resetConfig, clearAllData] = useChatStore( - (state) => [ + const [config, updateConfig, resetConfig, clearAllData, clearSessions] = + useChatStore((state) => [ state.config, state.updateConfig, state.resetConfig, state.clearAllData, - ] - ); + state.clearSessions, + ]); const updateStore = useUpdateStore(); const [checkingUpdate, setCheckingUpdate] = useState(false); @@ -93,7 +93,7 @@ export function Settings(props: { closeSettings: () => void }) {
} - onClick={clearAllData} + onClick={clearSessions} bordered title={Locale.Settings.Actions.ClearAll} /> diff --git a/app/store/app.ts b/app/store/app.ts index 703078ad..46e46d52 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -177,6 +177,7 @@ interface ChatStore { config: ChatConfig; sessions: ChatSession[]; currentSessionIndex: number; + clearSessions: () => void; removeSession: (index: number) => void; selectSession: (index: number) => void; newSession: () => void; @@ -211,6 +212,13 @@ export const useChatStore = create()( ...DEFAULT_CONFIG, }, + clearSessions(){ + set(() => ({ + sessions: [createEmptySession()], + currentSessionIndex: 0, + })); + }, + resetConfig() { set(() => ({ config: { ...DEFAULT_CONFIG } })); }, From ed5cd11d6ad984dcd78b1a65340b01dc7bc1fda4 Mon Sep 17 00:00:00 2001 From: Sad Pencil Date: Sun, 2 Apr 2023 14:23:12 +0800 Subject: [PATCH 07/91] Fix typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d026e61a..40f7fc74 100644 --- a/README.md +++ b/README.md @@ -43,14 +43,14 @@ One-Click to deploy your own ChatGPT web UI. - Plugins: support network search, caculator, any other apis etc. 插件机制,支持联网搜索、计算器、调用其他平台 api [#165](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/165) ### 不会开发的功能 Not in Plan -- User login, accounts, cloud sync 用户登陆、账号管理、消息云同步 +- User login, accounts, cloud sync 用户登录、账号管理、消息云同步 - UI text customize 界面文字自定义 ## 开始使用 1. 准备好你的 [OpenAI API Key](https://platform.openai.com/account/api-keys); 2. 点击右侧按钮开始部署: - [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登陆即可,记得在环境变量页填入 API Key; + [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web),直接使用 Github 账号登录即可,记得在环境变量页填入 API Key; 3. 部署完毕后,即可开始使用; 4. (可选)[绑定自定义域名](https://vercel.com/docs/concepts/projects/domains/add-a-domain):Vercel 分配的域名 DNS 在某些区域被污染了,绑定自定义域名即可直连。 From fea4f561b4c175c6f5c1fcc842e31a475132591b Mon Sep 17 00:00:00 2001 From: Cesaryuan <35998162+cesaryuan@users.noreply.github.com> Date: Sun, 2 Apr 2023 19:43:11 +0800 Subject: [PATCH 08/91] fix: fix history message count Bug: The length of `new Array(20).slice(20 - 24) ` is 4 which should be 24. --- app/store/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/store/app.ts b/app/store/app.ts index ec0c8c50..d6fd140f 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -384,7 +384,7 @@ export const useChatStore = create()( const config = get().config; const n = session.messages.length; const recentMessages = session.messages.slice( - n - config.historyMessageCount, + - config.historyMessageCount, ); const memoryPrompt = get().getMemoryPrompt(); From 12f342f01589a1a458d16601c47d617ebe124659 Mon Sep 17 00:00:00 2001 From: Cesaryuan Date: Sun, 2 Apr 2023 20:23:56 +0800 Subject: [PATCH 09/91] fix: historyMessageCount --- app/store/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/store/app.ts b/app/store/app.ts index d6fd140f..b0fcbe91 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -384,7 +384,7 @@ export const useChatStore = create()( const config = get().config; const n = session.messages.length; const recentMessages = session.messages.slice( - - config.historyMessageCount, + Math.max(0, n - config.historyMessageCount), ); const memoryPrompt = get().getMemoryPrompt(); From 16028795f91bb65c84362475b977271ac0df3243 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Sun, 2 Apr 2023 12:28:18 +0000 Subject: [PATCH 10/91] fix: #203 pwa installation problem --- public/serviceWorker.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/serviceWorker.js b/public/serviceWorker.js index 028c79a8..f5a24b70 100644 --- a/public/serviceWorker.js +++ b/public/serviceWorker.js @@ -11,3 +11,5 @@ self.addEventListener("install", function (event) { }), ); }); + +self.addEventListener("fetch", (e) => {}); From a90e646381e91287ac355d952aaa3695317439ff Mon Sep 17 00:00:00 2001 From: MapleUncle Date: Sun, 2 Apr 2023 20:38:14 +0800 Subject: [PATCH 11/91] =?UTF-8?q?=F0=9F=90=9E=20fix(locales):=20Fix=20the?= =?UTF-8?q?=20missing=20SendPreviewBubble=20in=20ES=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/locales/es.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/locales/es.ts b/app/locales/es.ts index 1850e4cf..a78bf1aa 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -78,6 +78,7 @@ const es: LocaleType = { SendKey: "Tecla de envío", Theme: "Tema", TightBorder: "Borde ajustado", + SendPreviewBubble: "Send preview bubble", Prompt: { Disable: { Title: "Desactivar autocompletado", From 37587f6f717eb5092f1c5e5fb5eabedd40f12c94 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Sun, 2 Apr 2023 13:56:34 +0000 Subject: [PATCH 12/91] fix: #244 optimize polyfill --- app/components/home.tsx | 33 ++++++++++++++++++++------------- app/page.tsx | 3 +++ app/requests.ts | 4 ---- app/store/app.ts | 17 ++++++----------- package.json | 2 +- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/app/components/home.tsx b/app/components/home.tsx index 7ed35dfb..8e4013e2 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -21,7 +21,13 @@ import CloseIcon from "../icons/close.svg"; import CopyIcon from "../icons/copy.svg"; import DownloadIcon from "../icons/download.svg"; -import { Message, SubmitKey, useChatStore, ChatSession } from "../store"; +import { + Message, + SubmitKey, + useChatStore, + ChatSession, + BOT_HELLO, +} from "../store"; import { showModal, showToast } from "./ui-lib"; import { copyToClipboard, @@ -307,18 +313,19 @@ export function Chat(props: { }, ] : [], - ).concat( - userInput.length > 0 && config.sendPreviewBubble - ? [ - { - role: "user", - content: userInput, - date: new Date().toLocaleString(), - preview: false, - }, - ] - : [], - ); + ) + .concat( + userInput.length > 0 && config.sendPreviewBubble + ? [ + { + role: "user", + content: userInput, + date: new Date().toLocaleString(), + preview: false, + }, + ] + : [], + ); // auto scroll useLayoutEffect(() => { diff --git a/app/page.tsx b/app/page.tsx index 54300e71..2ad763ce 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,7 @@ import { Analytics } from "@vercel/analytics/react"; + +import "array.prototype.at"; + import { Home } from "./components/home"; export default function App() { diff --git a/app/requests.ts b/app/requests.ts index a8ba4e9f..0be9dbf7 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -2,10 +2,6 @@ import type { ChatRequest, ChatReponse } from "./api/openai/typing"; import { filterConfig, Message, ModelConfig, useAccessStore } from "./store"; import Locale from "./locales"; -if (!Array.prototype.at) { - require("array.prototype.at/auto"); -} - const TIME_OUT_MS = 30000; const makeRequestParam = ( diff --git a/app/store/app.ts b/app/store/app.ts index e6327723..7c2b57f1 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -11,10 +11,6 @@ import { trimTopic } from "../utils"; import Locale from "../locales"; -if (!Array.prototype.at) { - require("array.prototype.at/auto"); -} - export type Message = ChatCompletionResponseMessage & { date: string; streaming?: boolean; @@ -162,6 +158,11 @@ export interface ChatSession { } const DEFAULT_TOPIC = Locale.Store.DefaultTopic; +export const BOT_HELLO = { + role: "assistant", + content: Locale.Store.BotHello, + date: "", +}; function createEmptySession(): ChatSession { const createDate = new Date().toLocaleString(); @@ -170,13 +171,7 @@ function createEmptySession(): ChatSession { id: Date.now(), topic: DEFAULT_TOPIC, memoryPrompt: "", - messages: [ - { - role: "assistant", - content: Locale.Store.BotHello, - date: createDate, - }, - ], + messages: [], stat: { tokenCount: 0, wordCount: 0, diff --git a/package.json b/package.json index 7c6832ed..b67d7b0d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dependencies": { "@svgr/webpack": "^6.5.1", "@vercel/analytics": "^0.1.11", + "array.prototype.at": "^1.1.1", "emoji-picker-react": "^4.4.7", "eventsource-parser": "^0.1.0", "fuse.js": "^6.6.2", @@ -39,7 +40,6 @@ "@types/react-dom": "^18.0.11", "@types/react-katex": "^3.0.0", "@types/spark-md5": "^3.0.2", - "array.prototype.at": "^1.1.1", "cross-env": "^7.0.3", "eslint": "^8.36.0", "eslint-config-next": "13.2.3", From 7b5af271d501b2c8d85f438dfa358913b8da81ac Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Sun, 2 Apr 2023 14:22:06 +0000 Subject: [PATCH 13/91] fix: #367 failed to fetch account usage --- app/components/settings.tsx | 12 ++++-------- app/locales/cn.ts | 4 ++-- app/locales/en.ts | 4 ++-- app/locales/es.ts | 4 ++-- app/locales/tw.ts | 4 ++-- app/requests.ts | 19 ++++++++++++++----- 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 8f015006..43959698 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -72,7 +72,6 @@ export function Settings(props: { closeSettings: () => void }) { } const [usage, setUsage] = useState<{ - granted?: number; used?: number; }>(); const [loadingUsage, setLoadingUsage] = useState(false); @@ -81,8 +80,7 @@ export function Settings(props: { closeSettings: () => void }) { requestUsage() .then((res) => setUsage({ - granted: res?.total_granted, - used: res?.total_used, + used: res, }), ) .finally(() => { @@ -285,7 +283,8 @@ export function Settings(props: { closeSettings: () => void }) { checked={config.sendPreviewBubble} onChange={(e) => updateConfig( - (config) => (config.sendPreviewBubble = e.currentTarget.checked), + (config) => + (config.sendPreviewBubble = e.currentTarget.checked), ) } > @@ -360,10 +359,7 @@ export function Settings(props: { closeSettings: () => void }) { subTitle={ loadingUsage ? Locale.Settings.Usage.IsChecking - : Locale.Settings.Usage.SubTitle( - usage?.granted ?? "[?]", - usage?.used ?? "[?]", - ) + : Locale.Settings.Usage.SubTitle(usage?.used ?? "[?]") } > {loadingUsage ? ( diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 66436e12..62be467b 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -103,8 +103,8 @@ const cn = { }, Usage: { Title: "账户余额", - SubTitle(granted: any, used: any) { - return `总共 $${granted},已使用 $${used}`; + SubTitle(used: any) { + return `本月已使用 $${used}`; }, IsChecking: "正在检查…", Check: "重新检查", diff --git a/app/locales/en.ts b/app/locales/en.ts index 55884308..98fa7404 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -105,8 +105,8 @@ const en: LocaleType = { }, Usage: { Title: "Account Balance", - SubTitle(granted: any, used: any) { - return `Total $${granted}, Used $${used}`; + SubTitle(used: any) { + return `Used this month $${used}`; }, IsChecking: "Checking...", Check: "Check Again", diff --git a/app/locales/es.ts b/app/locales/es.ts index a78bf1aa..fca7202d 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -105,8 +105,8 @@ const es: LocaleType = { }, Usage: { Title: "Saldo de la cuenta", - SubTitle(granted: any, used: any) { - return `Total $${granted}, Usado $${used}`; + SubTitle(used: any) { + return `Usado $${used}`; }, IsChecking: "Comprobando...", Check: "Comprobar de nuevo", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 7137e884..27156283 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -103,8 +103,8 @@ const tw: LocaleType = { }, Usage: { Title: "帳戶餘額", - SubTitle(granted: any, used: any) { - return `總共 $${granted},已使用 $${used}`; + SubTitle(used: any) { + return `本月已使用 $${used}`; }, IsChecking: "正在檢查…", Check: "重新檢查", diff --git a/app/requests.ts b/app/requests.ts index 0be9dbf7..cf2ac7f7 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -48,6 +48,7 @@ export function requestOpenaiClient(path: string) { method, headers: { "Content-Type": "application/json", + "Cache-Control": "no-cache", path, ...getHeaders(), }, @@ -69,17 +70,25 @@ export async function requestChat(messages: Message[]) { } export async function requestUsage() { + const formatDate = (d: Date) => + `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d + .getDate() + .toString() + .padStart(2, "0")}`; + const ONE_DAY = 24 * 60 * 60 * 1000; + const now = new Date(Date.now() + ONE_DAY); + const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + const startDate = formatDate(startOfMonth); + const endDate = formatDate(now); const res = await requestOpenaiClient( - "dashboard/billing/credit_grants?_vercel_no_cache=1", + `dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, )(null, "GET"); try { const response = (await res.json()) as { - total_available: number; - total_granted: number; - total_used: number; + total_usage: number; }; - return response; + return Math.round(response.total_usage) / 100; } catch (error) { console.error("[Request usage] ", error, res.body); } From 4f0108b0eaa3fb1f06e3227c7f3ae9d22306621a Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Sun, 2 Apr 2023 14:48:18 +0000 Subject: [PATCH 14/91] fix: #289 use highlight.js instead of prism --- app/components/markdown.tsx | 38 ++++++++++- app/layout.tsx | 2 +- app/styles/globals.scss | 1 + app/styles/highlight.scss | 114 +++++++++++++++++++++++++++++++++ app/styles/prism.scss | 122 ------------------------------------ package.json | 2 +- yarn.lock | 39 +++++++++++- 7 files changed, 190 insertions(+), 128 deletions(-) create mode 100644 app/styles/highlight.scss delete mode 100644 app/styles/prism.scss diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index 6d3cd0bf..89492612 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -4,8 +4,8 @@ import RemarkMath from "remark-math"; import RemarkBreaks from "remark-breaks"; import RehypeKatex from "rehype-katex"; import RemarkGfm from "remark-gfm"; -import RehypePrsim from "rehype-prism-plus"; -import { useRef } from "react"; +import RehypeHighlight from "rehype-highlight"; +import { useRef, useState, RefObject, useEffect } from "react"; import { copyToClipboard } from "../utils"; export function PreCode(props: { children: any }) { @@ -27,11 +27,43 @@ export function PreCode(props: { children: any }) { ); } +const useLazyLoad = (ref: RefObject): boolean => { + const [isIntersecting, setIntersecting] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver(([entry]) => { + if (entry.isIntersecting) { + setIntersecting(true); + observer.disconnect(); + } + }); + + if (ref.current) { + observer.observe(ref.current); + } + + return () => { + observer.disconnect(); + }; + }, [ref]); + + return isIntersecting; +}; + export function Markdown(props: { content: string }) { return ( + License: see project LICENSE + Touched: 2022 +*/ + .hljs-comment, + .hljs-meta { + color: #565f89; + } + + .hljs-deletion, + .hljs-doctag, + .hljs-regexp, + .hljs-selector-attr, + .hljs-selector-class, + .hljs-selector-id, + .hljs-selector-pseudo, + .hljs-tag, + .hljs-template-tag, + .hljs-variable.language_ { + color: #f7768e; + } + + .hljs-link, + .hljs-literal, + .hljs-number, + .hljs-params, + .hljs-template-variable, + .hljs-type, + .hljs-variable { + color: #ff9e64; + } + + .hljs-attribute, + .hljs-built_in { + color: #e0af68; + } + + .hljs-keyword, + .hljs-property, + .hljs-subst, + .hljs-title, + .hljs-title.class_, + .hljs-title.class_.inherited__, + .hljs-title.function_ { + color: #7dcfff; + } + + .hljs-selector-tag { + color: #73daca; + } + + .hljs-addition, + .hljs-bullet, + .hljs-quote, + .hljs-string, + .hljs-symbol { + color: #9ece6a; + } + + .hljs-code, + .hljs-formula, + .hljs-section { + color: #7aa2f7; + } + + .hljs-attr, + .hljs-char.escape_, + .hljs-keyword, + .hljs-name, + .hljs-operator { + color: #bb9af7; + } + + .hljs-punctuation { + color: #c0caf5; + } + + .hljs { + background: #1a1b26; + color: #9aa5ce; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: 700; + } +} diff --git a/app/styles/prism.scss b/app/styles/prism.scss deleted file mode 100644 index 65ee8b5f..00000000 --- a/app/styles/prism.scss +++ /dev/null @@ -1,122 +0,0 @@ -.markdown-body { - pre { - background: #282a36; - color: #f8f8f2; - } - - code[class*="language-"], - pre[class*="language-"] { - color: #f8f8f2; - background: none; - text-shadow: 0 1px rgba(0, 0, 0, 0.3); - font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; - } - - /* Code blocks */ - pre[class*="language-"] { - padding: 1em; - margin: 0.5em 0; - overflow: auto; - border-radius: 0.3em; - } - - :not(pre) > code[class*="language-"], - pre[class*="language-"] { - background: #282a36; - } - - /* Inline code */ - :not(pre) > code[class*="language-"] { - padding: 0.1em; - border-radius: 0.3em; - white-space: normal; - } - - .token.comment, - .token.prolog, - .token.doctype, - .token.cdata { - color: #6272a4; - } - - .token.punctuation { - color: #f8f8f2; - } - - .namespace { - opacity: 0.7; - } - - .token.property, - .token.tag, - .token.constant, - .token.symbol, - .token.deleted { - color: #ff79c6; - } - - .token.boolean, - .token.number { - color: #bd93f9; - } - - .token.selector, - .token.attr-name, - .token.string, - .token.char, - .token.builtin, - .token.inserted { - color: #50fa7b; - } - - .token.operator, - .token.entity, - .token.url, - .language-css .token.string, - .style .token.string, - .token.variable { - color: #f8f8f2; - } - - .token.atrule, - .token.attr-value, - .token.function, - .token.class-name { - color: #f1fa8c; - } - - .token.keyword { - color: #8be9fd; - } - - .token.regex, - .token.important { - color: #ffb86c; - } - - .token.important, - .token.bold { - font-weight: bold; - } - - .token.italic { - font-style: italic; - } - - .token.entity { - cursor: help; - } -} diff --git a/package.json b/package.json index b67d7b0d..2e018647 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", + "rehype-highlight": "^6.0.0", "rehype-katex": "^6.0.2", - "rehype-prism-plus": "^1.5.1", "remark-breaks": "^3.0.2", "remark-gfm": "^3.0.1", "remark-math": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 246b818b..fd26bb00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2548,6 +2548,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fault@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c" + integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ== + dependencies: + format "^0.2.0" + fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -2612,6 +2619,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + formdata-polyfill@^4.0.10: version "4.0.10" resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" @@ -2874,7 +2886,7 @@ hast-util-to-string@^2.0.0: dependencies: "@types/hast" "^2.0.0" -hast-util-to-text@^3.1.0: +hast-util-to-text@^3.0.0, hast-util-to-text@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-3.1.2.tgz#ecf30c47141f41e91a5d32d0b1e1859fd2ac04f2" integrity sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw== @@ -2900,6 +2912,11 @@ hastscript@^7.0.0: property-information "^6.0.0" space-separated-tokens "^2.0.0" +highlight.js@~11.7.0: + version "11.7.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e" + integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ== + human-signals@^4.3.0: version "4.3.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" @@ -3385,6 +3402,15 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lowlight@^2.0.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-2.8.1.tgz#5f54016ebd1b2f66b3d0b94d10ef6dd5df4f2e42" + integrity sha512-HCaGL61RKc1MYzEYn3rFoGkK0yslzCVDFJEanR19rc2L0mb8i58XM55jSRbzp9jcQrFzschPlwooC0vuNitk8Q== + dependencies: + "@types/hast" "^2.0.0" + fault "^2.0.0" + highlight.js "~11.7.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -4374,6 +4400,17 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" +rehype-highlight@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/rehype-highlight/-/rehype-highlight-6.0.0.tgz#8097219d8813b51f4c2b6d92db27dac6cbc9a641" + integrity sha512-q7UtlFicLhetp7K48ZgZiJgchYscMma7XjzX7t23bqEJF8m6/s+viXQEe4oHjrATTIZpX7RG8CKD7BlNZoh9gw== + dependencies: + "@types/hast" "^2.0.0" + hast-util-to-text "^3.0.0" + lowlight "^2.0.0" + unified "^10.0.0" + unist-util-visit "^4.0.0" + rehype-katex@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/rehype-katex/-/rehype-katex-6.0.2.tgz#20197bbc10bdf79f6b999bffa6689d7f17226c35" From 6c1862797bb6d27c271d3cf0a3f80937e6f0c361 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Sun, 2 Apr 2023 15:05:54 +0000 Subject: [PATCH 15/91] refactor: split homt.tsx components --- app/components/chat-list.tsx | 69 +++++ app/components/chat.tsx | 499 ++++++++++++++++++++++++++++++++ app/components/home.tsx | 533 +---------------------------------- app/components/settings.tsx | 2 +- 4 files changed, 572 insertions(+), 531 deletions(-) create mode 100644 app/components/chat-list.tsx create mode 100644 app/components/chat.tsx diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx new file mode 100644 index 00000000..5a74ff15 --- /dev/null +++ b/app/components/chat-list.tsx @@ -0,0 +1,69 @@ +import { useState, useRef, useEffect, useLayoutEffect } from "react"; +import DeleteIcon from "../icons/delete.svg"; +import styles from "./home.module.scss"; + +import { + Message, + SubmitKey, + useChatStore, + ChatSession, + BOT_HELLO, +} from "../store"; + +import Locale from "../locales"; + +export function ChatItem(props: { + onClick?: () => void; + onDelete?: () => void; + title: string; + count: number; + time: string; + selected: boolean; +}) { + return ( +
+
{props.title}
+
+
+ {Locale.ChatItem.ChatItemCount(props.count)} +
+
{props.time}
+
+
+ +
+
+ ); +} + +export function ChatList() { + const [sessions, selectedIndex, selectSession, removeSession] = useChatStore( + (state) => [ + state.sessions, + state.currentSessionIndex, + state.selectSession, + state.removeSession, + ], + ); + + return ( +
+ {sessions.map((item, i) => ( + selectSession(i)} + onDelete={() => confirm(Locale.Home.DeleteChat) && removeSession(i)} + /> + ))} +
+ ); +} diff --git a/app/components/chat.tsx b/app/components/chat.tsx new file mode 100644 index 00000000..348fe2ea --- /dev/null +++ b/app/components/chat.tsx @@ -0,0 +1,499 @@ +import { useDebouncedCallback } from "use-debounce"; +import { useState, useRef, useEffect, useLayoutEffect } from "react"; + +import SendWhiteIcon from "../icons/send-white.svg"; +import BrainIcon from "../icons/brain.svg"; +import ExportIcon from "../icons/export.svg"; +import MenuIcon from "../icons/menu.svg"; +import CopyIcon from "../icons/copy.svg"; +import DownloadIcon from "../icons/download.svg"; +import LoadingIcon from "../icons/three-dots.svg"; +import BotIcon from "../icons/bot.svg"; + +import { + Message, + SubmitKey, + useChatStore, + ChatSession, + BOT_HELLO, +} from "../store"; + +import { + copyToClipboard, + downloadAs, + isMobileScreen, + selectOrCopy, +} from "../utils"; + +import dynamic from "next/dynamic"; + +import { ControllerPool } from "../requests"; +import { Prompt, usePromptStore } from "../store/prompt"; +import Locale from "../locales"; + +import { IconButton } from "./button"; +import styles from "./home.module.scss"; + +import { showModal, showToast } from "./ui-lib"; + +const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { + loading: () => , +}); + +const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, { + loading: () => , +}); + +export function Avatar(props: { role: Message["role"] }) { + const config = useChatStore((state) => state.config); + + if (props.role === "assistant") { + return ; + } + + return ( +
+ +
+ ); +} + +function exportMessages(messages: Message[], topic: string) { + const mdText = + `# ${topic}\n\n` + + messages + .map((m) => { + return m.role === "user" ? `## ${m.content}` : m.content.trim(); + }) + .join("\n\n"); + const filename = `${topic}.md`; + + showModal({ + title: Locale.Export.Title, + children: ( +
+
{mdText}
+
+ ), + actions: [ + } + bordered + text={Locale.Export.Copy} + onClick={() => copyToClipboard(mdText)} + />, + } + bordered + text={Locale.Export.Download} + onClick={() => downloadAs(mdText, filename)} + />, + ], + }); +} + +function showMemoryPrompt(session: ChatSession) { + showModal({ + title: `${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`, + children: ( +
+
+          {session.memoryPrompt || Locale.Memory.EmptyContent}
+        
+
+ ), + actions: [ + } + bordered + text={Locale.Memory.Copy} + onClick={() => copyToClipboard(session.memoryPrompt)} + />, + ], + }); +} + +function useSubmitHandler() { + const config = useChatStore((state) => state.config); + const submitKey = config.submitKey; + + const shouldSubmit = (e: React.KeyboardEvent) => { + if (e.key !== "Enter") return false; + if (e.key === "Enter" && e.nativeEvent.isComposing) return false; + return ( + (config.submitKey === SubmitKey.AltEnter && e.altKey) || + (config.submitKey === SubmitKey.CtrlEnter && e.ctrlKey) || + (config.submitKey === SubmitKey.ShiftEnter && e.shiftKey) || + (config.submitKey === SubmitKey.MetaEnter && e.metaKey) || + (config.submitKey === SubmitKey.Enter && + !e.altKey && + !e.ctrlKey && + !e.shiftKey && + !e.metaKey) + ); + }; + + return { + submitKey, + shouldSubmit, + }; +} + +export function PromptHints(props: { + prompts: Prompt[]; + onPromptSelect: (prompt: Prompt) => void; +}) { + if (props.prompts.length === 0) return null; + + return ( +
+ {props.prompts.map((prompt, i) => ( +
props.onPromptSelect(prompt)} + > +
{prompt.title}
+
{prompt.content}
+
+ ))} +
+ ); +} + +export function Chat(props: { + showSideBar?: () => void; + sideBarShowing?: boolean; +}) { + type RenderMessage = Message & { preview?: boolean }; + + const chatStore = useChatStore(); + const [session, sessionIndex] = useChatStore((state) => [ + state.currentSession(), + state.currentSessionIndex, + ]); + const fontSize = useChatStore((state) => state.config.fontSize); + + const inputRef = useRef(null); + const [userInput, setUserInput] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const { submitKey, shouldSubmit } = useSubmitHandler(); + + // prompt hints + const promptStore = usePromptStore(); + const [promptHints, setPromptHints] = useState([]); + const onSearch = useDebouncedCallback( + (text: string) => { + setPromptHints(promptStore.search(text)); + }, + 100, + { leading: true, trailing: true }, + ); + + const onPromptSelect = (prompt: Prompt) => { + setUserInput(prompt.content); + setPromptHints([]); + inputRef.current?.focus(); + }; + + const scrollInput = () => { + const dom = inputRef.current; + if (!dom) return; + const paddingBottomNum: number = parseInt( + window.getComputedStyle(dom).paddingBottom, + 10, + ); + dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum; + }; + + // only search prompts when user input is short + const SEARCH_TEXT_LIMIT = 30; + const onInput = (text: string) => { + scrollInput(); + setUserInput(text); + const n = text.trim().length; + + // clear search results + if (n === 0) { + setPromptHints([]); + } else if (!chatStore.config.disablePromptHint && n < SEARCH_TEXT_LIMIT) { + // check if need to trigger auto completion + if (text.startsWith("/") && text.length > 1) { + onSearch(text.slice(1)); + } + } + }; + + // submit user input + const onUserSubmit = () => { + if (userInput.length <= 0) return; + setIsLoading(true); + chatStore.onUserInput(userInput).then(() => setIsLoading(false)); + setUserInput(""); + setPromptHints([]); + inputRef.current?.focus(); + }; + + // stop response + const onUserStop = (messageIndex: number) => { + console.log(ControllerPool, sessionIndex, messageIndex); + ControllerPool.stop(sessionIndex, messageIndex); + }; + + // check if should send message + const onInputKeyDown = (e: React.KeyboardEvent) => { + if (shouldSubmit(e)) { + onUserSubmit(); + e.preventDefault(); + } + }; + const onRightClick = (e: any, message: Message) => { + // auto fill user input + if (message.role === "user") { + setUserInput(message.content); + } + + // copy to clipboard + if (selectOrCopy(e.currentTarget, message.content)) { + e.preventDefault(); + } + }; + + const onResend = (botIndex: number) => { + // find last user input message and resend + for (let i = botIndex; i >= 0; i -= 1) { + if (messages[i].role === "user") { + setIsLoading(true); + chatStore + .onUserInput(messages[i].content) + .then(() => setIsLoading(false)); + inputRef.current?.focus(); + return; + } + } + }; + + // for auto-scroll + const latestMessageRef = useRef(null); + const [autoScroll, setAutoScroll] = useState(true); + + const config = useChatStore((state) => state.config); + + // preview messages + const messages = (session.messages as RenderMessage[]) + .concat( + isLoading + ? [ + { + role: "assistant", + content: "……", + date: new Date().toLocaleString(), + preview: true, + }, + ] + : [], + ) + .concat( + userInput.length > 0 && config.sendPreviewBubble + ? [ + { + role: "user", + content: userInput, + date: new Date().toLocaleString(), + preview: false, + }, + ] + : [], + ); + + // auto scroll + useLayoutEffect(() => { + setTimeout(() => { + const dom = latestMessageRef.current; + const inputDom = inputRef.current; + + // only scroll when input overlaped message body + let shouldScroll = true; + if (dom && inputDom) { + const domRect = dom.getBoundingClientRect(); + const inputRect = inputDom.getBoundingClientRect(); + shouldScroll = domRect.top > inputRect.top; + } + + if (dom && autoScroll && shouldScroll) { + dom.scrollIntoView({ + block: "end", + }); + } + }, 500); + }); + + return ( +
+
+
+
{ + const newTopic = prompt(Locale.Chat.Rename, session.topic); + if (newTopic && newTopic !== session.topic) { + chatStore.updateCurrentSession( + (session) => (session.topic = newTopic!), + ); + } + }} + > + {session.topic} +
+
+ {Locale.Chat.SubTitle(session.messages.length)} +
+
+
+
+ } + bordered + title={Locale.Chat.Actions.ChatList} + onClick={props?.showSideBar} + /> +
+
+ } + bordered + title={Locale.Chat.Actions.CompressedHistory} + onClick={() => { + showMemoryPrompt(session); + }} + /> +
+
+ } + bordered + title={Locale.Chat.Actions.Export} + onClick={() => { + exportMessages(session.messages, session.topic); + }} + /> +
+
+
+ +
+ {messages.map((message, i) => { + const isUser = message.role === "user"; + + return ( +
+
+
+ +
+ {(message.preview || message.streaming) && ( +
+ {Locale.Chat.Typing} +
+ )} +
+ {!isUser && + !(message.preview || message.content.length === 0) && ( +
+ {message.streaming ? ( +
onUserStop(i)} + > + {Locale.Chat.Actions.Stop} +
+ ) : ( +
onResend(i)} + > + {Locale.Chat.Actions.Retry} +
+ )} + +
copyToClipboard(message.content)} + > + {Locale.Chat.Actions.Copy} +
+
+ )} + {(message.preview || message.content.length === 0) && + !isUser ? ( + + ) : ( +
onRightClick(e, message)} + onDoubleClickCapture={() => { + if (!isMobileScreen()) return; + setUserInput(message.content); + }} + > + +
+ )} +
+ {!isUser && !message.preview && ( +
+
+ {message.date.toLocaleString()} +
+
+ )} +
+
+ ); + })} +
+ - +
+
+ +
+ +
+ + ); +} From c2b37f811bcfb41001dab787f11e493aba45b9a3 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 02:37:12 +0800 Subject: [PATCH 68/91] feat: close #469 support reset session and do not send memory --- README.md | 1 - app/components/chat.module.scss | 8 ++++++++ app/components/chat.tsx | 30 ++++++++++++++++++++++++++++-- app/locales/cn.ts | 5 ++++- app/locales/en.ts | 6 +++++- app/locales/es.ts | 4 ++++ app/locales/it.ts | 4 ++++ app/locales/tw.ts | 3 +++ app/store/app.ts | 22 ++++++++++++++++++++-- app/styles/globals.scss | 4 ++++ 10 files changed, 80 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index edf7f1db..d47a3895 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,6 @@ One-Click to deploy your own ChatGPT web UI. - 用户登录、账号管理、消息云同步 - ## Get Started > [简体中文 > 如何开始使用](./README_CN.md#开始使用) diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index 5216fb25..f57e6c10 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -63,6 +63,14 @@ font-size: 12px; font-weight: bold; margin-bottom: 10px; + display: flex; + justify-content: space-between; + align-items: center; + + .memory-prompt-action { + display: flex; + align-items: center; + } } .memory-prompt-content { diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 03f5de67..dc746e24 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -144,6 +144,16 @@ function PromptToast(props: { title={Locale.Context.Edit} onClose={() => props.setShowModal(false)} actions={[ + } + bordered + text={Locale.Memory.Reset} + onClick={() => + confirm(Locale.Memory.ResetConfirm) && + chatStore.resetSession() + } + />, } @@ -212,8 +222,24 @@ function PromptToast(props: {
- {Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "} - {session.messages.length}) + + {Locale.Memory.Title} ({session.lastSummarizeIndex} of{" "} + {session.messages.length}) + + +
{session.memoryPrompt || Locale.Memory.EmptyContent} diff --git a/app/locales/cn.ts b/app/locales/cn.ts index e0adb6dc..64f04626 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -39,7 +39,10 @@ const cn = { Memory: { Title: "历史记忆", EmptyContent: "尚未记忆", - Copy: "全部复制", + Send: "发送记忆", + Copy: "复制记忆", + Reset: "重置对话", + ResetConfirm: "重置后将清空当前对话记录以及历史记忆,确认重置?", }, Home: { NewChat: "新的聊天", diff --git a/app/locales/en.ts b/app/locales/en.ts index af820150..add9e1c7 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -41,7 +41,11 @@ const en: LocaleType = { Memory: { Title: "Memory Prompt", EmptyContent: "Nothing yet.", - Copy: "Copy All", + Send: "Send Memory", + Copy: "Copy Memory", + Reset: "Reset Session", + ResetConfirm: + "Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?", }, Home: { NewChat: "New Chat", diff --git a/app/locales/es.ts b/app/locales/es.ts index 5c73b608..b3af1f03 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -42,6 +42,10 @@ const es: LocaleType = { Title: "Historial de memoria", EmptyContent: "Aún no hay nada.", Copy: "Copiar todo", + Send: "Send Memory", + Reset: "Reset Session", + ResetConfirm: + "Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?", }, Home: { NewChat: "Nuevo chat", diff --git a/app/locales/it.ts b/app/locales/it.ts index ce87796d..82afb818 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -42,6 +42,10 @@ const it: LocaleType = { Title: "Prompt di memoria", EmptyContent: "Vuoto.", Copy: "Copia tutto", + Send: "Send Memory", + Reset: "Reset Session", + ResetConfirm: + "Resetting will clear the current conversation history and historical memory. Are you sure you want to reset?", }, Home: { NewChat: "Nuova Chat", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index eaab03fc..237f3fbe 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -41,6 +41,9 @@ const tw: LocaleType = { Title: "上下文記憶 Prompt", EmptyContent: "尚未記憶", Copy: "複製全部", + Send: "發送記憶", + Reset: "重置對話", + ResetConfirm: "重置後將清空當前對話記錄以及歷史記憶,確認重置?", }, Home: { NewChat: "新的對話", diff --git a/app/store/app.ts b/app/store/app.ts index d01e3cdd..9f1e8e88 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -149,6 +149,7 @@ export interface ChatStat { export interface ChatSession { id: number; topic: string; + sendMemory: boolean; memoryPrompt: string; context: Message[]; messages: Message[]; @@ -170,6 +171,7 @@ function createEmptySession(): ChatSession { return { id: Date.now(), topic: DEFAULT_TOPIC, + sendMemory: true, memoryPrompt: "", context: [], messages: [], @@ -202,6 +204,7 @@ interface ChatStore { messageIndex: number, updater: (message?: Message) => void, ) => void; + resetSession: () => void; getMessagesWithMemory: () => Message[]; getMemoryPrompt: () => Message; @@ -391,7 +394,11 @@ export const useChatStore = create()( const context = session.context.slice(); - if (session.memoryPrompt && session.memoryPrompt.length > 0) { + if ( + session.sendMemory && + session.memoryPrompt && + session.memoryPrompt.length > 0 + ) { const memoryPrompt = get().getMemoryPrompt(); context.push(memoryPrompt); } @@ -415,6 +422,13 @@ export const useChatStore = create()( set(() => ({ sessions })); }, + resetSession() { + get().updateCurrentSession((session) => { + session.messages = []; + session.memoryPrompt = ""; + }); + }, + summarizeSession() { const session = get().currentSession(); @@ -506,7 +520,7 @@ export const useChatStore = create()( }), { name: LOCAL_KEY, - version: 1.1, + version: 1.2, migrate(persistedState, version) { const state = persistedState as ChatStore; @@ -514,6 +528,10 @@ export const useChatStore = create()( state.sessions.forEach((s) => (s.context = [])); } + if (version < 1.2) { + state.sessions.forEach((s) => (s.sendMemory = true)); + } + return state; }, }, diff --git a/app/styles/globals.scss b/app/styles/globals.scss index 48f56995..6492b000 100644 --- a/app/styles/globals.scss +++ b/app/styles/globals.scss @@ -126,6 +126,10 @@ select { text-align: center; } +label { + cursor: pointer; +} + input { text-align: center; } From f920b2001d4da22b0872580cecdbd86a08380f57 Mon Sep 17 00:00:00 2001 From: xiaotianxt Date: Thu, 6 Apr 2023 02:41:27 +0800 Subject: [PATCH 69/91] performance: introduce lazy-loading for ChatList Reduce the first load JS bundle size using next/dynamic. --- app/components/home.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/components/home.tsx b/app/components/home.tsx index 66f86348..9e57cb87 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -19,7 +19,6 @@ import CloseIcon from "../icons/close.svg"; import { useChatStore } from "../store"; import { isMobileScreen } from "../utils"; import Locale from "../locales"; -import { ChatList } from "./chat-list"; import { Chat } from "./chat"; import dynamic from "next/dynamic"; @@ -39,6 +38,10 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, { loading: () => , }); +const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { + loading: () => , +}); + function useSwitchTheme() { const config = useChatStore((state) => state.config); From 8e560d2b2eec503ae43685a5a23f0c726eb9ae58 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 03:19:33 +0800 Subject: [PATCH 70/91] fix: #410 can not stop response --- app/api/chat-stream/route.ts | 3 +++ app/components/chat.tsx | 31 +++++++++++++++++++--------- app/requests.ts | 13 ++++++------ app/store/app.ts | 39 +++++++++++++++++++++++------------- scripts/setup.sh | 3 ++- 5 files changed, 57 insertions(+), 32 deletions(-) diff --git a/app/api/chat-stream/route.ts b/app/api/chat-stream/route.ts index f3317554..526623ce 100644 --- a/app/api/chat-stream/route.ts +++ b/app/api/chat-stream/route.ts @@ -53,6 +53,9 @@ export async function POST(req: NextRequest) { return new Response(stream); } catch (error) { console.error("[Chat Stream]", error); + return new Response( + ["```json\n", JSON.stringify(error, null, " "), "\n```"].join(""), + ); } } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index dc746e24..ea1eb7e2 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -12,7 +12,14 @@ import BotIcon from "../icons/bot.svg"; import AddIcon from "../icons/add.svg"; import DeleteIcon from "../icons/delete.svg"; -import { Message, SubmitKey, useChatStore, BOT_HELLO, ROLES } from "../store"; +import { + Message, + SubmitKey, + useChatStore, + BOT_HELLO, + ROLES, + createMessage, +} from "../store"; import { copyToClipboard, @@ -407,8 +414,8 @@ export function Chat(props: { }; // stop response - const onUserStop = (messageIndex: number) => { - ControllerPool.stop(sessionIndex, messageIndex); + const onUserStop = (messageId: number) => { + ControllerPool.stop(sessionIndex, messageId); }; // check if should send message @@ -439,6 +446,7 @@ export function Chat(props: { .onUserInput(messages[i].content) .then(() => setIsLoading(false)); inputRef.current?.focus(); + messages.splice(i, 2); return; } } @@ -462,9 +470,10 @@ export function Chat(props: { isLoading ? [ { - role: "assistant", - content: "……", - date: new Date().toLocaleString(), + ...createMessage({ + role: "assistant", + content: "……", + }), preview: true, }, ] @@ -474,9 +483,10 @@ export function Chat(props: { userInput.length > 0 && config.sendPreviewBubble ? [ { - role: "user", - content: userInput, - date: new Date().toLocaleString(), + ...createMessage({ + role: "user", + content: userInput, + }), preview: true, }, ] @@ -489,6 +499,7 @@ export function Chat(props: { useEffect(() => { if (props.sideBarShowing && isMobileScreen()) return; inputRef.current?.focus(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( @@ -592,7 +603,7 @@ export function Chat(props: { {message.streaming ? (
onUserStop(i)} + onClick={() => onUserStop(message.id ?? i)} > {Locale.Chat.Actions.Stop}
diff --git a/app/requests.ts b/app/requests.ts index da9b5c97..c60efc95 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -204,23 +204,22 @@ export const ControllerPool = { addController( sessionIndex: number, - messageIndex: number, + messageId: number, controller: AbortController, ) { - const key = this.key(sessionIndex, messageIndex); + const key = this.key(sessionIndex, messageId); this.controllers[key] = controller; return key; }, - stop(sessionIndex: number, messageIndex: number) { - const key = this.key(sessionIndex, messageIndex); + stop(sessionIndex: number, messageId: number) { + const key = this.key(sessionIndex, messageId); const controller = this.controllers[key]; - console.log(controller); controller?.abort(); }, - remove(sessionIndex: number, messageIndex: number) { - const key = this.key(sessionIndex, messageIndex); + remove(sessionIndex: number, messageId: number) { + const key = this.key(sessionIndex, messageId); delete this.controllers[key]; }, diff --git a/app/store/app.ts b/app/store/app.ts index 9f1e8e88..64faa31d 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -15,8 +15,19 @@ export type Message = ChatCompletionResponseMessage & { date: string; streaming?: boolean; isError?: boolean; + id?: number; }; +export function createMessage(override: Partial): Message { + return { + id: Date.now(), + date: new Date().toLocaleString(), + role: "user", + content: "", + ...override, + }; +} + export enum SubmitKey { Enter = "Enter", CtrlEnter = "Ctrl + Enter", @@ -159,11 +170,10 @@ export interface ChatSession { } const DEFAULT_TOPIC = Locale.Store.DefaultTopic; -export const BOT_HELLO: Message = { +export const BOT_HELLO: Message = createMessage({ role: "assistant", content: Locale.Store.BotHello, - date: "", -}; +}); function createEmptySession(): ChatSession { const createDate = new Date().toLocaleString(); @@ -311,18 +321,15 @@ export const useChatStore = create()( }, async onUserInput(content) { - const userMessage: Message = { + const userMessage: Message = createMessage({ role: "user", content, - date: new Date().toLocaleString(), - }; + }); - const botMessage: Message = { - content: "", + const botMessage: Message = createMessage({ role: "assistant", - date: new Date().toLocaleString(), streaming: true, - }; + }); // get recent messages const recentMessages = get().getMessagesWithMemory(); @@ -345,7 +352,10 @@ export const useChatStore = create()( botMessage.streaming = false; botMessage.content = content; get().onNewMessage(botMessage); - ControllerPool.remove(sessionIndex, messageIndex); + ControllerPool.remove( + sessionIndex, + botMessage.id ?? messageIndex, + ); } else { botMessage.content = content; set(() => ({})); @@ -361,13 +371,13 @@ export const useChatStore = create()( userMessage.isError = true; botMessage.isError = true; set(() => ({})); - ControllerPool.remove(sessionIndex, messageIndex); + ControllerPool.remove(sessionIndex, botMessage.id ?? messageIndex); }, onController(controller) { // collect controller for stop/retry ControllerPool.addController( sessionIndex, - messageIndex, + botMessage.id ?? messageIndex, controller, ); }, @@ -441,7 +451,8 @@ export const useChatStore = create()( requestWithPrompt(session.messages, Locale.Store.Prompt.Topic).then( (res) => { get().updateCurrentSession( - (session) => (session.topic = trimTopic(res)), + (session) => + (session.topic = res ? trimTopic(res) : DEFAULT_TOPIC), ); }, ); diff --git a/scripts/setup.sh b/scripts/setup.sh index 63a28bf0..b9653339 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -61,4 +61,5 @@ read -p "Enter CODE: " CODE read -p "Enter PORT: " PORT # Build and run the project using the environment variables -OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn build && OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn start +OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn build +OPENAI_API_KEY=$OPENAI_API_KEY CODE=$CODE PORT=$PORT yarn start From dce2546f5f99df85810ced575c1a1c9cbc178781 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 03:26:42 +0800 Subject: [PATCH 71/91] fix: #451 override default model config --- app/requests.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/requests.ts b/app/requests.ts index c60efc95..b047a220 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -1,5 +1,5 @@ import type { ChatRequest, ChatReponse } from "./api/openai/typing"; -import { Message, ModelConfig, useAccessStore } from "./store"; +import { Message, ModelConfig, useAccessStore, useChatStore } from "./store"; import Locale from "./locales"; import { showToast } from "./components/ui-lib"; @@ -21,10 +21,12 @@ const makeRequestParam = ( sendMessages = sendMessages.filter((m) => m.role !== "assistant"); } + const modelConfig = useChatStore.getState().config.modelConfig; + return { - model: "gpt-3.5-turbo", messages: sendMessages, stream: options?.stream, + ...modelConfig, }; }; From acfe6eec18ea33ed0a65f8653199b220cdccff55 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 03:56:54 +0800 Subject: [PATCH 72/91] fix: #463 add subscrption total amount --- app/components/settings.tsx | 28 +++++++++------------ app/locales/cn.ts | 16 ++++++------ app/locales/en.ts | 4 +-- app/locales/es.ts | 4 +-- app/locales/it.ts | 4 +-- app/locales/tw.ts | 4 +-- app/requests.ts | 49 +++++++++++++++++++++---------------- 7 files changed, 56 insertions(+), 53 deletions(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index b563d7ba..a14f6473 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -96,26 +96,18 @@ export function Settings(props: { closeSettings: () => void }) { const [usage, setUsage] = useState<{ used?: number; + subscription?: number; }>(); const [loadingUsage, setLoadingUsage] = useState(false); function checkUsage() { setLoadingUsage(true); requestUsage() - .then((res) => - setUsage({ - used: res, - }), - ) + .then((res) => setUsage(res)) .finally(() => { setLoadingUsage(false); }); } - useEffect(() => { - checkUpdate(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - const accessStore = useAccessStore(); const enabledAccessControl = useMemo( () => accessStore.enabledAccessControl(), @@ -127,12 +119,13 @@ export function Settings(props: { closeSettings: () => void }) { const builtinCount = SearchService.count.builtin; const customCount = promptStore.prompts.size ?? 0; - const showUsage = accessStore.token !== ""; + const showUsage = !!accessStore.token || !!accessStore.accessCode; + useEffect(() => { - if (showUsage) { - checkUsage(); - } - }, [showUsage]); + checkUpdate(); + showUsage && checkUsage(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return ( @@ -392,7 +385,10 @@ export function Settings(props: { closeSettings: () => void }) { showUsage ? loadingUsage ? Locale.Settings.Usage.IsChecking - : Locale.Settings.Usage.SubTitle(usage?.used ?? "[?]") + : Locale.Settings.Usage.SubTitle( + usage?.used ?? "[?]", + usage?.subscription ?? "[?]", + ) : Locale.Settings.Usage.NoAccess } > diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 64f04626..033e1c2c 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -3,7 +3,7 @@ import { SubmitKey } from "../store/app"; const cn = { WIP: "该功能仍在开发中……", Error: { - Unauthorized: "现在是未授权状态,请在设置页输入授权码。", + Unauthorized: "现在是未授权状态,请在设置页输入访问密码。", }, ChatItem: { ChatItemCount: (count: number) => `${count} 条对话`, @@ -104,22 +104,22 @@ const cn = { }, Token: { Title: "API Key", - SubTitle: "使用自己的 Key 可绕过授权访问限制", + SubTitle: "使用自己的 Key 可绕过密码访问限制", Placeholder: "OpenAI API Key", }, Usage: { - Title: "账户余额", - SubTitle(used: any) { - return `本月已使用 $${used}`; + Title: "余额查询", + SubTitle(used: any, total: any) { + return `本月已使用 $${used},订阅总额 $${total}`; }, IsChecking: "正在检查…", Check: "重新检查", - NoAccess: "输入API Key查看余额", + NoAccess: "输入 API Key 或访问密码查看余额", }, AccessCode: { - Title: "授权码", + Title: "访问密码", SubTitle: "现在是未授权访问状态", - Placeholder: "请输入授权码", + Placeholder: "请输入访问密码", }, Model: "模型 (model)", Temperature: { diff --git a/app/locales/en.ts b/app/locales/en.ts index add9e1c7..aefc2e57 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -112,8 +112,8 @@ const en: LocaleType = { }, Usage: { Title: "Account Balance", - SubTitle(used: any) { - return `Used this month $${used}`; + SubTitle(used: any, total: any) { + return `Used this month $${used}, subscription $${total}`; }, IsChecking: "Checking...", Check: "Check Again", diff --git a/app/locales/es.ts b/app/locales/es.ts index b3af1f03..6997d5f6 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -112,8 +112,8 @@ const es: LocaleType = { }, Usage: { Title: "Saldo de la cuenta", - SubTitle(used: any) { - return `Usado $${used}`; + SubTitle(used: any, total: any) { + return `Usado $${used}, subscription $${total}`; }, IsChecking: "Comprobando...", Check: "Comprobar de nuevo", diff --git a/app/locales/it.ts b/app/locales/it.ts index 82afb818..95e6747b 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -113,8 +113,8 @@ const it: LocaleType = { }, Usage: { Title: "Bilancio Account", - SubTitle(used: any) { - return `Usato in questo mese $${used}`; + SubTitle(used: any, total: any) { + return `Usato in questo mese $${used}, subscription $${total}`; }, IsChecking: "Controllando...", Check: "Controlla ancora", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 237f3fbe..4340fe2c 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -109,8 +109,8 @@ const tw: LocaleType = { }, Usage: { Title: "帳戶餘額", - SubTitle(used: any) { - return `本月已使用 $${used}`; + SubTitle(used: any, total: any) { + return `本月已使用 $${used},订阅总额 $${total}`; }, IsChecking: "正在檢查…", Check: "重新檢查", diff --git a/app/requests.ts b/app/requests.ts index b047a220..8462f269 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -1,6 +1,5 @@ import type { ChatRequest, ChatReponse } from "./api/openai/typing"; import { Message, ModelConfig, useAccessStore, useChatStore } from "./store"; -import Locale from "./locales"; import { showToast } from "./components/ui-lib"; const TIME_OUT_MS = 30000; @@ -83,31 +82,39 @@ export async function requestUsage() { const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const startDate = formatDate(startOfMonth); const endDate = formatDate(now); - const res = await requestOpenaiClient( - `dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, - )(null, "GET"); - try { - const response = (await res.json()) as { - total_usage: number; - error?: { - type: string; - message: string; - }; + const [used, subs] = await Promise.all([ + requestOpenaiClient( + `dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, + )(null, "GET"), + requestOpenaiClient("dashboard/billing/subscription")(null, "GET"), + ]); + + const response = (await used.json()) as { + total_usage?: number; + error?: { + type: string; + message: string; }; + }; - if (response.error && response.error.type) { - showToast(response.error.message); - return; - } + const total = (await subs.json()) as { + hard_limit_usd?: number; + }; - if (response.total_usage) { - response.total_usage = Math.round(response.total_usage) / 100; - } - return response.total_usage; - } catch (error) { - console.error("[Request usage] ", error, res.body); + if (response.error && response.error.type) { + showToast(response.error.message); + return; } + + if (response.total_usage) { + response.total_usage = Math.round(response.total_usage) / 100; + } + + return { + used: response.total_usage, + subscription: total.hard_limit_usd, + }; } export async function requestChatStream( From c77f946be1aeeab55700e8ed54fabb3c92cc2ebe Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 04:01:53 +0800 Subject: [PATCH 73/91] fixup --- app/components/chat.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index ea1eb7e2..90c88d97 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -445,8 +445,10 @@ export function Chat(props: { chatStore .onUserInput(messages[i].content) .then(() => setIsLoading(false)); + chatStore.updateCurrentSession((session) => + session.messages.splice(i, 2), + ); inputRef.current?.focus(); - messages.splice(i, 2); return; } } From 46daafd190e9cf3e30355fe59c6911315026390b Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 04:10:51 +0800 Subject: [PATCH 74/91] Update README_CN.md --- README_CN.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README_CN.md b/README_CN.md index bc4ad132..dff312b5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -125,13 +125,6 @@ bash <(curl -s https://raw.githubusercontent.com/Yidadaa/ChatGPT-Next-Web/main/s ``` -## 截图 Screenshots - -![设置](./static/settings.png) - -![更多展示](./static/more.png) - - ## 鸣谢 ### 捐赠者 > 仅列出了部分大额打赏,小额打赏(< 100RMB)人数太多,在此不再列出,敬请谅解。 From 796eafbf8fec2e5b5bfc4c7b2cb3bc789080d537 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 04:11:12 +0800 Subject: [PATCH 75/91] Update README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index dff312b5..05c67b77 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,5 +1,5 @@
-预览 +预览

ChatGPT Next Web

From f509cc73ca25a664d7701c1419225afb7f7e6747 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 04:17:20 +0800 Subject: [PATCH 76/91] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index eb83f774..95f6a879 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug report about: Create a report to help us improve -title: '' +title: "[Bug] " labels: '' assignees: '' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d..25c36ab6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: Feature request about: Suggest an idea for this project -title: '' +title: "[Feature] " labels: '' assignees: '' From 998c5b29695c0dfb1af9d28cdd427e6b7b7c5416 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 04:27:38 +0800 Subject: [PATCH 77/91] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 95f6a879..01fa35e8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,7 +23,7 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. -** Deployment +**Deployment** - [ ] Docker - [ ] Vercel - [ ] Server From 6bd2c2f1212ca3e8e9ec7a6101b2b6a93109e53b Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 04:43:31 +0800 Subject: [PATCH 78/91] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..7712d974 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +flynn.zhang@foxmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From 03b3f164721703a2f1c17356016a3dcf7b4ba732 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 04:50:32 +0800 Subject: [PATCH 79/91] Update README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 05c67b77..cba9df9c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -90,7 +90,7 @@ OpenAI 代理接口协议,如果遇到 ssl 证书问题,请尝试通过此 ## 开发 -> 强烈不建议在本地进行开发或者部署,由于一些技术原因,导致很难在本地配置好 OpenAI API 代理,除非你能保证可以直。连 OpenAI 服务器 +> 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器。 点击下方按钮,开始二次开发: From d92108453f20c6b5807daeed0f1e84ab9ee62a5b Mon Sep 17 00:00:00 2001 From: Mokou Date: Thu, 6 Apr 2023 12:39:31 +0800 Subject: [PATCH 80/91] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E6=B5=8F=E8=A7=88=E5=99=A8=E7=9A=84input=20range?= =?UTF-8?q?=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/input-range.module.scss | 7 +++++ app/components/input-range.tsx | 37 ++++++++++++++++++++++++ app/components/settings.tsx | 21 ++++++-------- app/styles/globals.scss | 39 ++++++++++++++++++-------- 4 files changed, 81 insertions(+), 23 deletions(-) create mode 100644 app/components/input-range.module.scss create mode 100644 app/components/input-range.tsx diff --git a/app/components/input-range.module.scss b/app/components/input-range.module.scss new file mode 100644 index 00000000..5a555a45 --- /dev/null +++ b/app/components/input-range.module.scss @@ -0,0 +1,7 @@ +.input-range { + border: var(--border-in-light); + border-radius: 10px; + padding: 5px 15px 5px 10px; + font-size: 12px; + display: flex; +} diff --git a/app/components/input-range.tsx b/app/components/input-range.tsx new file mode 100644 index 00000000..a8ee9532 --- /dev/null +++ b/app/components/input-range.tsx @@ -0,0 +1,37 @@ +import * as React from "react"; +import styles from "./input-range.module.scss"; + +interface InputRangeProps { + onChange: React.ChangeEventHandler; + title?: string; + value: number | string; + className?: string; + min: string; + max: string; + step: string; +} + +export function InputRange({ + onChange, + title, + value, + className, + min, + max, + step, +}: InputRangeProps) { + return ( +
+ {title || value} + +
+ ); +} diff --git a/app/components/settings.tsx b/app/components/settings.tsx index a14f6473..ed1c28ee 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -32,6 +32,7 @@ import { UPDATE_URL } from "../constant"; import { SearchService, usePromptStore } from "../store/prompt"; import { requestUsage } from "../requests"; import { ErrorBoundary } from "./error"; +import { InputRange } from "./input-range"; function SettingItem(props: { title: string; @@ -274,8 +275,7 @@ export function Settings(props: { closeSettings: () => void }) { title={Locale.Settings.FontSize.Title} subTitle={Locale.Settings.FontSize.SubTitle} > - void }) { (config.fontSize = Number.parseInt(e.currentTarget.value)), ) } - > + > @@ -407,8 +407,7 @@ export function Settings(props: { closeSettings: () => void }) { title={Locale.Settings.HistoryCount.Title} subTitle={Locale.Settings.HistoryCount.SubTitle} > - void }) { (config.historyMessageCount = e.target.valueAsNumber), ) } - > + > void }) { title={Locale.Settings.Temperature.Title} subTitle={Locale.Settings.Temperature.SubTitle} > - void }) { )), ); }} - > + > void }) { title={Locale.Settings.PresencePenlty.Title} subTitle={Locale.Settings.PresencePenlty.SubTitle} > - void }) { )), ); }} - > + >
diff --git a/app/styles/globals.scss b/app/styles/globals.scss index 6492b000..53902d93 100644 --- a/app/styles/globals.scss +++ b/app/styles/globals.scss @@ -159,19 +159,11 @@ input[type="checkbox"]:checked::after { input[type="range"] { appearance: none; - border: var(--border-in-light); - border-radius: 10px; - padding: 5px 15px 5px 10px; background-color: var(--white); color: var(--black); - - &::before { - content: attr(value); - font-size: 12px; - } } -input[type="range"]::-webkit-slider-thumb { +@mixin thumb() { appearance: none; height: 8px; width: 20px; @@ -180,11 +172,36 @@ input[type="range"]::-webkit-slider-thumb { cursor: pointer; transition: all ease 0.3s; margin-left: 5px; + border: none; +} + +input[type="range"]::-webkit-slider-thumb { + @include thumb(); +} + +input[type="range"]::-moz-range-thumb { + @include thumb(); +} + +input[type="range"]::-ms-thumb { + @include thumb(); +} + +@mixin thumbHover() { + transform: scaleY(1.2); + width: 24px; } input[type="range"]::-webkit-slider-thumb:hover { - transform: scaleY(1.2); - width: 24px; + @include thumbHover(); +} + +input[type="range"]::-moz-range-thumb:hover { + @include thumbHover(); +} + +input[type="range"]::-ms-thumb:hover { + @include thumbHover(); } input[type="number"], From 03a2a4e534cb122588280e565cde25fd4f2dbfae Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 17:02:47 +0800 Subject: [PATCH 81/91] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d47a3895..46cb038e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

ChatGPT Next Web

-One-Click to deploy your own ChatGPT web UI. +Build well-designed ChatGPT web UI on Vercel with One-Click. 一键免费部署你的私人 ChatGPT 网页应用。 From a68721fcf29fb8f691576978a591640f55dcb6b1 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 17:14:19 +0800 Subject: [PATCH 82/91] Update requests.ts --- app/requests.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/requests.ts b/app/requests.ts index 8462f269..04d801e2 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -46,11 +46,10 @@ function getHeaders() { export function requestOpenaiClient(path: string) { return (body: any, method = "POST") => - fetch("/api/openai", { + fetch("/api/openai?_vercel_no_cache=1", { method, headers: { "Content-Type": "application/json", - "Cache-Control": "no-cache", path, ...getHeaders(), }, From 6823839f4b8fd803a2c1074f3fc288222b51a243 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 17:28:09 +0800 Subject: [PATCH 83/91] fixup --- app/api/openai/route.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/api/openai/route.ts b/app/api/openai/route.ts index cc51dbfc..3477fc27 100644 --- a/app/api/openai/route.ts +++ b/app/api/openai/route.ts @@ -6,6 +6,7 @@ async function makeRequest(req: NextRequest) { const api = await requestOpenai(req); const res = new NextResponse(api.body); res.headers.set("Content-Type", "application/json"); + res.headers.set("Cache-Control", "no-cache"); return res; } catch (e) { console.error("[OpenAI] ", req.body, e); @@ -16,7 +17,7 @@ async function makeRequest(req: NextRequest) { }, { status: 500, - }, + } ); } } From f7e42179d061a251bad7677af98262f44bc5cd9c Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 17:34:17 +0800 Subject: [PATCH 84/91] fixup --- app/requests.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/requests.ts b/app/requests.ts index 04d801e2..987434ed 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -9,7 +9,7 @@ const makeRequestParam = ( options?: { filterBot?: boolean; stream?: boolean; - }, + } ): ChatRequest => { let sendMessages = messages.map((v) => ({ role: v.role, @@ -76,7 +76,7 @@ export async function requestUsage() { .getDate() .toString() .padStart(2, "0")}`; - const ONE_DAY = 24 * 60 * 60 * 1000; + const ONE_DAY = 2 * 24 * 60 * 60 * 1000; const now = new Date(Date.now() + ONE_DAY); const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const startDate = formatDate(startOfMonth); @@ -84,7 +84,7 @@ export async function requestUsage() { const [used, subs] = await Promise.all([ requestOpenaiClient( - `dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`, + `dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}` )(null, "GET"), requestOpenaiClient("dashboard/billing/subscription")(null, "GET"), ]); @@ -124,7 +124,7 @@ export async function requestChatStream( onMessage: (message: string, done: boolean) => void; onError: (error: Error, statusCode?: number) => void; onController?: (controller: AbortController) => void; - }, + } ) { const req = makeRequestParam(messages, { stream: true, @@ -213,7 +213,7 @@ export const ControllerPool = { addController( sessionIndex: number, messageId: number, - controller: AbortController, + controller: AbortController ) { const key = this.key(sessionIndex, messageId); this.controllers[key] = controller; From 09fde0528a3b90d868336bf30dcc154751bb9d5f Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 17:38:03 +0800 Subject: [PATCH 85/91] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46cb038e..a317a564 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

ChatGPT Next Web

-Build well-designed ChatGPT web UI on Vercel with One-Click. +One-Click to deploy well-designed ChatGPT web UI on Vercel. 一键免费部署你的私人 ChatGPT 网页应用。 From 85bf4ac0770d525046d3de9509ec80cd06bc5336 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Thu, 6 Apr 2023 18:16:49 +0800 Subject: [PATCH 86/91] fix: #559 custom input ui style --- app/components/ui-lib.module.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/ui-lib.module.scss b/app/components/ui-lib.module.scss index 10163e99..95091cd0 100644 --- a/app/components/ui-lib.module.scss +++ b/app/components/ui-lib.module.scss @@ -149,6 +149,7 @@ background-color: var(--white); color: var(--black); resize: none; + min-width: 50px; } @media only screen and (max-width: 600px) { @@ -159,4 +160,4 @@ max-height: 50vh; } } -} +} \ No newline at end of file From dd20c36a557b37726ff74635fdef9f7fef535c4c Mon Sep 17 00:00:00 2001 From: xiaotianxt Date: Thu, 6 Apr 2023 20:38:10 +0800 Subject: [PATCH 87/91] fix: distinguish PC/Mobile behavior on auto-scroll The chat list should be set to auto-scroll on mobile screen when the input textarea is focused. It should not behave like that on PC screen because user may want to refer to previous content. --- app/components/chat.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 90c88d97..a8b53fea 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -421,6 +421,7 @@ export function Chat(props: { // check if should send message const onInputKeyDown = (e: React.KeyboardEvent) => { if (shouldSubmit(e)) { + setAutoScroll(true); onUserSubmit(); e.preventDefault(); } @@ -667,7 +668,7 @@ export function Chat(props: { onInput={(e) => onInput(e.currentTarget.value)} value={userInput} onKeyDown={onInputKeyDown} - onFocus={() => setAutoScroll(true)} + onFocus={() => setAutoScroll(isMobileScreen())} onBlur={() => { setAutoScroll(false); setTimeout(() => setPromptHints([]), 500); From 2092f30af5898e9a3b9da1d63d535764f58d12fb Mon Sep 17 00:00:00 2001 From: latorc <65785354+latorc@users.noreply.github.com> Date: Thu, 6 Apr 2023 20:55:54 +0800 Subject: [PATCH 88/91] Update faq-cn.md --- docs/faq-cn.md | 102 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 13 deletions(-) diff --git a/docs/faq-cn.md b/docs/faq-cn.md index 2b7b4587..b26bdedb 100644 --- a/docs/faq-cn.md +++ b/docs/faq-cn.md @@ -1,7 +1,21 @@ # 常见问题 + > We are sorry that there is currently no English version of the FAQ. English users can use translation tools to access this document. We look forward to receiving your PR for an English version of the documentation. - +## 如何快速获得帮助? +1. 询问ChatGPT / Bing / 百度 / Google等。 +2. 询问网友。请提供问题的背景信息和碰到问题的详细描述。高质量的提问容易获得有用的答案。 + +# 部署相关问题 + +## 为什么 Docker 部署版本一直提示更新 +Docker 版本相当于稳定版,latest Docker 总是与 latest release version 一致,目前我们的发版频率是一到两天发一次,所以 Docker 版本会总是落后最新的提交一到两天,这在预期内。 + +## 如何部署在Vercel上 +1. 注册Github账号,fork该项目 +2. 注册Vercel(需手机验证,可以用中国号码),连接你的Github账户 +3. Vercel上新建项目,选择你在Github fork的项目,按需填写环境变量,开始部署。部署之后,你可以在有梯子的条件下,通过vercel提供的域名访问你的项目。 +4. 如果需要在国内无墙访问:在你的域名管理网站,添加一条域名的CNAME记录,指向cname.vercel-dns.com。之后在Vercel上设置你的域名访问。 ## 如何修改 Vercel 环境变量 - 进入 vercel 的控制台页面; @@ -9,8 +23,11 @@ - 点击页面头部的 Settings 选项; - 找到侧边栏的 Environment Variables 选项; - 修改对应的值即可。 - - + +## 环境变量CODE是什么?必须设置吗? +这是你自定义的访问密码,你可以选择: +1. 不设置,删除该环境变量即可。谨慎:此时任何人可以访问你的项目。 +2. 部署项目时,设置环境变量CODE(支持多个密码逗号分隔)。设置访问密码后,用户需要在设置界面输入访问密码才可以使用。参见[相关说明](https://github.com/Yidadaa/ChatGPT-Next-Web/blob/main/README_CN.md#%E9%85%8D%E7%BD%AE%E9%A1%B5%E9%9D%A2%E8%AE%BF%E9%97%AE%E5%AF%86%E7%A0%81) ## 为什么我部署的版本没有流式响应 > 相关讨论:[#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386) @@ -28,7 +45,15 @@ keepalive_timeout 300; # 设定keep-alive超时时间为65秒 如果你是在 netlify 部署,此问题依然等待解决,请耐心等待。 - +## 我部署好了,但是无法访问 +请检查排除以下问题: +- 服务启动了吗? +- 端口正确映射了吗? +- 防火墙开放端口了吗? +- 到服务器的路由通吗? +- 域名正确解析了吗? + +# 使用相关问题 ## 为什么会一直提示“出错了,稍后重试吧” 原因可能有很多,请依次排查: @@ -37,16 +62,67 @@ keepalive_timeout 300; # 设定keep-alive超时时间为65秒 - 请检查 api key 是否可用; - 如果经历了上述步骤依旧无法确定问题,请在 issue 区提交一个新 issue,并附上 vercel 的 runtime log 或者 docker 运行时的 log。 - - -## 为什么 Docker 部署版本一直提示更新 -Docker 版本相当于稳定版,latest Docker 总是与 latest release version 一致,目前我们的发版频率是一到两天发一次,所以 Docker 版本会总是落后最新的提交一到两天,这在预期内。 - - - - ## 为什么 ChatGPT 的回复会乱码 设置界面 - 模型设置项中,有一项为 `temperature`,如果此值大于 1,那么就有可能造成回复乱码,将其调回 1 以内即可。 +## 使用时提示“现在是未授权状态,请在设置页输入访问密码”? +项目通过环境变量CODE设置了访问密码。第一次使用时,需要到设置中,输入访问码才可以使用。 + +## 使用时提示"You exceeded your current quota, ..." +API KEY有问题。余额不足。 + +## 什么是代理,如何使用? +由于OpenAI的IP限制,中国和其他一些国家/地区无法直接连接OpenAI API,需要通过代理。你可以使用代理服务器(正向代理),或者已经设置好的OpenAI API反向代理。 +- 正向代理例子:科学上网梯子。docker部署的情况下,设置环境变量HTTP_PROXY为你的代理地址(http://地址:端口)。 +- 反向代理例子:可以用别人搭建的代理地址,或者通过Cloudflare免费设置。设置项目环境变量BASE_URL为你的代理地址。 + +## 国内服务器可以部署吗? +可以但需要解决的问题: +- 需要代理才能连接github和openAI等网站; +- 国内服务器要设置域名解析的话需要备案; +- 国内政策限制代理访问外网/ChatGPT相关应用,可能被封。 + +# 网络服务相关问题 +## Cloudflare是什么? +Cloudflare(CF)是一个提供CDN,域名管理,静态页面托管,边缘计算函数部署等的网络服务供应商。常见的用途:购买和/或托管你的域名(解析、动态域名等),给你的服务器套上CDN(可以隐藏ip免被墙),部署网站(CF Pages)。CF免费提供大多数服务。 + +## Vercel是什么? +Vercel 是一个全球化的云平台,旨在帮助开发人员更快地构建和部署现代 Web 应用程序。本项目以及许多Web应用可以一键免费部署在Vercel上。无需懂代码,无需懂linux,无需服务器,无需付费,无需设置OpenAI API代理。缺点是需要绑定域名才可以在国内无墙访问。 + +## 如何获得一个域名? +1. 自己去域名供应商处注册,国外有Namesilo(支持支付宝), Cloudflare等等,国内有万网等等; +2. 免费的域名供应商:eu.org(二级域名)等; +3. 问朋友要一个免费的二级域名。 + +## 如何获得一台服务器 +- 国外服务器供应商举例:亚马逊云,谷歌云,Vultr,Bandwagon,Hostdare,等等; +国外服务器事项:服务器线路影响国内访问速度,推荐CN2 GIA和CN2线路的服务器。若服务器在国内访问困难(丢包严重等),可以尝试套CDN(Cloudflare等供应商)。 +- 国内服务器供应商:阿里云,腾讯等; +国内服务器事项:解析域名需要备案;国内服务器带宽较贵;访问国外网站(Github, openAI等)需要代理。 + +# OpenAI相关问题 +## 如何注册OpenAI账号? +去chat.openai.com注册。你需要: +- 一个良好的梯子(OpenAI支持地区原生IP地址) +- 一个支持的邮箱(例如Gmail或者公司/学校邮箱,非Outlook或qq邮箱) +- 接收短信认证的方式(例如SMS-activate网站) + +## 怎么开通OpenAI API? 怎么查询API余额? +官网地址(需梯子):https://platform.openai.com/account/usage +有网友搭建了无需梯子的余额查询代理,请询问网友获取。请鉴别来源是否可靠,以免API Key泄露。 + +## 我新注册的OpenAI账号怎么没有API余额? +(4月6日更新)新注册账号通常会在24小时后显示API余额。当前新注册账号赠送5美元余额。 + +## 如何给OpenAI API充值? +OpenAI只接受指定地区的信用卡(中国信用卡无法使用)。一些途径举例: +1. Depay虚拟信用卡 +2. 申请国外信用卡 +3. 网上找人代充 + +## 如何使用GPT-4的API访问? +(4月6日更新)GPT-4的API访问需要单独申请。到以下地址填写你的信息进入申请队列waitlist(准备好你的OpenAI组织ID):https://openai.com/waitlist/gpt-4-api +之后等待邮件消息。 + ## 如何使用 Azure OpenAI 接口 -请参考:[#371](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/371) \ No newline at end of file +请参考:[#371](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/371) From b7cdea1b822c757fe5127e2246b853d423085f4c Mon Sep 17 00:00:00 2001 From: leedom Date: Thu, 6 Apr 2023 21:02:48 +0800 Subject: [PATCH 89/91] refactor: optimize send button --- app/components/button.module.scss | 7 +++++++ app/components/button.tsx | 6 ++++-- app/components/chat.tsx | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/components/button.module.scss b/app/components/button.module.scss index 88da9748..2c33d0ac 100644 --- a/app/components/button.module.scss +++ b/app/components/button.module.scss @@ -10,6 +10,13 @@ transition: all 0.3s ease; overflow: hidden; user-select: none; + outline: none; + border: none; + + &[disabled] { + cursor: not-allowed; + opacity: 0.5; + } } .shadow { diff --git a/app/components/button.tsx b/app/components/button.tsx index 2e5707ae..1675a4b7 100644 --- a/app/components/button.tsx +++ b/app/components/button.tsx @@ -11,9 +11,10 @@ export function IconButton(props: { noDark?: boolean; className?: string; title?: string; + disabled?: boolean; }) { return ( -
{props.text}
)} -
+ ); } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 90c88d97..9fefe952 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -679,6 +679,7 @@ export function Chat(props: { text={Locale.Chat.Send} className={styles["chat-input-send"]} noDark + disabled={!userInput} onClick={onUserSubmit} />
From fb3f5a414a0f8d333c849f859cc701ad8f8e15a9 Mon Sep 17 00:00:00 2001 From: Leedom <30711792+leedom92@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:34:00 +0800 Subject: [PATCH 90/91] Update button.module.scss --- app/components/button.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/button.module.scss b/app/components/button.module.scss index 2c33d0ac..5b0bbe06 100644 --- a/app/components/button.module.scss +++ b/app/components/button.module.scss @@ -12,6 +12,7 @@ user-select: none; outline: none; border: none; + color: rgb(51, 51, 51); &[disabled] { cursor: not-allowed; From cd671066f767803d4486aeaec3de798e1c17a390 Mon Sep 17 00:00:00 2001 From: leedom Date: Thu, 6 Apr 2023 22:52:18 +0800 Subject: [PATCH 91/91] remove unnecessary judgment --- app/components/chat.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 9fefe952..0a0cb1ca 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -404,7 +404,6 @@ export function Chat(props: { // submit user input const onUserSubmit = () => { - if (userInput.length <= 0) return; setIsLoading(true); chatStore.onUserInput(userInput).then(() => setIsLoading(false)); setUserInput("");