From 2a79d356674236e4b4e345ae02236d52fd609f73 Mon Sep 17 00:00:00 2001 From: leedom Date: Sat, 1 Apr 2023 17:34:38 +0800 Subject: [PATCH 001/149] feat: add optimize textarea height when inputing --- app/components/home.module.scss | 2 +- app/components/home.tsx | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/components/home.module.scss b/app/components/home.module.scss index 764805d80..d88a3d5ca 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -414,7 +414,7 @@ background-color: var(--white); color: var(--black); font-family: inherit; - padding: 10px 14px 50px; + padding: 10px 90px 10px 14px; resize: none; outline: none; } diff --git a/app/components/home.tsx b/app/components/home.tsx index de93510db..2ff87b3ee 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -108,7 +108,7 @@ export function ChatList() { state.currentSessionIndex, state.selectSession, state.removeSession, - ] + ], ); return ( @@ -192,6 +192,7 @@ export function Chat(props: { const inputRef = useRef(null); const [userInput, setUserInput] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [textareaRows, setTextareaRows] = useState(2); const { submitKey, shouldSubmit } = useSubmitHandler(); // prompt hints @@ -202,7 +203,7 @@ export function Chat(props: { setPromptHints(promptStore.search(text)); }, 100, - { leading: true, trailing: true } + { leading: true, trailing: true }, ); const onPromptSelect = (prompt: Prompt) => { @@ -216,7 +217,7 @@ export function Chat(props: { if (!dom) return; const paddingBottomNum: number = parseInt( window.getComputedStyle(dom).paddingBottom, - 10 + 10, ); dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum; }; @@ -237,6 +238,11 @@ export function Chat(props: { onSearch(text.slice(1)); } } + + // textarea rows optimize + const length = text.split("\n").length - 1; + const rowsLength = length < 2 ? 2 : length > 6 ? 6 : length; + setTextareaRows(rowsLength > 6 ? 6 : rowsLength); }; // submit user input @@ -247,6 +253,7 @@ export function Chat(props: { setUserInput(""); setPromptHints([]); inputRef.current?.focus(); + setTextareaRows(2); }; // stop response @@ -304,7 +311,7 @@ export function Chat(props: { preview: true, }, ] - : [] + : [], ) .concat( userInput.length > 0 @@ -316,7 +323,7 @@ export function Chat(props: { preview: true, }, ] - : [] + : [], ); // auto scroll @@ -354,7 +361,7 @@ export function Chat(props: { const newTopic = prompt(Locale.Chat.Rename, session.topic); if (newTopic && newTopic !== session.topic) { chatStore.updateCurrentSession( - (session) => (session.topic = newTopic!) + (session) => (session.topic = newTopic!), ); } }} @@ -485,7 +492,7 @@ export function Chat(props: { ref={inputRef} className={styles["chat-input"]} placeholder={Locale.Chat.Input(submitKey)} - rows={4} + rows={textareaRows} onInput={(e) => onInput(e.currentTarget.value)} value={userInput} onKeyDown={(e) => onInputKeyDown(e as any)} @@ -603,7 +610,7 @@ export function Home() { state.newSession, state.currentSessionIndex, state.removeSession, - ] + ], ); const loading = !useHasHydrated(); const [showSideBar, setShowSideBar] = useState(true); From 9ba59351c5f623b5710e56f6c9397d70b9c307ff Mon Sep 17 00:00:00 2001 From: Leedom <30711792+leedom92@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:48:05 +0800 Subject: [PATCH 002/149] Update home.tsx --- app/components/home.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/components/home.tsx b/app/components/home.tsx index 2ff87b3ee..6c606ed86 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -108,7 +108,7 @@ export function ChatList() { state.currentSessionIndex, state.selectSession, state.removeSession, - ], + ] ); return ( @@ -203,7 +203,7 @@ export function Chat(props: { setPromptHints(promptStore.search(text)); }, 100, - { leading: true, trailing: true }, + { leading: true, trailing: true } ); const onPromptSelect = (prompt: Prompt) => { @@ -217,7 +217,7 @@ export function Chat(props: { if (!dom) return; const paddingBottomNum: number = parseInt( window.getComputedStyle(dom).paddingBottom, - 10, + 10 ); dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum; }; @@ -311,7 +311,7 @@ export function Chat(props: { preview: true, }, ] - : [], + : [] ) .concat( userInput.length > 0 @@ -323,7 +323,7 @@ export function Chat(props: { preview: true, }, ] - : [], + : [] ); // auto scroll @@ -361,7 +361,7 @@ export function Chat(props: { const newTopic = prompt(Locale.Chat.Rename, session.topic); if (newTopic && newTopic !== session.topic) { chatStore.updateCurrentSession( - (session) => (session.topic = newTopic!), + (session) => (session.topic = newTopic!) ); } }} @@ -610,7 +610,7 @@ export function Home() { state.newSession, state.currentSessionIndex, state.removeSession, - ], + ] ); const loading = !useHasHydrated(); const [showSideBar, setShowSideBar] = useState(true); From a811637176219828ea42e7aedb5558292f8539e6 Mon Sep 17 00:00:00 2001 From: leedom Date: Sun, 2 Apr 2023 02:40:00 +0800 Subject: [PATCH 003/149] refactor: use the method of element-ui --- app/components/home.tsx | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/app/components/home.tsx b/app/components/home.tsx index 6c606ed86..01f148989 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -37,6 +37,8 @@ import { REPO_URL } from "../constant"; import { ControllerPool } from "../requests"; import { Prompt, usePromptStore } from "../store/prompt"; +import calcTextareaHeight from "../calcTextareaHeight"; + export function Loading(props: { noLogo?: boolean }) { return (
@@ -108,7 +110,7 @@ export function ChatList() { state.currentSessionIndex, state.selectSession, state.removeSession, - ] + ], ); return ( @@ -179,6 +181,10 @@ export function PromptHints(props: { export function Chat(props: { showSideBar?: () => void; sideBarShowing?: boolean; + autoSize: { + minRows: number; + maxRows: number; + }; }) { type RenderMessage = Message & { preview?: boolean }; @@ -190,9 +196,10 @@ export function Chat(props: { const fontSize = useChatStore((state) => state.config.fontSize); const inputRef = useRef(null); + const [userInput, setUserInput] = useState(""); const [isLoading, setIsLoading] = useState(false); - const [textareaRows, setTextareaRows] = useState(2); + const [textareaStyle, setTextareaStyle] = useState({}); const { submitKey, shouldSubmit } = useSubmitHandler(); // prompt hints @@ -203,7 +210,7 @@ export function Chat(props: { setPromptHints(promptStore.search(text)); }, 100, - { leading: true, trailing: true } + { leading: true, trailing: true }, ); const onPromptSelect = (prompt: Prompt) => { @@ -217,7 +224,7 @@ export function Chat(props: { if (!dom) return; const paddingBottomNum: number = parseInt( window.getComputedStyle(dom).paddingBottom, - 10 + 10, ); dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum; }; @@ -239,10 +246,13 @@ export function Chat(props: { } } - // textarea rows optimize - const length = text.split("\n").length - 1; - const rowsLength = length < 2 ? 2 : length > 6 ? 6 : length; - setTextareaRows(rowsLength > 6 ? 6 : rowsLength); + resizeTextarea(); + }; + + // set style for textarea + const resizeTextarea = () => { + const { minRows, maxRows } = props.autoSize; + setTextareaStyle(calcTextareaHeight(inputRef.current, minRows, maxRows)); }; // submit user input @@ -253,7 +263,6 @@ export function Chat(props: { setUserInput(""); setPromptHints([]); inputRef.current?.focus(); - setTextareaRows(2); }; // stop response @@ -311,7 +320,7 @@ export function Chat(props: { preview: true, }, ] - : [] + : [], ) .concat( userInput.length > 0 @@ -323,7 +332,7 @@ export function Chat(props: { preview: true, }, ] - : [] + : [], ); // auto scroll @@ -361,7 +370,7 @@ export function Chat(props: { const newTopic = prompt(Locale.Chat.Rename, session.topic); if (newTopic && newTopic !== session.topic) { chatStore.updateCurrentSession( - (session) => (session.topic = newTopic!) + (session) => (session.topic = newTopic!), ); } }} @@ -490,9 +499,9 @@ export function Chat(props: {
+ ); +} From c2b37f811bcfb41001dab787f11e493aba45b9a3 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 02:37:12 +0800 Subject: [PATCH 031/149] 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 edf7f1dbe..d47a38954 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 5216fb255..f57e6c100 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 03f5de671..dc746e24f 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 e0adb6dc6..64f04626c 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 af8201503..add9e1c74 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 5c73b608b..b3af1f036 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 ce87796d1..82afb818e 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 eaab03fcd..237f3fbe0 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 d01e3cdd5..9f1e8e887 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 48f56995b..6492b000a 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 032/149] 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 66f86348f..9e57cb870 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 033/149] 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 f33175543..526623ce1 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 dc746e24f..ea1eb7e2b 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 da9b5c97f..c60efc95c 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 9f1e8e887..64faa31db 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 63a28bf09..b96533398 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 034/149] 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 c60efc95c..b047a220f 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 035/149] 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 b563d7bab..a14f6473d 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 64f04626c..033e1c2cf 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 add9e1c74..aefc2e57c 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 b3af1f036..6997d5f6d 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 82afb818e..95e6747ba 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 237f3fbe0..4340fe2c8 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 b047a220f..8462f2694 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 036/149] 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 ea1eb7e2b..90c88d97b 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 037/149] Update README_CN.md --- README_CN.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README_CN.md b/README_CN.md index bc4ad132a..dff312b51 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 038/149] 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 dff312b51..05c67b770 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 039/149] 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 eb83f7741..95f6a879b 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 bbcbbe7d6..25c36ab67 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 040/149] 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 95f6a879b..01fa35e82 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 041/149] 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 000000000..7712d9742 --- /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 042/149] 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 05c67b770..cba9df9c5 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 043/149] =?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 000000000..5a555a457 --- /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 000000000..a8ee9532b --- /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 a14f6473d..ed1c28ee5 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 6492b000a..53902d935 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 044/149] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d47a38954..46cb038eb 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 045/149] 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 8462f2694..04d801e2e 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 046/149] 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 cc51dbfc9..3477fc270 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 047/149] fixup --- app/requests.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/requests.ts b/app/requests.ts index 04d801e2e..987434ed4 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 048/149] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46cb038eb..a317a5649 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 049/149] 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 10163e993..95091cd0a 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 050/149] 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 90c88d97b..a8b53fea3 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 051/149] 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 2b7b45871..b26bdedb1 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 052/149] 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 88da97481..2c33d0acf 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 2e5707ae7..1675a4b7d 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 90c88d97b..9fefe9528 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 053/149] 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 2c33d0acf..5b0bbe067 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 054/149] 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 9fefe9528..0a0cb1ca2 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(""); From f3dbe5a25116bc9487edd5165cf8cbe442655264 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 23:18:51 +0800 Subject: [PATCH 055/149] fix: #513 show toast after copying --- app/utils.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/utils.ts b/app/utils.ts index 9fcb11820..bb44e072d 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -7,23 +7,21 @@ export function trimTopic(topic: string) { } export async function copyToClipboard(text: string) { - if (navigator.clipboard) { - navigator.clipboard.writeText(text).catch(err => { - console.error('Failed to copy: ', err); - }); - } else { - const textArea = document.createElement('textarea'); + try { + await navigator.clipboard.writeText(text); + } catch (error) { + const textArea = document.createElement("textarea"); textArea.value = text; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { - document.execCommand('copy'); - console.log('Text copied to clipboard'); - } catch (err) { - console.error('Failed to copy: ', err); + document.execCommand("copy"); + } catch (error) { + showToast(Locale.Copy.Failed); } - document.body.removeChild(textArea); + } finally { + showToast(Locale.Copy.Success); } } From 806587c8eae4ffa21805bc29e83f7ce85ca4682a Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 6 Apr 2023 23:47:47 +0800 Subject: [PATCH 056/149] fix: #512 Mobile renaming should not return to chat list --- app/components/chat.tsx | 16 ++++++---------- app/icons/return.svg | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 app/icons/return.svg diff --git a/app/components/chat.tsx b/app/components/chat.tsx index b69209641..4ab616444 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -4,7 +4,7 @@ import { memo, 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 ReturnIcon from "../icons/return.svg"; import CopyIcon from "../icons/copy.svg"; import DownloadIcon from "../icons/download.svg"; import LoadingIcon from "../icons/three-dots.svg"; @@ -404,6 +404,7 @@ export function Chat(props: { // submit user input const onUserSubmit = () => { + if (userInput.length <= 0) return; setIsLoading(true); chatStore.onUserInput(userInput).then(() => setIsLoading(false)); setUserInput(""); @@ -420,7 +421,6 @@ export function Chat(props: { // check if should send message const onInputKeyDown = (e: React.KeyboardEvent) => { if (shouldSubmit(e)) { - setAutoScroll(true); onUserSubmit(); e.preventDefault(); } @@ -507,13 +507,10 @@ export function Chat(props: { return (
-
+
{ + onClickCapture={() => { const newTopic = prompt(Locale.Chat.Rename, session.topic); if (newTopic && newTopic !== session.topic) { chatStore.updateCurrentSession( @@ -531,7 +528,7 @@ export function Chat(props: {
} + icon={} bordered title={Locale.Chat.Actions.ChatList} onClick={props?.showSideBar} @@ -667,7 +664,7 @@ export function Chat(props: { onInput={(e) => onInput(e.currentTarget.value)} value={userInput} onKeyDown={onInputKeyDown} - onFocus={() => setAutoScroll(isMobileScreen())} + onFocus={() => setAutoScroll(true)} onBlur={() => { setAutoScroll(false); setTimeout(() => setPromptHints([]), 500); @@ -679,7 +676,6 @@ export function Chat(props: { text={Locale.Chat.Send} className={styles["chat-input-send"]} noDark - disabled={!userInput} onClick={onUserSubmit} />
diff --git a/app/icons/return.svg b/app/icons/return.svg new file mode 100644 index 000000000..eba5e78f9 --- /dev/null +++ b/app/icons/return.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file From 595206436231727659fde77239b6c1e668b0d879 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 7 Apr 2023 00:14:27 +0800 Subject: [PATCH 057/149] feat: #499 revert delete session --- app/components/button.module.scss | 2 +- app/components/chat-list.tsx | 6 ++---- app/components/home.tsx | 7 ++----- app/components/ui-lib.module.scss | 20 ++++++++++++++++++-- app/components/ui-lib.tsx | 28 ++++++++++++++++++++++++---- app/locales/cn.ts | 2 ++ app/locales/en.ts | 2 ++ app/locales/es.ts | 2 ++ app/locales/it.ts | 2 ++ app/locales/tw.ts | 2 ++ app/store/app.ts | 24 +++++++++++++++++++++++- 11 files changed, 80 insertions(+), 17 deletions(-) diff --git a/app/components/button.module.scss b/app/components/button.module.scss index 5b0bbe067..e7d5d8940 100644 --- a/app/components/button.module.scss +++ b/app/components/button.module.scss @@ -12,7 +12,7 @@ user-select: none; outline: none; border: none; - color: rgb(51, 51, 51); + color: var(--black); &[disabled] { cursor: not-allowed; diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx index 8d02805f7..ab5d849f1 100644 --- a/app/components/chat-list.tsx +++ b/app/components/chat-list.tsx @@ -59,6 +59,7 @@ export function ChatList() { state.removeSession, state.moveSession, ]); + const chatStore = useChatStore(); const onDragEnd: OnDragEndResponder = (result) => { const { destination, source } = result; @@ -95,10 +96,7 @@ export function ChatList() { index={i} selected={i === selectedIndex} onClick={() => selectSession(i)} - onDelete={() => - (!isMobileScreen() || confirm(Locale.Home.DeleteChat)) && - removeSession(i) - } + onDelete={chatStore.deleteSession} /> ))} {provided.placeholder} diff --git a/app/components/home.tsx b/app/components/home.tsx index 9e57cb870..b6a2161d6 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -93,6 +93,7 @@ function _Home() { state.removeSession, ], ); + const chatStore = useChatStore(); const loading = !useHasHydrated(); const [showSideBar, setShowSideBar] = useState(true); @@ -142,11 +143,7 @@ function _Home() {
} - onClick={() => { - if (confirm(Locale.Home.DeleteChat)) { - removeSession(currentIndex); - } - }} + onClick={chatStore.deleteSession} />
diff --git a/app/components/ui-lib.module.scss b/app/components/ui-lib.module.scss index 95091cd0a..83eb614f7 100644 --- a/app/components/ui-lib.module.scss +++ b/app/components/ui-lib.module.scss @@ -135,9 +135,25 @@ box-shadow: var(--card-shadow); border: var(--border-in-light); color: var(--black); - padding: 10px 30px; + padding: 10px 20px; border-radius: 50px; margin-bottom: 20px; + display: flex; + align-items: center; + + .toast-action { + padding-left: 20px; + color: var(--primary); + opacity: 0.8; + border: 0; + background: none; + cursor: pointer; + font-family: inherit; + + &:hover { + opacity: 1; + } + } } } @@ -160,4 +176,4 @@ max-height: 50vh; } } -} \ No newline at end of file +} diff --git a/app/components/ui-lib.tsx b/app/components/ui-lib.tsx index 6761e7f97..a72aa868f 100644 --- a/app/components/ui-lib.tsx +++ b/app/components/ui-lib.tsx @@ -110,17 +110,37 @@ export function showModal(props: ModalProps) { root.render(); } -export type ToastProps = { content: string }; +export type ToastProps = { + content: string; + action?: { + text: string; + onClick: () => void; + }; +}; export function Toast(props: ToastProps) { return (
-
{props.content}
+
+ {props.content} + {props.action && ( + + )} +
); } -export function showToast(content: string, delay = 3000) { +export function showToast( + content: string, + action?: ToastProps["action"], + delay = 3000, +) { const div = document.createElement("div"); div.className = styles.show; document.body.appendChild(div); @@ -139,7 +159,7 @@ export function showToast(content: string, delay = 3000) { close(); }, delay); - root.render(); + root.render(); } export type InputProps = React.HTMLProps & { diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 033e1c2cf..e21272a12 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -47,6 +47,8 @@ const cn = { Home: { NewChat: "新的聊天", DeleteChat: "确认删除选中的对话?", + DeleteToast: "已删除会话", + Revert: "撤销", }, Settings: { Title: "设置", diff --git a/app/locales/en.ts b/app/locales/en.ts index aefc2e57c..61d20b60f 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -50,6 +50,8 @@ const en: LocaleType = { Home: { NewChat: "New Chat", DeleteChat: "Confirm to delete the selected conversation?", + DeleteToast: "Chat Deleted", + Revert: "Revert", }, Settings: { Title: "Settings", diff --git a/app/locales/es.ts b/app/locales/es.ts index 6997d5f6d..5a83cb55c 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -50,6 +50,8 @@ const es: LocaleType = { Home: { NewChat: "Nuevo chat", DeleteChat: "¿Confirmar eliminación de la conversación seleccionada?", + DeleteToast: "Chat Deleted", + Revert: "Revert", }, Settings: { Title: "Configuración", diff --git a/app/locales/it.ts b/app/locales/it.ts index 95e6747ba..7108090eb 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -50,6 +50,8 @@ const it: LocaleType = { Home: { NewChat: "Nuova Chat", DeleteChat: "Confermare la cancellazione della conversazione selezionata?", + DeleteToast: "Chat Deleted", + Revert: "Revert", }, Settings: { Title: "Impostazioni", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 4340fe2c8..ff1794b55 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -48,6 +48,8 @@ const tw: LocaleType = { Home: { NewChat: "新的對話", DeleteChat: "確定要刪除選取的對話嗎?", + DeleteToast: "已刪除對話", + Revert: "撤銷", }, Settings: { Title: "設定", diff --git a/app/store/app.ts b/app/store/app.ts index c63fa9d4f..e72163ebb 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -7,9 +7,10 @@ import { requestChatStream, requestWithPrompt, } from "../requests"; -import { trimTopic } from "../utils"; +import { isMobileScreen, trimTopic } from "../utils"; import Locale from "../locales"; +import { showToast } from "../components/ui-lib"; export type Message = ChatCompletionResponseMessage & { date: string; @@ -204,6 +205,7 @@ interface ChatStore { moveSession: (from: number, to: number) => void; selectSession: (index: number) => void; newSession: () => void; + deleteSession: () => void; currentSession: () => ChatSession; onNewMessage: (message: Message) => void; onUserInput: (content: string) => Promise; @@ -324,6 +326,26 @@ export const useChatStore = create()( })); }, + deleteSession() { + const deletedSession = get().currentSession(); + const index = get().currentSessionIndex; + const isLastSession = get().sessions.length === 1; + if (!isMobileScreen() || confirm(Locale.Home.DeleteChat)) { + get().removeSession(index); + } + showToast(Locale.Home.DeleteToast, { + text: Locale.Home.Revert, + onClick() { + set((state) => ({ + sessions: state.sessions + .slice(0, index) + .concat([deletedSession]) + .concat(state.sessions.slice(index + Number(isLastSession))), + })); + }, + }); + }, + currentSession() { let index = get().currentSessionIndex; const sessions = get().sessions; From 4d675c11e8a61d05fe1667d93fe5f4e676484fc4 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Fri, 7 Apr 2023 00:58:22 +0800 Subject: [PATCH 058/149] Update faq-cn.md --- docs/faq-cn.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/faq-cn.md b/docs/faq-cn.md index b26bdedb1..88293b9c6 100644 --- a/docs/faq-cn.md +++ b/docs/faq-cn.md @@ -126,3 +126,13 @@ OpenAI只接受指定地区的信用卡(中国信用卡无法使用)。一 ## 如何使用 Azure OpenAI 接口 请参考:[#371](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/371) + +## 为什么我的 Token 消耗得这么快? +> 相关讨论:[#518](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/518) +- 如果你有 GPT 4 的权限,并且日常在使用 GPT 4 api,那么由于 GPT 4 价格是 GPT 3.5 的 15 倍左右,你的账单金额会急速膨胀; +- 如果你在使用 GPT 3.5,并且使用频率并不高,仍然发现自己的账单金额在飞快增加,那么请马上按照以下步骤排查: + - 去 openai 官网查看你的 api key 消费记录,如果你的 token 每小时都有消费,并且每次都消耗了上万 token,那你的 key 一定是泄露了,请立即删除重新生成。**不要在乱七八糟的网站上查余额。** + - 如果你的密码设置很短,比如 5 位以内的字母,那么爆破成本是非常低的,建议你搜索一下 docker 的日志记录,确认是否有人大量尝试了密码组合,关键字:got access code +- 通过上述两个方法就可以定位到你的 token 被快速消耗的原因: + - 如果 openai 消费记录异常,但是 docker 日志没有问题,那么说明是 api key 泄露; + - 如果 docker 日志发现大量 got access code 爆破记录,那么就是密码被爆破了。 From a28bfdbaf80cebb0884ff08e2897d31405ade7bf Mon Sep 17 00:00:00 2001 From: Zepto <38270878+XGZepto@users.noreply.github.com> Date: Thu, 6 Apr 2023 18:53:11 +0000 Subject: [PATCH 059/149] Add FAQ-EN --- README.md | 4 +- docs/faq.en.md | 136 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 docs/faq.en.md diff --git a/README.md b/README.md index a317a5649..1346585b8 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,7 @@ One-Click to deploy well-designed ChatGPT web UI on Vercel. 3. Enjoy :) ## FAQ -> [简体中文 > 常见问题](./docs/faq-cn.md) - -We are sorry that there is currently no English version of the FAQ. English users can use translation tools to access the document. We look forward to receiving your PR for an English version of the documentation. +[简体中文 > 常见问题](./docs/faq-cn.md) | [English > FAQ](./docs/faq.en.md) ## Keep Updated > [简体中文 > 如何保持代码更新](./README_CN.md#保持更新) diff --git a/docs/faq.en.md b/docs/faq.en.md new file mode 100644 index 000000000..46121bb51 --- /dev/null +++ b/docs/faq.en.md @@ -0,0 +1,136 @@ +# Frequently Asked Questions + +## How to get help quickly? +1. Ask ChatGPT / Bing / Baidu / Google, etc. +2. Ask online friends. Please provide background information and a detailed description of the problem. High-quality questions are more likely to get useful answers. + +# Deployment Related Questions + +## Why does the Docker deployment version always prompt for updates +The Docker version is equivalent to the stable version, and the latest Docker is always consistent with the latest release version. Currently, our release frequency is once every one to two days, so the Docker version will always be one to two days behind the latest commit, which is expected. + +## How to deploy on Vercel +1. Register a Github account and fork this project. +2. Register Vercel (mobile phone verification required, Chinese number can be used), and connect your Github account. +3. Create a new project on Vercel, select the project you forked on Github, fill in the required environment variables, and start deploying. After deployment, you can access your project through the domain provided by Vercel under conditions with a ladder (VPN). +4. If you need to access it without a wall in China: In your domain management website, add a CNAME record for the domain name, pointing to cname.vercel-dns.com. Then set up your domain access on Vercel. + +## How to modify Vercel environment variables +- Enter the Vercel console page; +- Select your chatgpt next web project; +- Click on the Settings option at the top of the page; +- Find the Environment Variables option in the sidebar; +- Modify the corresponding values as needed. + +## What is the environment variable CODE? Is it necessary to set it? +This is your custom access password, you can choose: +1. Do not set it, delete the environment variable. Be cautious: anyone can access your project at this time. +2. When deploying the project, set the environment variable CODE (supports multiple passwords separated by commas). After setting the access password, users need to enter the access password in the settings page to use it. See [related instructions](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) + +## Why doesn't the version I deployed have streaming response +> Related discussion: [#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386) + +If you use nginx reverse proxy, you need to add the following code to the configuration file: +``` +# No caching, support streaming output +proxy_cache off; # Turn off caching +proxy_buffering off; # Turn off proxy buffering +chunked_transfer_encoding on; # Turn on chunked transfer encoding +tcp_nopush on; # Turn on TCP NOPUSH option, disable Nagle algorithm +tcp_nodelay on; # Turn on TCP NODELAY option, disable delay ACK algorithm +keepalive_timeout 300; # Set keep-alive timeout to 65 seconds +``` + +If you are deploying on netlify, this issue is still waiting to be resolved, please be patient. + +## I've deployed, but it's not accessible +Please check and troubleshoot the following issues: +- Is the service started? +- Is the port correctly mapped? +- Is the firewall port open? +- Is the route to the server okay? +- Is the domain name resolved correctly? + +# Usage Related Questions + +## Why does it always prompt "An error occurred, please try again later" +There could be many reasons, please check the following in order: +- First, check if your code version is the latest version, update to the latest version and try again; +- Check if the api key is set correctly, the environment variable name must be uppercase with underscores; +- Check if the api key is available; +- If you still cannot determine the problem after going through the above steps, please submit a new issue in the issue area and attach the runtime log of vercel or the log of docker runtime. + +## Why does ChatGPT's reply get garbled +In the settings page - model settings, there is an item called `temperature`. If this value is greater than 1, it may cause garbled replies. Adjust it back to within 1. + +## It prompts "Now it's unauthorized, please enter the access password on the settings page" when using? +The project has set an access password through the environment variable CODE. When using it for the first time, you need to go to settings and enter the access code to use. + +## It prompts "You exceeded your current quota, ..." when using? +The API KEY is problematic. Insufficient balance. + +## What is a proxy and how to use it? +Due to IP restrictions of OpenAI, China and some other countries/regions cannot directly connect to OpenAI API and need to go through a proxy. You can use a proxy server (forward proxy) or a pre-configured OpenAI API reverse proxy. +- Forward proxy example: VPN ladder. In the case of docker deployment, set the environment variable HTTP_PROXY to your proxy address (http://address:port). +- Reverse proxy example: You can use someone else's proxy address or set it up for free through Cloudflare. Set the project environment variable BASE_URL to your proxy address. + +## Can I deploy it on a server in China? +It is possible but there are issues to be addressed: +- Proxy is required to connect to websites such as Github and OpenAI; +- Domain name resolution requires filing for servers in China; +- Chinese policy restricts proxy access to foreign websites/ChatGPT-related applications, which may be blocked. + +# Network Service Related Questions +## What is Cloudflare? +Cloudflare (CF) is a network service provider offering CDN, domain management, static page hosting, edge computing function deployment, and more. Common use cases: purchase and/or host your domain (resolution, dynamic domain, etc.), apply CDN to your server (can hide IP to avoid being blocked), deploy websites (CF Pages). CF offers most services for free. + +## What is Vercel? +Vercel is a global cloud platform designed to help developers build and deploy modern web applications more quickly. This project and many web applications can be deployed on Vercel with a single click for free. No need to understand code, Linux, have a server, pay, or set up an OpenAI API proxy. The downside is that you need to bind a domain name to access it without restrictions in China. + +## How to obtain a domain name? +1. Register with a domain provider, such as Namesilo (supports Alipay) or Cloudflare for international providers, and Wanwang for domestic providers in China. +2. Free domain name providers: eu.org (second-level domain), etc. +3. Ask friends for a free second-level domain. + +## How to obtain a server +- Examples of international server providers: Amazon Web Services, Google Cloud, Vultr, Bandwagon, Hostdare, etc. + International server considerations: Server lines affect access speed in China; CN2 GIA and CN2 lines are recommended. If the server has difficulty accessing in China (serious packet loss, etc.), you can try using a CDN (from providers like Cloudflare). +- Domestic server providers: Alibaba Cloud, Tencent, etc. + Domestic server considerations: Domain name resolution requires filing; domestic server bandwidth is relatively expensive; accessing foreign websites (Github, OpenAI, etc.) requires a proxy. + +# OpenAI-related Questions +## How to register an OpenAI account? +Go to chat.openai.com to register. You will need: +- A good VPN (OpenAI supports native IP addresses of supported regions) +- A supported email (e.g., Gmail or a company/school email, not Outlook or QQ email) +- A way to receive SMS verification (e.g., SMS-activate website) + +## How to activate OpenAI API? How to check API balance? +Official website (requires VPN): https://platform.openai.com/account/usage +Some users have set up a proxy to check the balance without a VPN; ask online friends for access. Please verify the source is reliable to avoid API Key leakage. + +## Why doesn't my new OpenAI account have an API balance? +(Updated April 6th) Newly registered accounts usually display API balance within 24 hours. New accounts are currently given a $5 balance. + +## How to recharge OpenAI API? +OpenAI only accepts credit cards from designated regions (Chinese credit cards cannot be used). Some options include: +1. Depay virtual credit card +2. Apply for a foreign credit card +3. Find someone online to top up + +## How to access the GPT-4 API? +(Updated April 6th) Access to the GPT-4 API requires a separate application. Go to the following address and enter your information to join the waitlist (prepare your OpenAI organization ID): https://openai.com/waitlist/gpt-4-api +Wait for email updates afterwards. + +## How to use the Azure OpenAI interface +Please refer to: [#371](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/371) + +## Why is my Token consumed so fast? +> Related discussion: [#518](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/518) +- If you have GPT-4 access and use GPT-4 API regularly, your bill will increase rapidly since GPT-4 pricing is about 15 times higher than GPT-3.5; +- If you are using GPT-3.5 and not using it frequently, but still find your bill increasing fast, please troubleshoot immediately using these steps: + - Check your API key consumption record on the OpenAI website; if your token is consumed every hour and each time consumes tens of thousands of tokens, your key must have been leaked. Please delete it and regenerate it immediately. **Do not check your balance on random websites.** + - If your password is short, such as 5 characters or fewer, the cost of brute-forcing is very low. It is recommended to search docker logs to confirm whether someone has tried a large number of password combinations. Keyword: got access code +- By following these two methods, you can locate the reason for your token's rapid consumption: + - If the OpenAI consumption record is abnormal but the Docker log has no issues, it means your API key has been leaked; + - If the Docker log shows a large number of got access code brute-force attempts, your password has been cracked. \ No newline at end of file From 52f80e0c44dfee6873aac611eb6602fb62cabf28 Mon Sep 17 00:00:00 2001 From: Zepto <38270878+XGZepto@users.noreply.github.com> Date: Fri, 7 Apr 2023 02:59:36 +0800 Subject: [PATCH 060/149] Refine China Specific Issues --- docs/faq.en.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/faq.en.md b/docs/faq.en.md index 46121bb51..319fc7dea 100644 --- a/docs/faq.en.md +++ b/docs/faq.en.md @@ -12,12 +12,12 @@ The Docker version is equivalent to the stable version, and the latest Docker is ## How to deploy on Vercel 1. Register a Github account and fork this project. 2. Register Vercel (mobile phone verification required, Chinese number can be used), and connect your Github account. -3. Create a new project on Vercel, select the project you forked on Github, fill in the required environment variables, and start deploying. After deployment, you can access your project through the domain provided by Vercel under conditions with a ladder (VPN). -4. If you need to access it without a wall in China: In your domain management website, add a CNAME record for the domain name, pointing to cname.vercel-dns.com. Then set up your domain access on Vercel. +3. Create a new project on Vercel, select the project you forked on Github, fill in the required environment variables, and start deploying. After deployment, you can access your project through the domain provided by Vercel. (Requires proxy in mainland China) +* If you need to access it directly in China: At your DNS provider, add a CNAME record for the domain name, pointing to cname.vercel-dns.com. Then set up your domain access on Vercel. ## How to modify Vercel environment variables - Enter the Vercel console page; -- Select your chatgpt next web project; +- Select your chatgpt-next-web project; - Click on the Settings option at the top of the page; - Find the Environment Variables option in the sidebar; - Modify the corresponding values as needed. @@ -25,7 +25,7 @@ The Docker version is equivalent to the stable version, and the latest Docker is ## What is the environment variable CODE? Is it necessary to set it? This is your custom access password, you can choose: 1. Do not set it, delete the environment variable. Be cautious: anyone can access your project at this time. -2. When deploying the project, set the environment variable CODE (supports multiple passwords separated by commas). After setting the access password, users need to enter the access password in the settings page to use it. See [related instructions](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) +2. When deploying the project, set the environment variable CODE (supports multiple passwords, separated by commas). After setting the access password, users need to enter the access password in the settings page to use it. See [related instructions](https://github.com/Yidadaa/ChatGPT-Next-Web#access-password) ## Why doesn't the version I deployed have streaming response > Related discussion: [#386](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/386) @@ -101,7 +101,7 @@ Vercel is a global cloud platform designed to help developers build and deploy m # OpenAI-related Questions ## How to register an OpenAI account? Go to chat.openai.com to register. You will need: -- A good VPN (OpenAI supports native IP addresses of supported regions) +- A good VPN (OpenAI only allows native IP addresses of supported regions) - A supported email (e.g., Gmail or a company/school email, not Outlook or QQ email) - A way to receive SMS verification (e.g., SMS-activate website) @@ -113,7 +113,7 @@ Some users have set up a proxy to check the balance without a VPN; ask online fr (Updated April 6th) Newly registered accounts usually display API balance within 24 hours. New accounts are currently given a $5 balance. ## How to recharge OpenAI API? -OpenAI only accepts credit cards from designated regions (Chinese credit cards cannot be used). Some options include: +OpenAI only accepts credit cards from designated regions (Chinese credit cards cannot be used). If the credit cards from your region is not supported, some options include: 1. Depay virtual credit card 2. Apply for a foreign credit card 3. Find someone online to top up @@ -133,4 +133,4 @@ Please refer to: [#371](https://github.com/Yidadaa/ChatGPT-Next-Web/issues/371) - If your password is short, such as 5 characters or fewer, the cost of brute-forcing is very low. It is recommended to search docker logs to confirm whether someone has tried a large number of password combinations. Keyword: got access code - By following these two methods, you can locate the reason for your token's rapid consumption: - If the OpenAI consumption record is abnormal but the Docker log has no issues, it means your API key has been leaked; - - If the Docker log shows a large number of got access code brute-force attempts, your password has been cracked. \ No newline at end of file + - If the Docker log shows a large number of got access code brute-force attempts, your password has been cracked. From 905bf41cd831bcb16d9042202979fa0c99454be5 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Fri, 7 Apr 2023 11:08:20 +0800 Subject: [PATCH 061/149] Update utils.ts --- app/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/utils.ts b/app/utils.ts index bb44e072d..333866c7b 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -9,6 +9,7 @@ export function trimTopic(topic: string) { export async function copyToClipboard(text: string) { try { await navigator.clipboard.writeText(text); + showToast(Locale.Copy.Success); } catch (error) { const textArea = document.createElement("textarea"); textArea.value = text; @@ -17,11 +18,11 @@ export async function copyToClipboard(text: string) { textArea.select(); try { document.execCommand("copy"); + showToast(Locale.Copy.Success); } catch (error) { showToast(Locale.Copy.Failed); } - } finally { - showToast(Locale.Copy.Success); + document.body.removeChild(textArea); } } From 6420f615662be17e27f83caa3058606261e0db71 Mon Sep 17 00:00:00 2001 From: Yifei Zhang Date: Fri, 7 Apr 2023 11:23:10 +0800 Subject: [PATCH 062/149] fix: #537 delete chat button style --- app/components/home.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/home.module.scss b/app/components/home.module.scss index da954dc1d..abef999a5 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -157,6 +157,7 @@ right: -20px; transition: all ease 0.3s; opacity: 0; + cursor: pointer; } .chat-item:hover > .chat-item-delete { From 3656c8458fa955570dff2e0d6cb076e3e5a8e7e9 Mon Sep 17 00:00:00 2001 From: leedom Date: Fri, 7 Apr 2023 12:17:37 +0800 Subject: [PATCH 063/149] feat: textarea with adaptive height --- app/calcTextareaHeight.js | 104 -------------------------------------- app/components/chat.tsx | 24 ++++++++- app/components/home.tsx | 2 - app/utils.ts | 4 ++ tsconfig.json | 2 +- 5 files changed, 28 insertions(+), 108 deletions(-) delete mode 100644 app/calcTextareaHeight.js diff --git a/app/calcTextareaHeight.js b/app/calcTextareaHeight.js deleted file mode 100644 index 2324473dc..000000000 --- a/app/calcTextareaHeight.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * fork from element-ui - * https://github.com/ElemeFE/element/blob/master/packages/input/src/calcTextareaHeight.js - */ - -let hiddenTextarea; - -const HIDDEN_STYLE = ` - height:0 !important; - visibility:hidden !important; - overflow:hidden !important; - position:absolute !important; - z-index:-1000 !important; - top:0 !important; - right:0 !important -`; - -const CONTEXT_STYLE = [ - "letter-spacing", - "line-height", - "padding-top", - "padding-bottom", - "font-family", - "font-weight", - "font-size", - "text-rendering", - "text-transform", - "width", - "text-indent", - "padding-left", - "padding-right", - "border-width", - "box-sizing", -]; - -function calculateNodeStyling(targetElement) { - const style = window.getComputedStyle(targetElement); - - const boxSizing = style.getPropertyValue("box-sizing"); - - const paddingSize = - parseFloat(style.getPropertyValue("padding-bottom")) + - parseFloat(style.getPropertyValue("padding-top")); - - const borderSize = - parseFloat(style.getPropertyValue("border-bottom-width")) + - parseFloat(style.getPropertyValue("border-top-width")); - - const contextStyle = CONTEXT_STYLE.map( - (name) => `${name}:${style.getPropertyValue(name)}`, - ).join(";"); - - return { contextStyle, paddingSize, borderSize, boxSizing }; -} - -export default function calcTextareaHeight( - targetElement, - minRows = 2, - maxRows = 4, -) { - if (!hiddenTextarea) { - hiddenTextarea = document.createElement("textarea"); - document.body.appendChild(hiddenTextarea); - } - - let { paddingSize, borderSize, boxSizing, contextStyle } = - calculateNodeStyling(targetElement); - - hiddenTextarea.setAttribute("style", `${contextStyle};${HIDDEN_STYLE}`); - hiddenTextarea.value = targetElement.value || targetElement.placeholder || ""; - - let height = hiddenTextarea.scrollHeight; - const result = {}; - - if (boxSizing === "border-box") { - height = height + borderSize; - } else if (boxSizing === "content-box") { - height = height - paddingSize; - } - - hiddenTextarea.value = ""; - let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; - - if (minRows !== null) { - let minHeight = singleRowHeight * minRows; - if (boxSizing === "border-box") { - minHeight = minHeight + paddingSize + borderSize; - } - height = Math.max(minHeight, height); - result.minHeight = `${minHeight}px`; - } - if (maxRows !== null) { - let maxHeight = singleRowHeight * maxRows; - if (boxSizing === "border-box") { - maxHeight = maxHeight + paddingSize + borderSize; - } - height = Math.min(maxHeight, height); - } - result.height = `${height}px`; - hiddenTextarea.parentNode && - hiddenTextarea.parentNode.removeChild(hiddenTextarea); - hiddenTextarea = null; - return result; -} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 4ab616444..b631d534e 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -41,6 +41,8 @@ import chatStyle from "./chat.module.scss"; import { Input, Modal, showModal, showToast } from "./ui-lib"; +import calcTextareaHeight from "../calcTextareaHeight"; + const Markdown = dynamic( async () => memo((await import("./markdown")).Markdown), { @@ -331,6 +333,10 @@ function useScrollToBottom() { export function Chat(props: { showSideBar?: () => void; sideBarShowing?: boolean; + autoSize: { + minRows: number; + maxRows?: number; + }; }) { type RenderMessage = Message & { preview?: boolean }; @@ -347,6 +353,7 @@ export function Chat(props: { const { submitKey, shouldSubmit } = useSubmitHandler(); const { scrollRef, setAutoScroll } = useScrollToBottom(); const [hitBottom, setHitBottom] = useState(false); + const [textareaStyle, setTextareaStyle] = useState({}); const onChatBodyScroll = (e: HTMLElement) => { const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; @@ -380,6 +387,16 @@ export function Chat(props: { dom.scrollTop = dom.scrollHeight - dom.offsetHeight + paddingBottomNum; }; + // textarea has an adaptive height + const resizeTextarea = () => { + const dom = inputRef.current; + if (!dom) return; + const { minRows, maxRows } = props.autoSize; + setTimeout(() => { + setTextareaStyle(calcTextareaHeight(dom, minRows, maxRows)); + }, 50); + }; + // only search prompts when user input is short const SEARCH_TEXT_LIMIT = 30; const onInput = (text: string) => { @@ -504,6 +521,11 @@ export function Chat(props: { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Textarea Adaptive height + useEffect(() => { + resizeTextarea(); + }); + return (
@@ -659,8 +681,8 @@ export function Chat(props: {