From 21da781350e88edde1842b74097c7908bd5c93be Mon Sep 17 00:00:00 2001 From: Yin Min Date: Wed, 19 Apr 2023 19:28:33 +0800 Subject: [PATCH 01/24] Update common.ts Add OrgID --- app/api/common.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/api/common.ts b/app/api/common.ts index 53ab18ed6..22e71884f 100644 --- a/app/api/common.ts +++ b/app/api/common.ts @@ -18,10 +18,15 @@ export async function requestOpenai(req: NextRequest) { console.log("[Proxy] ", openaiPath); console.log("[Base Url]", baseUrl); + if (process.env.OPENAI_ORG_ID) { + console.log("[Org ID]", process.env.OPENAI_ORG_ID); + } + return fetch(`${baseUrl}/${openaiPath}`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, + ...(process.env.OPENAI_ORG_ID && { "OpenAI-Organization": process.env.OPENAI_ORG_ID }), }, method: req.method, body: req.body, From 7e8973c9ffba853b46ea9d795b1a05e81828ed37 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 20 Apr 2023 22:52:14 +0800 Subject: [PATCH 02/24] feat: close #291 gpt-4 model uses black icon --- app/components/chat.tsx | 12 +++++++++--- app/icons/black-bot.svg | 28 ++++++++++++++++++++++++++++ app/store/app.ts | 8 ++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 app/icons/black-bot.svg diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 5dce8fd61..d27c138e7 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -10,6 +10,7 @@ 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 BlackBotIcon from "../icons/black-bot.svg"; import AddIcon from "../icons/add.svg"; import DeleteIcon from "../icons/delete.svg"; import MaxIcon from "../icons/max.svg"; @@ -30,6 +31,7 @@ import { createMessage, useAccessStore, Theme, + ModelType, } from "../store"; import { @@ -64,13 +66,17 @@ const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, { loading: () => , }); -export function Avatar(props: { role: Message["role"] }) { +export function Avatar(props: { role: Message["role"]; model?: ModelType }) { const config = useChatStore((state) => state.config); if (props.role !== "user") { return (
- + {props.model?.startsWith("gpt-4") ? ( + + ) : ( + + )}
); } @@ -727,7 +733,7 @@ export function Chat(props: { >
- +
{(message.preview || message.streaming) && (
diff --git a/app/icons/black-bot.svg b/app/icons/black-bot.svg new file mode 100644 index 000000000..3aad2adde --- /dev/null +++ b/app/icons/black-bot.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/store/app.ts b/app/store/app.ts index 8d875fee5..5af4374c2 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -17,6 +17,7 @@ export type Message = ChatCompletionResponseMessage & { streaming?: boolean; isError?: boolean; id?: number; + model?: ModelType; }; export function createMessage(override: Partial): Message { @@ -58,7 +59,7 @@ export interface ChatConfig { disablePromptHint: boolean; modelConfig: { - model: string; + model: ModelType; temperature: number; max_tokens: number; presence_penalty: number; @@ -96,7 +97,9 @@ export const ALL_MODELS = [ name: "gpt-3.5-turbo-0301", available: true, }, -]; +] as const; + +export type ModelType = (typeof ALL_MODELS)[number]["name"]; export function limitNumber( x: number, @@ -387,6 +390,7 @@ export const useChatStore = create()( role: "assistant", streaming: true, id: userMessage.id! + 1, + model: get().config.modelConfig.model, }); // get recent messages From e3d2dd72794aa3d2b63c477231d54b0df62111e6 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 20 Apr 2023 22:55:14 +0800 Subject: [PATCH 03/24] feat: close #427 add OPENAI_ORG_ID --- README.md | 4 ++++ README_CN.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 1764f2dbe..e9ec7e68e 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,10 @@ Access passsword, separated by comma. Override openai api request base url. +### `OPENAI_ORG_ID` (optional) + +Specify OpenAI organization ID. + ## Development > [简体中文 > 如何进行二次开发](./README_CN.md#开发) diff --git a/README_CN.md b/README_CN.md index d2d64aa00..03ec2a101 100644 --- a/README_CN.md +++ b/README_CN.md @@ -94,6 +94,10 @@ OpenAI 接口代理 URL,如果你手动配置了 openai 接口代理,请填 > 如果遇到 ssl 证书问题,请将 `BASE_URL` 的协议设置为 http。 +### `OPENAI_ORG_ID` (可选) + +指定 OpenAI 中的组织 ID。 + ## 开发 > 强烈不建议在本地进行开发或者部署,由于一些技术原因,很难在本地配置好 OpenAI API 代理,除非你能保证可以直连 OpenAI 服务器。 From 2e9e69d66c56a0d0a2468b6456477d156980126b Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 20 Apr 2023 22:58:19 +0800 Subject: [PATCH 04/24] fixup --- 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 5af4374c2..0a3ff8656 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -122,7 +122,7 @@ export function limitModel(name: string) { export const ModalConfigValidator = { model(x: string) { - return limitModel(x); + return limitModel(x) as ModelType; }, max_tokens(x: number) { return limitNumber(x, 0, 32000, 2000); From 06d503152bcba1ad9175441709d7e5c3044eea0a Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 20 Apr 2023 23:04:58 +0800 Subject: [PATCH 05/24] feat: close #928 summarize with gpt-3.5 --- app/requests.ts | 38 ++++++++++++++++++++++++++++++++------ app/store/app.ts | 16 ++++++++-------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/app/requests.ts b/app/requests.ts index 3cb838e63..9159f1cf5 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -1,5 +1,11 @@ import type { ChatRequest, ChatResponse } from "./api/openai/typing"; -import { Message, ModelConfig, useAccessStore, useChatStore } from "./store"; +import { + Message, + ModelConfig, + ModelType, + useAccessStore, + useChatStore, +} from "./store"; import { showToast } from "./components/ui-lib"; const TIME_OUT_MS = 60000; @@ -9,6 +15,7 @@ const makeRequestParam = ( options?: { filterBot?: boolean; stream?: boolean; + model?: ModelType; }, ): ChatRequest => { let sendMessages = messages.map((v) => ({ @@ -26,6 +33,11 @@ const makeRequestParam = ( // @ts-expect-error delete modelConfig.max_tokens; + // override model config + if (options?.model) { + modelConfig.model = options.model; + } + return { messages: sendMessages, stream: options?.stream, @@ -50,7 +62,7 @@ function getHeaders() { export function requestOpenaiClient(path: string) { return (body: any, method = "POST") => - fetch("/api/openai?_vercel_no_cache=1", { + fetch("/api/openai", { method, headers: { "Content-Type": "application/json", @@ -61,8 +73,16 @@ export function requestOpenaiClient(path: string) { }); } -export async function requestChat(messages: Message[]) { - const req: ChatRequest = makeRequestParam(messages, { filterBot: true }); +export async function requestChat( + messages: Message[], + options?: { + model?: ModelType; + }, +) { + const req: ChatRequest = makeRequestParam(messages, { + filterBot: true, + model: options?.model, + }); const res = await requestOpenaiClient("v1/chat/completions")(req); @@ -204,7 +224,13 @@ export async function requestChatStream( } } -export async function requestWithPrompt(messages: Message[], prompt: string) { +export async function requestWithPrompt( + messages: Message[], + prompt: string, + options?: { + model?: ModelType; + }, +) { messages = messages.concat([ { role: "user", @@ -213,7 +239,7 @@ export async function requestWithPrompt(messages: Message[], prompt: string) { }, ]); - const res = await requestChat(messages); + const res = await requestChat(messages, options); return res?.choices?.at(0)?.message?.content ?? ""; } diff --git a/app/store/app.ts b/app/store/app.ts index 0a3ff8656..fe2a07da7 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -535,14 +535,14 @@ export const useChatStore = create()( session.topic === DEFAULT_TOPIC && countMessages(session.messages) >= SUMMARIZE_MIN_LEN ) { - requestWithPrompt(session.messages, Locale.Store.Prompt.Topic).then( - (res) => { - get().updateCurrentSession( - (session) => - (session.topic = res ? trimTopic(res) : DEFAULT_TOPIC), - ); - }, - ); + requestWithPrompt(session.messages, Locale.Store.Prompt.Topic, { + model: "gpt-3.5-turbo", + }).then((res) => { + get().updateCurrentSession( + (session) => + (session.topic = res ? trimTopic(res) : DEFAULT_TOPIC), + ); + }); } const config = get().config; From 2390da11651c80bd3e0fd3935063614a5694aa02 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Thu, 20 Apr 2023 23:09:42 +0800 Subject: [PATCH 06/24] fix: #930 wont show delete for first message --- app/components/chat.tsx | 66 ++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index d27c138e7..02f461416 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -723,6 +723,11 @@ export function Chat(props: { > {messages.map((message, i) => { const isUser = message.role === "user"; + const showActions = + !isUser && + i > 0 && + !(message.preview || message.content.length === 0); + const showTyping = message.preview || message.streaming; return (
- {(message.preview || message.streaming) && ( + {showTyping && (
{Locale.Chat.Typing}
)}
- {!isUser && - !(message.preview || message.content.length === 0) && ( -
- {message.streaming ? ( -
onUserStop(message.id ?? i)} - > - {Locale.Chat.Actions.Stop} -
- ) : ( - <> -
onDelete(message.id ?? i)} - > - {Locale.Chat.Actions.Delete} -
-
onResend(message.id ?? i)} - > - {Locale.Chat.Actions.Retry} -
- - )} - + {showActions && ( +
+ {message.streaming ? (
copyToClipboard(message.content)} + onClick={() => onUserStop(message.id ?? i)} > - {Locale.Chat.Actions.Copy} + {Locale.Chat.Actions.Stop}
+ ) : ( + <> +
onDelete(message.id ?? i)} + > + {Locale.Chat.Actions.Delete} +
+
onResend(message.id ?? i)} + > + {Locale.Chat.Actions.Retry} +
+ + )} + +
copyToClipboard(message.content)} + > + {Locale.Chat.Actions.Copy}
- )} +
+ )} Date: Thu, 20 Apr 2023 23:20:25 +0800 Subject: [PATCH 07/24] feat: reactive isMobileScreen --- app/components/chat-list.tsx | 1 - app/components/chat.tsx | 13 +++++++------ app/components/home.tsx | 20 +++++++++----------- app/utils.ts | 18 ++++++++++++++++++ 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx index cab8812c3..f0139205b 100644 --- a/app/components/chat-list.tsx +++ b/app/components/chat-list.tsx @@ -10,7 +10,6 @@ import { import { useChatStore } from "../store"; import Locale from "../locales"; -import { isMobileScreen } from "../utils"; export function ChatItem(props: { onClick?: () => void; diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 02f461416..c6bc61ebf 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -38,9 +38,9 @@ import { copyToClipboard, downloadAs, getEmojiUrl, - isMobileScreen, selectOrCopy, autoGrowTextArea, + useMobileScreen, } from "../utils"; import dynamic from "next/dynamic"; @@ -438,6 +438,7 @@ export function Chat(props: { const { submitKey, shouldSubmit } = useSubmitHandler(); const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom(); const [hitBottom, setHitBottom] = useState(false); + const isMobileScreen = useMobileScreen(); const onChatBodyScroll = (e: HTMLElement) => { const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; @@ -468,7 +469,7 @@ export function Chat(props: { const rows = inputRef.current ? autoGrowTextArea(inputRef.current) : 1; const inputRows = Math.min( 5, - Math.max(2 + Number(!isMobileScreen()), rows), + Math.max(2 + Number(!isMobileScreen), rows), ); setInputRows(inputRows); }, @@ -508,7 +509,7 @@ export function Chat(props: { setBeforeInput(userInput); setUserInput(""); setPromptHints([]); - if (!isMobileScreen()) inputRef.current?.focus(); + if (!isMobileScreen) inputRef.current?.focus(); setAutoScroll(true); }; @@ -640,7 +641,7 @@ export function Chat(props: { // Auto focus useEffect(() => { - if (props.sideBarShowing && isMobileScreen()) return; + if (props.sideBarShowing && isMobileScreen) return; inputRef.current?.focus(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -688,7 +689,7 @@ export function Chat(props: { }} />
- {!isMobileScreen() && ( + {!isMobileScreen && (
: } @@ -788,7 +789,7 @@ export function Chat(props: { } onContextMenu={(e) => onRightClick(e, message)} onDoubleClickCapture={() => { - if (!isMobileScreen()) return; + if (!isMobileScreen) return; setUserInput(message.content); }} fontSize={fontSize} diff --git a/app/components/home.tsx b/app/components/home.tsx index 828b7576a..ef30243c4 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -17,7 +17,7 @@ import LoadingIcon from "../icons/three-dots.svg"; import CloseIcon from "../icons/close.svg"; import { useChatStore } from "../store"; -import { getCSSVar, isMobileScreen } from "../utils"; +import { getCSSVar, useMobileScreen } from "../utils"; import Locale from "../locales"; import { Chat } from "./chat"; @@ -103,17 +103,14 @@ function useDragSideBar() { window.addEventListener("mousemove", handleMouseMove.current); window.addEventListener("mouseup", handleMouseUp.current); }; + const isMobileScreen = useMobileScreen(); useEffect(() => { - if (isMobileScreen()) { - return; - } - - document.documentElement.style.setProperty( - "--sidebar-width", - `${limit(chatStore.config.sidebarWidth ?? 300)}px`, - ); - }, [chatStore.config.sidebarWidth]); + const sideBarWidth = isMobileScreen + ? "100vw" + : `${limit(chatStore.config.sidebarWidth ?? 300)}px`; + document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); + }, [chatStore.config.sidebarWidth, isMobileScreen]); return { onDragMouseDown, @@ -148,6 +145,7 @@ function _Home() { // drag side bar const { onDragMouseDown } = useDragSideBar(); + const isMobileScreen = useMobileScreen(); useSwitchTheme(); @@ -158,7 +156,7 @@ function _Home() { return (
{ + const onResize = () => { + setIsMobileScreen(isMobileScreen()); + }; + + window.addEventListener("resize", onResize); + + return () => { + window.removeEventListener("resize", onResize); + }; + }, []); + + return isMobileScreen_; +} + export function isMobileScreen() { return window.innerWidth <= 600; } From 693dcf12d6c6ddd610b12bbc85ebab0474e46256 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 21 Apr 2023 01:12:39 +0800 Subject: [PATCH 08/24] refactor: close #643 use react router --- app/components/chat-list.tsx | 8 +- app/components/chat.tsx | 14 +-- app/components/home.tsx | 212 +++++++++-------------------------- app/components/settings.tsx | 10 +- app/components/sidebar.tsx | 135 ++++++++++++++++++++++ app/constant.ts | 6 + app/utils.ts | 2 +- package.json | 1 + yarn.lock | 20 ++++ 9 files changed, 234 insertions(+), 174 deletions(-) create mode 100644 app/components/sidebar.tsx diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx index f0139205b..fb0f740c5 100644 --- a/app/components/chat-list.tsx +++ b/app/components/chat-list.tsx @@ -10,6 +10,8 @@ import { import { useChatStore } from "../store"; import Locale from "../locales"; +import { Link, useNavigate } from "react-router-dom"; +import { Path } from "../constant"; export function ChatItem(props: { onClick?: () => void; @@ -59,6 +61,7 @@ export function ChatList() { state.moveSession, ]); const chatStore = useChatStore(); + const navigate = useNavigate(); const onDragEnd: OnDragEndResponder = (result) => { const { destination, source } = result; @@ -94,7 +97,10 @@ export function ChatList() { id={item.id} index={i} selected={i === selectedIndex} - onClick={() => selectSession(i)} + onClick={() => { + navigate(Path.Chat); + selectSession(i); + }} onDelete={() => chatStore.deleteSession(i)} /> ))} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index c6bc61ebf..bab422983 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -54,6 +54,8 @@ import styles from "./home.module.scss"; import chatStyle from "./chat.module.scss"; import { Input, Modal, showModal } from "./ui-lib"; +import { useNavigate } from "react-router-dom"; +import { Path } from "../constant"; const Markdown = dynamic( async () => memo((await import("./markdown")).Markdown), @@ -418,10 +420,7 @@ export function ChatActions(props: { ); } -export function Chat(props: { - showSideBar?: () => void; - sideBarShowing?: boolean; -}) { +export function Chat() { type RenderMessage = Message & { preview?: boolean }; const chatStore = useChatStore(); @@ -439,6 +438,7 @@ export function Chat(props: { const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom(); const [hitBottom, setHitBottom] = useState(false); const isMobileScreen = useMobileScreen(); + const navigate = useNavigate(); const onChatBodyScroll = (e: HTMLElement) => { const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; @@ -641,7 +641,7 @@ export function Chat(props: { // Auto focus useEffect(() => { - if (props.sideBarShowing && isMobileScreen) return; + if (isMobileScreen) return; inputRef.current?.focus(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -666,7 +666,7 @@ export function Chat(props: { icon={} bordered title={Locale.Chat.Actions.ChatList} - onClick={props?.showSideBar} + onClick={() => navigate(Path.Home)} />
@@ -830,7 +830,7 @@ export function Chat(props: { setAutoScroll(false); setTimeout(() => setPromptHints([]), 500); }} - autoFocus={!props?.sideBarShowing} + autoFocus rows={inputRows} /> +
{!props.noLogo && }
@@ -38,7 +38,7 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, { loading: () => , }); -const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { +const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, { loading: () => , }); @@ -73,50 +73,6 @@ function useSwitchTheme() { }, [config.theme]); } -function useDragSideBar() { - const limit = (x: number) => Math.min(500, Math.max(220, x)); - - const chatStore = useChatStore(); - const startX = useRef(0); - const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300); - const lastUpdateTime = useRef(Date.now()); - - const handleMouseMove = useRef((e: MouseEvent) => { - if (Date.now() < lastUpdateTime.current + 100) { - return; - } - lastUpdateTime.current = Date.now(); - const d = e.clientX - startX.current; - const nextWidth = limit(startDragWidth.current + d); - chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth)); - }); - - const handleMouseUp = useRef(() => { - startDragWidth.current = chatStore.config.sidebarWidth ?? 300; - window.removeEventListener("mousemove", handleMouseMove.current); - window.removeEventListener("mouseup", handleMouseUp.current); - }); - - const onDragMouseDown = (e: MouseEvent) => { - startX.current = e.clientX; - - window.addEventListener("mousemove", handleMouseMove.current); - window.addEventListener("mouseup", handleMouseUp.current); - }; - const isMobileScreen = useMobileScreen(); - - useEffect(() => { - const sideBarWidth = isMobileScreen - ? "100vw" - : `${limit(chatStore.config.sidebarWidth ?? 300)}px`; - document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); - }, [chatStore.config.sidebarWidth, isMobileScreen]); - - return { - onDragMouseDown, - }; -} - const useHasHydrated = () => { const [hasHydrated, setHasHydrated] = useState(false); @@ -127,130 +83,64 @@ const useHasHydrated = () => { return hasHydrated; }; -function _Home() { - const [createNewSession, currentIndex, removeSession] = useChatStore( - (state) => [ - state.newSession, - state.currentSessionIndex, - state.removeSession, - ], - ); - const chatStore = useChatStore(); - const loading = !useHasHydrated(); - const [showSideBar, setShowSideBar] = useState(true); - +function WideScreen() { // setting - const [openSettings, setOpenSettings] = useState(false); const config = useChatStore((state) => state.config); - // drag side bar - const { onDragMouseDown } = useDragSideBar(); - const isMobileScreen = useMobileScreen(); - - useSwitchTheme(); - - if (loading) { - return ; - } - return (
-
-
-
ChatGPT Next
-
- Build your own AI assistant. -
-
- -
-
- -
{ - setOpenSettings(false); - setShowSideBar(false); - }} - > - -
- -
-
-
- } - onClick={chatStore.deleteSession} - /> -
-
- } - onClick={() => { - setOpenSettings(true); - setShowSideBar(false); - }} - shadow - /> -
- -
-
- } - text={Locale.Home.NewChat} - onClick={() => { - createNewSession(); - setShowSideBar(false); - }} - shadow - /> -
-
- -
onDragMouseDown(e as any)} - >
+
+
- {openSettings ? ( - { - setOpenSettings(false); - setShowSideBar(true); - }} - /> - ) : ( - setShowSideBar(true)} - sideBarShowing={showSideBar} - /> - )} + + } /> + } /> + } /> + +
+
+ ); +} + +function MobileScreen() { + const location = useLocation(); + const isHome = location.pathname === Path.Home; + + return ( +
+
+ +
+ +
+ + + } /> + } /> +
); } export function Home() { + useSwitchTheme(); + + const isMobileScreen = useMobileScreen(); + + if (!useHasHydrated()) { + return ; + } + return ( - <_Home> + {isMobileScreen ? : } ); } diff --git a/app/components/settings.tsx b/app/components/settings.tsx index d81b5b358..021484927 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -29,10 +29,11 @@ import { Avatar } from "./chat"; import Locale, { AllLangs, changeLang, getLang } from "../locales"; import { copyToClipboard, getEmojiUrl } from "../utils"; import Link from "next/link"; -import { UPDATE_URL } from "../constant"; +import { Path, UPDATE_URL } from "../constant"; import { Prompt, SearchService, usePromptStore } from "../store/prompt"; import { ErrorBoundary } from "./error"; import { InputRange } from "./input-range"; +import { useNavigate } from "react-router-dom"; function UserPromptModal(props: { onClose?: () => void }) { const promptStore = usePromptStore(); @@ -176,7 +177,8 @@ function PasswordInput(props: HTMLProps) { ); } -export function Settings(props: { closeSettings: () => void }) { +export function Settings() { + const navigate = useNavigate(); const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [config, updateConfig, resetConfig, clearAllData, clearSessions] = useChatStore((state) => [ @@ -235,7 +237,7 @@ export function Settings(props: { closeSettings: () => void }) { useEffect(() => { const keydownEvent = (e: KeyboardEvent) => { if (e.key === "Escape") { - props.closeSettings(); + navigate(Path.Home); } }; document.addEventListener("keydown", keydownEvent); @@ -290,7 +292,7 @@ export function Settings(props: { closeSettings: () => void }) {
} - onClick={props.closeSettings} + onClick={() => navigate(Path.Home)} bordered title={Locale.Settings.Actions.Close} /> diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx new file mode 100644 index 000000000..338dec193 --- /dev/null +++ b/app/components/sidebar.tsx @@ -0,0 +1,135 @@ +import { useState, useEffect, useRef } from "react"; + +import styles from "./home.module.scss"; + +import { IconButton } from "./button"; +import SettingsIcon from "../icons/settings.svg"; +import GithubIcon from "../icons/github.svg"; +import ChatGptIcon from "../icons/chatgpt.svg"; +import AddIcon from "../icons/add.svg"; +import CloseIcon from "../icons/close.svg"; +import Locale from "../locales"; + +import { useChatStore } from "../store"; + +import { Path, REPO_URL } from "../constant"; + +import { HashRouter as Router, Link, useNavigate } from "react-router-dom"; +import { useMobileScreen } from "../utils"; +import { ChatList } from "./chat-list"; + +function useDragSideBar() { + const limit = (x: number) => Math.min(500, Math.max(220, x)); + + const chatStore = useChatStore(); + const startX = useRef(0); + const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300); + const lastUpdateTime = useRef(Date.now()); + + const handleMouseMove = useRef((e: MouseEvent) => { + if (Date.now() < lastUpdateTime.current + 100) { + return; + } + lastUpdateTime.current = Date.now(); + const d = e.clientX - startX.current; + const nextWidth = limit(startDragWidth.current + d); + chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth)); + }); + + const handleMouseUp = useRef(() => { + startDragWidth.current = chatStore.config.sidebarWidth ?? 300; + window.removeEventListener("mousemove", handleMouseMove.current); + window.removeEventListener("mouseup", handleMouseUp.current); + }); + + const onDragMouseDown = (e: MouseEvent) => { + startX.current = e.clientX; + + window.addEventListener("mousemove", handleMouseMove.current); + window.addEventListener("mouseup", handleMouseUp.current); + }; + const isMobileScreen = useMobileScreen(); + + useEffect(() => { + const sideBarWidth = isMobileScreen + ? "100vw" + : `${limit(chatStore.config.sidebarWidth ?? 300)}px`; + document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); + }, [chatStore.config.sidebarWidth, isMobileScreen]); + + return { + onDragMouseDown, + }; +} + +export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) { + const chatStore = useChatStore(); + + // drag side bar + const { onDragMouseDown } = useDragSideBar(); + const navigate = useNavigate(); + const isMobileScreen = useMobileScreen(); + + return ( + <> +
+
ChatGPT Next
+
+ Build your own AI assistant. +
+
+ +
+
+ +
{ + if (e.target === e.currentTarget) { + navigate(Path.Home); + } + props.setShowSideBar?.(false); + }} + > + +
+ +
+
+
+ } + onClick={chatStore.deleteSession} + /> +
+
+ + } shadow /> + +
+ +
+
+ } + text={Locale.Home.NewChat} + onClick={() => { + chatStore.newSession(); + props.setShowSideBar?.(false); + }} + shadow + /> +
+
+ +
onDragMouseDown(e as any)} + >
+ + ); +} diff --git a/app/constant.ts b/app/constant.ts index 6f08ad756..687445462 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -6,3 +6,9 @@ export const UPDATE_URL = `${REPO_URL}#keep-updated`; export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`; export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`; export const RUNTIME_CONFIG_DOM = "danger-runtime-config"; + +export enum Path { + Home = "/", + Chat = "/chat", + Settings = "/settings", +} diff --git a/app/utils.ts b/app/utils.ts index 9d6e90622..0af9fef5b 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -49,7 +49,7 @@ export function isIOS() { } export function useMobileScreen() { - const [isMobileScreen_, setIsMobileScreen] = useState(false); + const [isMobileScreen_, setIsMobileScreen] = useState(isMobileScreen()); useEffect(() => { const onResize = () => { setIsMobileScreen(isMobileScreen()); diff --git a/package.json b/package.json index 19047ad11..785005519 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", + "react-router-dom": "^6.10.0", "rehype-highlight": "^6.0.0", "rehype-katex": "^6.0.2", "remark-breaks": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index 342ea4a44..b7d9f8309 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1189,6 +1189,11 @@ tiny-glob "^0.2.9" tslib "^2.4.0" +"@remix-run/router@1.5.0": + version "1.5.0" + resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc" + integrity sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg== + "@rushstack/eslint-patch@^1.1.3": version "1.2.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728" @@ -4296,6 +4301,21 @@ react-redux@^8.0.4: react-is "^18.0.0" use-sync-external-store "^1.0.0" +react-router-dom@^6.10.0: + version "6.10.0" + resolved "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.10.0.tgz#090ddc5c84dc41b583ce08468c4007c84245f61f" + integrity sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg== + dependencies: + "@remix-run/router" "1.5.0" + react-router "6.10.0" + +react-router@6.10.0: + version "6.10.0" + resolved "https://registry.npmmirror.com/react-router/-/react-router-6.10.0.tgz#230f824fde9dd0270781b5cb497912de32c0a971" + integrity sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ== + dependencies: + "@remix-run/router" "1.5.0" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" From 5185166e3b5b1f2b802833c79565c51c766b912e Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 21 Apr 2023 01:18:49 +0800 Subject: [PATCH 09/24] fixup --- app/components/home.tsx | 2 -- app/utils.ts | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/components/home.tsx b/app/components/home.tsx index efca5c0b3..8b5e1d74d 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -21,7 +21,6 @@ import { HashRouter as Router, Routes, Route, - useNavigation, useLocation, } from "react-router-dom"; @@ -131,7 +130,6 @@ function MobileScreen() { export function Home() { useSwitchTheme(); - const isMobileScreen = useMobileScreen(); if (!useHasHydrated()) { diff --git a/app/utils.ts b/app/utils.ts index 0af9fef5b..dfec8d3e9 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -66,6 +66,9 @@ export function useMobileScreen() { } export function isMobileScreen() { + if (typeof window === "undefined") { + return false; + } return window.innerWidth <= 600; } From 82ad0573be93b0ee43f9cc52b865ea8878988dfa Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 21 Apr 2023 02:52:53 +0800 Subject: [PATCH 10/24] feat: close #380 collapse side bar --- app/components/chat-list.tsx | 31 ++++++++++----- app/components/home.module.scss | 69 ++++++++++++++++++++++++++++++++- app/components/home.tsx | 13 ++----- app/components/sidebar.tsx | 43 ++++++++++++-------- app/constant.ts | 4 ++ 5 files changed, 125 insertions(+), 35 deletions(-) diff --git a/app/components/chat-list.tsx b/app/components/chat-list.tsx index fb0f740c5..626336afd 100644 --- a/app/components/chat-list.tsx +++ b/app/components/chat-list.tsx @@ -22,6 +22,7 @@ export function ChatItem(props: { selected: boolean; id: number; index: number; + narrow?: boolean; }) { return ( @@ -35,13 +36,20 @@ export function ChatItem(props: { {...provided.draggableProps} {...provided.dragHandleProps} > -
{props.title}
-
-
- {Locale.ChatItem.ChatItemCount(props.count)} -
-
{props.time}
-
+ {props.narrow ? ( +
{props.count}
+ ) : ( + <> +
{props.title}
+
+
+ {Locale.ChatItem.ChatItemCount(props.count)} +
+
{props.time}
+
+ + )} +
@@ -51,7 +59,7 @@ export function ChatItem(props: { ); } -export function ChatList() { +export function ChatList(props: { narrow?: boolean }) { const [sessions, selectedIndex, selectSession, removeSession, moveSession] = useChatStore((state) => [ state.sessions, @@ -101,7 +109,12 @@ export function ChatList() { navigate(Path.Chat); selectSession(i); }} - onDelete={() => chatStore.deleteSession(i)} + onDelete={() => { + if (!props.narrow || confirm(Locale.Home.DeleteChat)) { + chatStore.deleteSession(i); + } + }} + narrow={props.narrow} /> ))} {provided.placeholder} diff --git a/app/components/home.module.scss b/app/components/home.module.scss index 9bf0d5719..38e755bc5 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -50,7 +50,7 @@ flex-direction: column; box-shadow: inset -2px 0px 2px 0px rgb(0, 0, 0, 0.05); position: relative; - transition: width ease 0.1s; + transition: width ease 0.05s; } .sidebar-drag { @@ -126,11 +126,13 @@ .sidebar-title { font-size: 20px; font-weight: bold; + animation: slide-in ease 0.3s; } .sidebar-sub-title { font-size: 12px; font-weight: 400px; + animation: slide-in ease 0.3s; } .sidebar-body { @@ -171,6 +173,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + animation: slide-in ease 0.3s; } .chat-item-delete { @@ -197,6 +200,7 @@ color: rgb(166, 166, 166); font-size: 12px; margin-top: 8px; + animation: slide-in ease 0.3s; } .chat-item-count, @@ -206,6 +210,69 @@ white-space: nowrap; } +.narrow-sidebar { + .sidebar-title, + .sidebar-sub-title { + display: none; + } + .sidebar-logo { + position: relative; + display: flex; + justify-content: center; + } + + .chat-item { + padding: 0; + min-height: 50px; + display: flex; + justify-content: center; + align-items: center; + transition: all ease 0.3s; + + &:hover { + .chat-item-narrow { + transform: scale(0.7) translateX(-50%); + } + } + } + + .chat-item-narrow { + font-weight: bolder; + font-size: 24px; + line-height: 0; + font-weight: lighter; + color: var(--black); + transform: translateX(0); + transition: all ease 0.3s; + opacity: 0.1; + padding: 4px; + } + + .chat-item-delete { + top: 15px; + } + + .chat-item:hover > .chat-item-delete { + opacity: 0.5; + right: 5px; + } + + .sidebar-tail { + flex-direction: column; + align-items: center; + + .sidebar-actions { + flex-direction: column; + align-items: center; + + .sidebar-action { + margin-right: 0; + margin-bottom: 15px; + } + } + } +} + .sidebar-tail { display: flex; justify-content: space-between; diff --git a/app/components/home.tsx b/app/components/home.tsx index 8b5e1d74d..123be03a9 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -41,7 +41,7 @@ const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, { loading: () => , }); -function useSwitchTheme() { +export function useSwitchTheme() { const config = useChatStore((state) => state.config); useEffect(() => { @@ -83,7 +83,6 @@ const useHasHydrated = () => { }; function WideScreen() { - // setting const config = useChatStore((state) => state.config); return ( @@ -92,9 +91,7 @@ function WideScreen() { config.tightBorder ? styles["tight-container"] : styles.container }`} > -
- -
+
@@ -113,9 +110,7 @@ function MobileScreen() { return (
-
- -
+
@@ -129,8 +124,8 @@ function MobileScreen() { } export function Home() { - useSwitchTheme(); const isMobileScreen = useMobileScreen(); + useSwitchTheme(); if (!useHasHydrated()) { return ; diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index 338dec193..71e75f8ab 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -12,14 +12,20 @@ import Locale from "../locales"; import { useChatStore } from "../store"; -import { Path, REPO_URL } from "../constant"; +import { + MAX_SIDEBAR_WIDTH, + MIN_SIDEBAR_WIDTH, + NARROW_SIDEBAR_WIDTH, + Path, + REPO_URL, +} from "../constant"; import { HashRouter as Router, Link, useNavigate } from "react-router-dom"; import { useMobileScreen } from "../utils"; import { ChatList } from "./chat-list"; function useDragSideBar() { - const limit = (x: number) => Math.min(500, Math.max(220, x)); + const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x); const chatStore = useChatStore(); const startX = useRef(0); @@ -27,7 +33,7 @@ function useDragSideBar() { const lastUpdateTime = useRef(Date.now()); const handleMouseMove = useRef((e: MouseEvent) => { - if (Date.now() < lastUpdateTime.current + 100) { + if (Date.now() < lastUpdateTime.current + 50) { return; } lastUpdateTime.current = Date.now(); @@ -49,29 +55,36 @@ function useDragSideBar() { window.addEventListener("mouseup", handleMouseUp.current); }; const isMobileScreen = useMobileScreen(); + const shouldNarrow = + !isMobileScreen && chatStore.config.sidebarWidth < MIN_SIDEBAR_WIDTH; useEffect(() => { - const sideBarWidth = isMobileScreen - ? "100vw" - : `${limit(chatStore.config.sidebarWidth ?? 300)}px`; + const barWidth = shouldNarrow + ? NARROW_SIDEBAR_WIDTH + : limit(chatStore.config.sidebarWidth ?? 300); + const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`; document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); - }, [chatStore.config.sidebarWidth, isMobileScreen]); + }, [chatStore.config.sidebarWidth, isMobileScreen, shouldNarrow]); return { onDragMouseDown, + shouldNarrow, }; } -export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) { +export function SideBar(props: { className?: string }) { const chatStore = useChatStore(); // drag side bar - const { onDragMouseDown } = useDragSideBar(); + const { onDragMouseDown, shouldNarrow } = useDragSideBar(); const navigate = useNavigate(); - const isMobileScreen = useMobileScreen(); return ( - <> +
ChatGPT Next
@@ -88,10 +101,9 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) { if (e.target === e.currentTarget) { navigate(Path.Home); } - props.setShowSideBar?.(false); }} > - +
@@ -116,10 +128,9 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
} - text={Locale.Home.NewChat} + text={shouldNarrow ? undefined : Locale.Home.NewChat} onClick={() => { chatStore.newSession(); - props.setShowSideBar?.(false); }} shadow /> @@ -130,6 +141,6 @@ export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) { className={styles["sidebar-drag"]} onMouseDown={(e) => onDragMouseDown(e as any)} >
- +
); } diff --git a/app/constant.ts b/app/constant.ts index 687445462..43ae4cc68 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -12,3 +12,7 @@ export enum Path { Chat = "/chat", Settings = "/settings", } + +export const MAX_SIDEBAR_WIDTH = 500; +export const MIN_SIDEBAR_WIDTH = 230; +export const NARROW_SIDEBAR_WIDTH = 100; From b6a7104b60b462f79cbdd7be6b5b8f4285196b62 Mon Sep 17 00:00:00 2001 From: Shi Liang <7258605+shih-liang@users.noreply.github.com> Date: Fri, 21 Apr 2023 13:03:02 +0800 Subject: [PATCH 11/24] chat-stream: runtime = "experimental-edge"; --- app/api/chat-stream/route.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/api/chat-stream/route.ts b/app/api/chat-stream/route.ts index 41f135495..22550e39c 100644 --- a/app/api/chat-stream/route.ts +++ b/app/api/chat-stream/route.ts @@ -59,6 +59,4 @@ export async function POST(req: NextRequest) { } } -export const config = { - runtime: "edge", -}; +export const runtime = "experimental-edge"; From 8966fd3b23935e86212840e17577d18d263ccac9 Mon Sep 17 00:00:00 2001 From: Shi Liang <7258605+shih-liang@users.noreply.github.com> Date: Fri, 21 Apr 2023 13:03:38 +0800 Subject: [PATCH 12/24] openai runtime = "experimental-edge"; --- app/api/openai/route.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/api/openai/route.ts b/app/api/openai/route.ts index 0ac94bdd5..bed70d928 100644 --- a/app/api/openai/route.ts +++ b/app/api/openai/route.ts @@ -30,6 +30,4 @@ export async function GET(req: NextRequest) { return makeRequest(req); } -export const config = { - runtime: "edge", -}; +export const runtime = "experimental-edge"; From 596a46846ad675c6a9304bd59700a13a47b5653e Mon Sep 17 00:00:00 2001 From: jzjwonderful Date: Fri, 21 Apr 2023 17:26:40 +0800 Subject: [PATCH 13/24] fix bug 978 --- scripts/setup.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index b96533398..751a9ac17 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -29,13 +29,13 @@ esac if ! command -v node >/dev/null || ! command -v git >/dev/null || ! command -v yarn >/dev/null; then case "$(uname -s)" in Linux) - if [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=\"ubuntu\"" ]]; then + if [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=ubuntu" ]]; then sudo apt-get update sudo apt-get -y install nodejs git yarn - elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=\"centos\"" ]]; then + elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=centos" ]]; then sudo yum -y install epel-release sudo yum -y install nodejs git yarn - elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=\"arch\"" ]]; then + elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=arch" ]]; then sudo pacman -Syu -y sudo pacman -S -y nodejs git yarn else From 4d45c07bf2096e9f12c142c010e3893c905d35f1 Mon Sep 17 00:00:00 2001 From: Zhenyu Zhu Date: Fri, 21 Apr 2023 18:52:32 +0800 Subject: [PATCH 14/24] fix: adjust presence_penalty step 0.1 --- app/components/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 021484927..4b552e289 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -645,7 +645,7 @@ export function Settings() { value={config.modelConfig.presence_penalty?.toFixed(1)} min="-2" max="2" - step="0.5" + step="0.1" onChange={(e) => { updateConfig( (config) => From 209a727fe92d9dac8e33c49a83efef702c661a7e Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 21 Apr 2023 23:22:02 +0800 Subject: [PATCH 15/24] feat: close #928 summarize with gpt3.5 --- app/requests.ts | 2 ++ app/store/app.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/app/requests.ts b/app/requests.ts index 9159f1cf5..ce72bb7cf 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -149,6 +149,7 @@ export async function requestChatStream( options?: { filterBot?: boolean; modelConfig?: ModelConfig; + model?: ModelType; onMessage: (message: string, done: boolean) => void; onError: (error: Error, statusCode?: number) => void; onController?: (controller: AbortController) => void; @@ -157,6 +158,7 @@ export async function requestChatStream( const req = makeRequestParam(messages, { stream: true, filterBot: options?.filterBot, + model: options?.model, }); console.log("[Request] ", req); diff --git a/app/store/app.ts b/app/store/app.ts index fe2a07da7..89995e0bb 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -583,6 +583,7 @@ export const useChatStore = create()( }), { filterBot: false, + model: "gpt-3.5-turbo", onMessage(message, done) { session.memoryPrompt = message; if (done) { From e1ce1f2f4002abbb0cb86cf688957457e92afb90 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 21 Apr 2023 23:28:53 +0800 Subject: [PATCH 16/24] feat: close #976 esc to close modal --- app/components/ui-lib.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/app/components/ui-lib.tsx b/app/components/ui-lib.tsx index a72aa868f..ffc05cf8e 100644 --- a/app/components/ui-lib.tsx +++ b/app/components/ui-lib.tsx @@ -2,7 +2,7 @@ import styles from "./ui-lib.module.scss"; import LoadingIcon from "../icons/three-dots.svg"; import CloseIcon from "../icons/close.svg"; import { createRoot } from "react-dom/client"; -import React from "react"; +import React, { useEffect } from "react"; export function Popover(props: { children: JSX.Element; @@ -64,6 +64,21 @@ interface ModalProps { onClose?: () => void; } export function Modal(props: ModalProps) { + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + props.onClose?.(); + } + }; + + window.addEventListener("keydown", onKeyDown); + + return () => { + window.removeEventListener("keydown", onKeyDown); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return (
From ab826363ea4d585becb70d53778d45c0aa312403 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Fri, 21 Apr 2023 23:37:25 +0800 Subject: [PATCH 17/24] fix: #965 improve loading animation --- app/components/home.module.scss | 5 ++++- app/components/home.tsx | 7 ++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/components/home.module.scss b/app/components/home.module.scss index 38e755bc5..1c021d884 100644 --- a/app/components/home.module.scss +++ b/app/components/home.module.scss @@ -313,6 +313,10 @@ .chat-message { display: flex; flex-direction: row; + + &:last-child { + animation: slide-in ease 0.3s; + } } .chat-message-user { @@ -325,7 +329,6 @@ display: flex; flex-direction: column; align-items: flex-start; - animation: slide-in ease 0.3s; &:hover { .chat-message-top-actions { diff --git a/app/components/home.tsx b/app/components/home.tsx index 123be03a9..ac3ce90e2 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -2,7 +2,7 @@ require("../polyfill"); -import { useState, useEffect } from "react"; +import { useState, useEffect, StyleHTMLAttributes } from "react"; import styles from "./home.module.scss"; @@ -23,6 +23,7 @@ import { Route, useLocation, } from "react-router-dom"; +import { SideBar } from "./sidebar"; export function Loading(props: { noLogo?: boolean }) { return ( @@ -37,10 +38,6 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, { loading: () => , }); -const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, { - loading: () => , -}); - export function useSwitchTheme() { const config = useChatStore((state) => state.config); From ae479f4a92d1f5a20cfd5265a932bc329a029d58 Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sat, 22 Apr 2023 00:12:07 +0800 Subject: [PATCH 18/24] fix: #963 config not work --- app/components/chat.tsx | 22 ++--- app/components/home.tsx | 6 +- app/components/settings.tsx | 16 ++-- app/components/sidebar.tsx | 26 +++--- app/locales/cn.ts | 2 +- app/locales/de.ts | 2 +- app/locales/en.ts | 2 +- app/locales/es.ts | 2 +- app/locales/it.ts | 2 +- app/locales/jp.ts | 2 +- app/locales/tr.ts | 2 +- app/locales/tw.ts | 2 +- app/requests.ts | 3 +- app/store/app.ts | 159 ++---------------------------------- app/store/config.ts | 135 ++++++++++++++++++++++++++++++ app/store/index.ts | 1 + 16 files changed, 190 insertions(+), 194 deletions(-) create mode 100644 app/store/config.ts diff --git a/app/components/chat.tsx b/app/components/chat.tsx index bab422983..c5cc54299 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -32,6 +32,7 @@ import { useAccessStore, Theme, ModelType, + useAppConfig, } from "../store"; import { @@ -69,7 +70,7 @@ const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, { }); export function Avatar(props: { role: Message["role"]; model?: ModelType }) { - const config = useChatStore((state) => state.config); + const config = useAppConfig(); if (props.role !== "user") { return ( @@ -285,7 +286,7 @@ function PromptToast(props: { } function useSubmitHandler() { - const config = useChatStore((state) => state.config); + const config = useAppConfig(); const submitKey = config.submitKey; const shouldSubmit = (e: React.KeyboardEvent) => { @@ -361,16 +362,16 @@ export function ChatActions(props: { scrollToBottom: () => void; hitBottom: boolean; }) { - const chatStore = useChatStore(); + const config = useAppConfig(); // switch themes - const theme = chatStore.config.theme; + const theme = config.theme; function nextTheme() { const themes = [Theme.Auto, Theme.Light, Theme.Dark]; const themeIndex = themes.indexOf(theme); const nextIndex = (themeIndex + 1) % themes.length; const nextTheme = themes[nextIndex]; - chatStore.updateConfig((config) => (config.theme = nextTheme)); + config.update((config) => (config.theme = nextTheme)); } // stop all responses @@ -428,7 +429,8 @@ export function Chat() { state.currentSession(), state.currentSessionIndex, ]); - const fontSize = useChatStore((state) => state.config.fontSize); + const config = useAppConfig(); + const fontSize = config.fontSize; const inputRef = useRef(null); const [userInput, setUserInput] = useState(""); @@ -492,7 +494,7 @@ export function Chat() { // clear search results if (n === 0) { setPromptHints([]); - } else if (!chatStore.config.disablePromptHint && n < SEARCH_TEXT_LIMIT) { + } else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) { // check if need to trigger auto completion if (text.startsWith("/")) { let searchText = text.slice(1); @@ -583,8 +585,6 @@ export function Chat() { inputRef.current?.focus(); }; - const config = useChatStore((state) => state.config); - const context: RenderMessage[] = session.context.slice(); const accessStore = useAccessStore(); @@ -692,10 +692,10 @@ export function Chat() { {!isMobileScreen && (
: } + icon={config.tightBorder ? : } bordered onClick={() => { - chatStore.updateConfig( + config.update( (config) => (config.tightBorder = !config.tightBorder), ); }} diff --git a/app/components/home.tsx b/app/components/home.tsx index ac3ce90e2..32334028f 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -9,7 +9,6 @@ import styles from "./home.module.scss"; import BotIcon from "../icons/bot.svg"; import LoadingIcon from "../icons/three-dots.svg"; -import { useChatStore } from "../store"; import { getCSSVar, useMobileScreen } from "../utils"; import { Chat } from "./chat"; @@ -24,6 +23,7 @@ import { useLocation, } from "react-router-dom"; import { SideBar } from "./sidebar"; +import { useAppConfig } from "../store/config"; export function Loading(props: { noLogo?: boolean }) { return ( @@ -39,7 +39,7 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, { }); export function useSwitchTheme() { - const config = useChatStore((state) => state.config); + const config = useAppConfig(); useEffect(() => { document.body.classList.remove("light"); @@ -80,7 +80,7 @@ const useHasHydrated = () => { }; function WideScreen() { - const config = useChatStore((state) => state.config); + const config = useAppConfig(); return (
) { export function Settings() { const navigate = useNavigate(); const [showEmojiPicker, setShowEmojiPicker] = useState(false); - const [config, updateConfig, resetConfig, clearAllData, clearSessions] = - useChatStore((state) => [ - state.config, - state.updateConfig, - state.resetConfig, - state.clearAllData, - state.clearSessions, - ]); + const config = useAppConfig(); + const updateConfig = config.update; + const resetConfig = config.reset; + const [clearAllData, clearSessions] = useChatStore((state) => [ + state.clearAllData, + state.clearSessions, + ]); const updateStore = useUpdateStore(); const [checkingUpdate, setCheckingUpdate] = useState(false); diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx index 71e75f8ab..d0c99dd19 100644 --- a/app/components/sidebar.tsx +++ b/app/components/sidebar.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; import styles from "./home.module.scss"; @@ -10,7 +10,7 @@ import AddIcon from "../icons/add.svg"; import CloseIcon from "../icons/close.svg"; import Locale from "../locales"; -import { useChatStore } from "../store"; +import { useAppConfig, useChatStore } from "../store"; import { MAX_SIDEBAR_WIDTH, @@ -20,16 +20,20 @@ import { REPO_URL, } from "../constant"; -import { HashRouter as Router, Link, useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { useMobileScreen } from "../utils"; -import { ChatList } from "./chat-list"; +import dynamic from "next/dynamic"; + +const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, { + loading: () => null, +}); function useDragSideBar() { const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x); - const chatStore = useChatStore(); + const config = useAppConfig(); const startX = useRef(0); - const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300); + const startDragWidth = useRef(config.sidebarWidth ?? 300); const lastUpdateTime = useRef(Date.now()); const handleMouseMove = useRef((e: MouseEvent) => { @@ -39,11 +43,11 @@ function useDragSideBar() { lastUpdateTime.current = Date.now(); const d = e.clientX - startX.current; const nextWidth = limit(startDragWidth.current + d); - chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth)); + config.update((config) => (config.sidebarWidth = nextWidth)); }); const handleMouseUp = useRef(() => { - startDragWidth.current = chatStore.config.sidebarWidth ?? 300; + startDragWidth.current = config.sidebarWidth ?? 300; window.removeEventListener("mousemove", handleMouseMove.current); window.removeEventListener("mouseup", handleMouseUp.current); }); @@ -56,15 +60,15 @@ function useDragSideBar() { }; const isMobileScreen = useMobileScreen(); const shouldNarrow = - !isMobileScreen && chatStore.config.sidebarWidth < MIN_SIDEBAR_WIDTH; + !isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH; useEffect(() => { const barWidth = shouldNarrow ? NARROW_SIDEBAR_WIDTH - : limit(chatStore.config.sidebarWidth ?? 300); + : limit(config.sidebarWidth ?? 300); const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`; document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); - }, [chatStore.config.sidebarWidth, isMobileScreen, shouldNarrow]); + }, [config.sidebarWidth, isMobileScreen, shouldNarrow]); return { onDragMouseDown, diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 1c198195e..777cea59b 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; const cn = { WIP: "该功能仍在开发中……", diff --git a/app/locales/de.ts b/app/locales/de.ts index e71abfaf7..42a4c8f68 100644 --- a/app/locales/de.ts +++ b/app/locales/de.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; import type { LocaleType } from "./index"; const de: LocaleType = { diff --git a/app/locales/en.ts b/app/locales/en.ts index 20e569606..f7af4bfb3 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; import type { LocaleType } from "./index"; const en: LocaleType = { diff --git a/app/locales/es.ts b/app/locales/es.ts index e2a9eb211..efecf113b 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; import type { LocaleType } from "./index"; const es: LocaleType = { diff --git a/app/locales/it.ts b/app/locales/it.ts index f0453b5c3..b519ef453 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; import type { LocaleType } from "./index"; const it: LocaleType = { diff --git a/app/locales/jp.ts b/app/locales/jp.ts index 2818820b5..1c8d66d90 100644 --- a/app/locales/jp.ts +++ b/app/locales/jp.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; const jp = { WIP: "この機能は開発中です……", diff --git a/app/locales/tr.ts b/app/locales/tr.ts index 04a846245..86f1f417c 100644 --- a/app/locales/tr.ts +++ b/app/locales/tr.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; import type { LocaleType } from "./index"; const tr: LocaleType = { diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 44c07898d..20e41f47a 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -1,4 +1,4 @@ -import { SubmitKey } from "../store/app"; +import { SubmitKey } from "../store/config"; import type { LocaleType } from "./index"; const tw: LocaleType = { diff --git a/app/requests.ts b/app/requests.ts index ce72bb7cf..0e7570904 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -4,6 +4,7 @@ import { ModelConfig, ModelType, useAccessStore, + useAppConfig, useChatStore, } from "./store"; import { showToast } from "./components/ui-lib"; @@ -27,7 +28,7 @@ const makeRequestParam = ( sendMessages = sendMessages.filter((m) => m.role !== "assistant"); } - const modelConfig = { ...useChatStore.getState().config.modelConfig }; + const modelConfig = { ...useAppConfig.getState().modelConfig }; // @yidadaa: wont send max_tokens, because it is nonsense for Muggles // @ts-expect-error diff --git a/app/store/app.ts b/app/store/app.ts index 89995e0bb..2294130ad 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -11,6 +11,7 @@ import { isMobileScreen, trimTopic } from "../utils"; import Locale from "../locales"; import { showToast } from "../components/ui-lib"; +import { ModelType, useAppConfig } from "./config"; export type Message = ChatCompletionResponseMessage & { date: string; @@ -30,133 +31,8 @@ export function createMessage(override: Partial): Message { }; } -export enum SubmitKey { - Enter = "Enter", - CtrlEnter = "Ctrl + Enter", - ShiftEnter = "Shift + Enter", - AltEnter = "Alt + Enter", - MetaEnter = "Meta + Enter", -} - -export enum Theme { - Auto = "auto", - Dark = "dark", - Light = "light", -} - -export interface ChatConfig { - historyMessageCount: number; // -1 means all - compressMessageLengthThreshold: number; - sendBotMessages: boolean; // send bot's message or not - submitKey: SubmitKey; - avatar: string; - fontSize: number; - theme: Theme; - tightBorder: boolean; - sendPreviewBubble: boolean; - sidebarWidth: number; - - disablePromptHint: boolean; - - modelConfig: { - model: ModelType; - temperature: number; - max_tokens: number; - presence_penalty: number; - }; -} - -export type ModelConfig = ChatConfig["modelConfig"]; - export const ROLES: Message["role"][] = ["system", "user", "assistant"]; -const ENABLE_GPT4 = true; - -export const ALL_MODELS = [ - { - name: "gpt-4", - available: ENABLE_GPT4, - }, - { - name: "gpt-4-0314", - available: ENABLE_GPT4, - }, - { - name: "gpt-4-32k", - available: ENABLE_GPT4, - }, - { - name: "gpt-4-32k-0314", - available: ENABLE_GPT4, - }, - { - name: "gpt-3.5-turbo", - available: true, - }, - { - name: "gpt-3.5-turbo-0301", - available: true, - }, -] as const; - -export type ModelType = (typeof ALL_MODELS)[number]["name"]; - -export function limitNumber( - x: number, - min: number, - max: number, - defaultValue: number, -) { - if (typeof x !== "number" || isNaN(x)) { - return defaultValue; - } - - return Math.min(max, Math.max(min, x)); -} - -export function limitModel(name: string) { - return ALL_MODELS.some((m) => m.name === name && m.available) - ? name - : ALL_MODELS[4].name; -} - -export const ModalConfigValidator = { - model(x: string) { - return limitModel(x) as ModelType; - }, - max_tokens(x: number) { - return limitNumber(x, 0, 32000, 2000); - }, - presence_penalty(x: number) { - return limitNumber(x, -2, 2, 0); - }, - temperature(x: number) { - return limitNumber(x, 0, 2, 1); - }, -}; - -const DEFAULT_CONFIG: ChatConfig = { - historyMessageCount: 4, - compressMessageLengthThreshold: 1000, - sendBotMessages: true as boolean, - submitKey: SubmitKey.CtrlEnter as SubmitKey, - avatar: "1f603", - fontSize: 14, - theme: Theme.Auto as Theme, - tightBorder: false, - sendPreviewBubble: true, - sidebarWidth: 300, - - disablePromptHint: false, - - modelConfig: { - model: "gpt-3.5-turbo", - temperature: 1, - max_tokens: 2000, - presence_penalty: 0, - }, -}; - export interface ChatStat { tokenCount: number; wordCount: number; @@ -202,7 +78,6 @@ function createEmptySession(): ChatSession { } interface ChatStore { - config: ChatConfig; sessions: ChatSession[]; currentSessionIndex: number; clearSessions: () => void; @@ -226,9 +101,6 @@ interface ChatStore { getMessagesWithMemory: () => Message[]; getMemoryPrompt: () => Message; - getConfig: () => ChatConfig; - resetConfig: () => void; - updateConfig: (updater: (config: ChatConfig) => void) => void; clearAllData: () => void; } @@ -243,9 +115,6 @@ export const useChatStore = create()( (set, get) => ({ sessions: [createEmptySession()], currentSessionIndex: 0, - config: { - ...DEFAULT_CONFIG, - }, clearSessions() { set(() => ({ @@ -254,20 +123,6 @@ export const useChatStore = create()( })); }, - resetConfig() { - set(() => ({ config: { ...DEFAULT_CONFIG } })); - }, - - getConfig() { - return get().config; - }, - - updateConfig(updater) { - const config = get().config; - updater(config); - set(() => ({ config })); - }, - selectSession(index: number) { set({ currentSessionIndex: index, @@ -390,7 +245,7 @@ export const useChatStore = create()( role: "assistant", streaming: true, id: userMessage.id! + 1, - model: get().config.modelConfig.model, + model: useAppConfig.getState().modelConfig.model, }); // get recent messages @@ -443,8 +298,8 @@ export const useChatStore = create()( controller, ); }, - filterBot: !get().config.sendBotMessages, - modelConfig: get().config.modelConfig, + filterBot: !useAppConfig.getState().sendBotMessages, + modelConfig: useAppConfig.getState().modelConfig, }); }, @@ -460,7 +315,7 @@ export const useChatStore = create()( getMessagesWithMemory() { const session = get().currentSession(); - const config = get().config; + const config = useAppConfig.getState(); const messages = session.messages.filter((msg) => !msg.isError); const n = messages.length; @@ -545,14 +400,14 @@ export const useChatStore = create()( }); } - const config = get().config; + const config = useAppConfig.getState(); let toBeSummarizedMsgs = session.messages.slice( session.lastSummarizeIndex, ); const historyMsgLength = countMessages(toBeSummarizedMsgs); - if (historyMsgLength > get().config?.modelConfig?.max_tokens ?? 4000) { + if (historyMsgLength > config?.modelConfig?.max_tokens ?? 4000) { const n = toBeSummarizedMsgs.length; toBeSummarizedMsgs = toBeSummarizedMsgs.slice( Math.max(0, n - config.historyMessageCount), diff --git a/app/store/config.ts b/app/store/config.ts new file mode 100644 index 000000000..346f38da2 --- /dev/null +++ b/app/store/config.ts @@ -0,0 +1,135 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +export enum SubmitKey { + Enter = "Enter", + CtrlEnter = "Ctrl + Enter", + ShiftEnter = "Shift + Enter", + AltEnter = "Alt + Enter", + MetaEnter = "Meta + Enter", +} + +export enum Theme { + Auto = "auto", + Dark = "dark", + Light = "light", +} + +const DEFAULT_CONFIG = { + historyMessageCount: 4, + compressMessageLengthThreshold: 1000, + sendBotMessages: true as boolean, + submitKey: SubmitKey.CtrlEnter as SubmitKey, + avatar: "1f603", + fontSize: 14, + theme: Theme.Auto as Theme, + tightBorder: false, + sendPreviewBubble: true, + sidebarWidth: 300, + + disablePromptHint: false, + + modelConfig: { + model: "gpt-3.5-turbo" as ModelType, + temperature: 1, + max_tokens: 2000, + presence_penalty: 0, + }, +}; + +export type ChatConfig = typeof DEFAULT_CONFIG; + +export type ChatConfigStore = ChatConfig & { + reset: () => void; + update: (updater: (config: ChatConfig) => void) => void; +}; + +export type ModelConfig = ChatConfig["modelConfig"]; + +const ENABLE_GPT4 = true; + +export const ALL_MODELS = [ + { + name: "gpt-4", + available: ENABLE_GPT4, + }, + { + name: "gpt-4-0314", + available: ENABLE_GPT4, + }, + { + name: "gpt-4-32k", + available: ENABLE_GPT4, + }, + { + name: "gpt-4-32k-0314", + available: ENABLE_GPT4, + }, + { + name: "gpt-3.5-turbo", + available: true, + }, + { + name: "gpt-3.5-turbo-0301", + available: true, + }, +] as const; + +export type ModelType = (typeof ALL_MODELS)[number]["name"]; + +export function limitNumber( + x: number, + min: number, + max: number, + defaultValue: number, +) { + if (typeof x !== "number" || isNaN(x)) { + return defaultValue; + } + + return Math.min(max, Math.max(min, x)); +} + +export function limitModel(name: string) { + return ALL_MODELS.some((m) => m.name === name && m.available) + ? name + : ALL_MODELS[4].name; +} + +export const ModalConfigValidator = { + model(x: string) { + return limitModel(x) as ModelType; + }, + max_tokens(x: number) { + return limitNumber(x, 0, 32000, 2000); + }, + presence_penalty(x: number) { + return limitNumber(x, -2, 2, 0); + }, + temperature(x: number) { + return limitNumber(x, 0, 2, 1); + }, +}; + +const CONFIG_KEY = "app-config"; + +export const useAppConfig = create()( + persist( + (set, get) => ({ + ...DEFAULT_CONFIG, + + reset() { + set(() => ({ ...DEFAULT_CONFIG })); + }, + + update(updater) { + const config = { ...get() }; + updater(config); + set(() => config); + }, + }), + { + name: CONFIG_KEY, + }, + ), +); diff --git a/app/store/index.ts b/app/store/index.ts index 3bdb58ca2..7b7bbd04d 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -1,3 +1,4 @@ export * from "./app"; export * from "./update"; export * from "./access"; +export * from "./config"; From 79f58f5c6ad61e321c24c039e8e17607bd8d0397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B2=E9=9C=A7?= <123147018+yunwuu@users.noreply.github.com> Date: Sat, 22 Apr 2023 00:47:15 +0800 Subject: [PATCH 19/24] fix: typo --- app/components/chat.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/chat.tsx b/app/components/chat.tsx index c5cc54299..b80bf5a18 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -545,7 +545,7 @@ export function Chat() { } }; - const findLastUesrIndex = (messageId: number) => { + const findLastUserIndex = (messageId: number) => { // find last user input message and resend let lastUserMessageIndex: number | null = null; for (let i = 0; i < session.messages.length; i += 1) { @@ -568,14 +568,14 @@ export function Chat() { }; const onDelete = (botMessageId: number) => { - const userIndex = findLastUesrIndex(botMessageId); + const userIndex = findLastUserIndex(botMessageId); if (userIndex === null) return; deleteMessage(userIndex); }; const onResend = (botMessageId: number) => { // find last user input message and resend - const userIndex = findLastUesrIndex(botMessageId); + const userIndex = findLastUserIndex(botMessageId); if (userIndex === null) return; setIsLoading(true); From 5d2fb8791ccaacc6baf873a26c842fd1c47e9427 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Sun, 23 Apr 2023 00:07:16 +0900 Subject: [PATCH 20/24] Update README.md Github -> GitHub --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9ec7e68e..a6d1e3d33 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ We recommend that you follow the steps below to re-deploy: ### Enable Automatic Updates -After forking the project, due to the limitations imposed by Github, you need to manually enable Workflows and Upstream Sync Action on the Actions page of the forked project. Once enabled, automatic updates will be scheduled every hour: +After forking the project, due to the limitations imposed by GitHub, you need to manually enable Workflows and Upstream Sync Action on the Actions page of the forked project. Once enabled, automatic updates will be scheduled every hour: ![Automatic Updates](./docs/images/enable-actions.jpg) @@ -110,7 +110,7 @@ After forking the project, due to the limitations imposed by Github, you need to ### Manually Updating Code -If you want to update instantly, you can check out the [Github documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code. +If you want to update instantly, you can check out the [GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to learn how to synchronize a forked project with upstream code. You can star or watch this project or follow author to get release notifictions in time. From 1761289716aba1e6c6745d7e313dd837e463b4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B2=E9=9C=A7?= <123147018+yunwuu@users.noreply.github.com> Date: Sat, 22 Apr 2023 23:53:58 +0800 Subject: [PATCH 21/24] fix: typo --- scripts/fetch-prompts.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fetch-prompts.mjs b/scripts/fetch-prompts.mjs index 7f6818d3b..9dc72626f 100644 --- a/scripts/fetch-prompts.mjs +++ b/scripts/fetch-prompts.mjs @@ -30,7 +30,7 @@ async function fetchEN() { .slice(1) .map((v) => v.split('","').map((v) => v.replace('"', ""))); } catch (error) { - console.error("[Fetch] failed to fetch cn prompts", error); + console.error("[Fetch] failed to fetch en prompts", error); return []; } } From 818629e58bdc96b30e83320ed863a41d4118bf96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B2=E9=9C=A7?= <123147018+yunwuu@users.noreply.github.com> Date: Sun, 23 Apr 2023 00:17:00 +0800 Subject: [PATCH 22/24] chore: add timeout to prompt download request --- scripts/fetch-prompts.mjs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/fetch-prompts.mjs b/scripts/fetch-prompts.mjs index 9dc72626f..689377ca7 100644 --- a/scripts/fetch-prompts.mjs +++ b/scripts/fetch-prompts.mjs @@ -10,10 +10,20 @@ const RAW_EN_URL = "f/awesome-chatgpt-prompts/main/prompts.csv"; const EN_URL = MIRRORF_FILE_URL + RAW_EN_URL; const FILE = "./public/prompts.json"; +const timeoutPromise = (timeout) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + reject(new Error('Request timeout')); + }, timeout); + }); +}; + async function fetchCN() { console.log("[Fetch] fetching cn prompts..."); try { - const raw = await (await fetch(CN_URL)).json(); + // const raw = await (await fetch(CN_URL)).json(); + const response = await Promise.race([fetch(CN_URL), timeoutPromise(5000)]); + const raw = await response.json(); return raw.map((v) => [v.act, v.prompt]); } catch (error) { console.error("[Fetch] failed to fetch cn prompts", error); @@ -24,7 +34,9 @@ async function fetchCN() { async function fetchEN() { console.log("[Fetch] fetching en prompts..."); try { - const raw = await (await fetch(EN_URL)).text(); + // const raw = await (await fetch(EN_URL)).text(); + const response = await Promise.race([fetch(EN_URL), timeoutPromise(5000)]); + const raw = response.text(); return raw .split("\n") .slice(1) From 725054c7d569f4d7b63def9b3bd3bb1b70cd3bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B2=E9=9C=A7?= <123147018+yunwuu@users.noreply.github.com> Date: Sun, 23 Apr 2023 10:51:34 +0800 Subject: [PATCH 23/24] fix: raw.split is not a function --- scripts/fetch-prompts.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/fetch-prompts.mjs b/scripts/fetch-prompts.mjs index 689377ca7..02b52a31d 100644 --- a/scripts/fetch-prompts.mjs +++ b/scripts/fetch-prompts.mjs @@ -36,7 +36,7 @@ async function fetchEN() { try { // const raw = await (await fetch(EN_URL)).text(); const response = await Promise.race([fetch(EN_URL), timeoutPromise(5000)]); - const raw = response.text(); + const raw = await response.text(); return raw .split("\n") .slice(1) From e5e2f6c2e1c293efcb0b41b254f4cd12ec374440 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Sun, 23 Apr 2023 00:57:55 +0800 Subject: [PATCH 24/24] Improve tw locale --- app/locales/tw.ts | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 20e41f47a..26791c77d 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -12,7 +12,7 @@ const tw: LocaleType = { Chat: { SubTitle: (count: number) => `您已經與 ChatGPT 進行了 ${count} 條對話`, Actions: { - ChatList: "查看消息列表", + ChatList: "查看訊息列表", CompressedHistory: "查看壓縮後的歷史 Prompt", Export: "匯出聊天紀錄", Copy: "複製", @@ -32,10 +32,10 @@ const tw: LocaleType = { Send: "發送", }, Export: { - Title: "匯出聊天記錄為 Markdown", + Title: "將聊天記錄匯出為 Markdown", Copy: "複製全部", Download: "下載檔案", - MessageFromYou: "來自你的訊息", + MessageFromYou: "來自您的訊息", MessageFromChatGPT: "來自 ChatGPT 的訊息", }, Memory: { @@ -43,8 +43,8 @@ const tw: LocaleType = { EmptyContent: "尚未記憶", Copy: "複製全部", Send: "發送記憶", - Reset: "重置對話", - ResetConfirm: "重置後將清空當前對話記錄以及歷史記憶,確認重置?", + Reset: "重設對話", + ResetConfirm: "重設後將清除目前對話記錄以及歷史記憶,確認重設?", }, Home: { NewChat: "新的對話", @@ -56,18 +56,18 @@ const tw: LocaleType = { Title: "設定", SubTitle: "設定選項", Actions: { - ClearAll: "清除所有數據", - ResetAll: "重置所有設定", + ClearAll: "清除所有資料", + ResetAll: "重設所有設定", Close: "關閉", ConfirmResetAll: { - Confirm: "Are you sure you want to reset all configurations?", + Confirm: "您確定要重設所有設定嗎?", }, ConfirmClearAll: { - Confirm: "Are you sure you want to reset all chat?", + Confirm: "您確定要清除所有聊天嗎?", }, }, Lang: { - Name: "Language", + Name: "語言", Options: { cn: "简体中文", en: "English", @@ -98,16 +98,16 @@ const tw: LocaleType = { SendPreviewBubble: "發送預覽氣泡", Prompt: { Disable: { - Title: "停用提示詞自動補全", - SubTitle: "在輸入框開頭輸入 / 即可觸發自動補全", + Title: "停用提示詞自動補齊", + SubTitle: "在輸入框開頭輸入 / 即可觸發自動補齊", }, List: "自定義提示詞列表", ListCount: (builtin: number, custom: number) => - `內置 ${builtin} 條,用戶定義 ${custom} 條`, + `內建 ${builtin} 條,用戶定義 ${custom} 條`, Edit: "編輯", Modal: { Title: "提示詞列表", - Add: "增加一條", + Add: "新增一條", Search: "搜尋提示詞", }, }, @@ -121,13 +121,13 @@ const tw: LocaleType = { }, Token: { Title: "API Key", - SubTitle: "使用自己的 Key 可規避授權訪問限制", + SubTitle: "使用自己的 Key 可規避授權存取限制", Placeholder: "OpenAI API Key", }, Usage: { Title: "帳戶餘額", SubTitle(used: any, total: any) { - return `本月已使用 $${used},订阅总额 $${total}`; + return `本月已使用 $${used},訂閱總額 $${total}`; }, IsChecking: "正在檢查…", Check: "重新檢查", @@ -135,17 +135,17 @@ const tw: LocaleType = { }, AccessCode: { Title: "授權碼", - SubTitle: "現在是未授權訪問狀態", + SubTitle: "目前是未授權存取狀態", Placeholder: "請輸入授權碼", }, Model: "模型 (model)", Temperature: { Title: "隨機性 (temperature)", - SubTitle: "值越大,回復越隨機", + SubTitle: "值越大,回應越隨機", }, MaxTokens: { - Title: "單次回復限制 (max_tokens)", - SubTitle: "單次交互所用的最大 Token 數", + Title: "單次回應限制 (max_tokens)", + SubTitle: "單次互動所用的最大 Token 數", }, PresencePenlty: { Title: "話題新穎度 (presence_penalty)", @@ -164,16 +164,16 @@ const tw: LocaleType = { Summarize: "Use the language used by the user (e.g. en-us for english conversation, zh-hant for chinese conversation, etc.) to summarise the conversation in at most 200 words. The summary will be used as prompt for you to continue the conversation in the future.", }, - ConfirmClearAll: "確認清除所有對話、設定數據?", + ConfirmClearAll: "確認清除所有對話、設定?", }, Copy: { Success: "已複製到剪貼簿中", Failed: "複製失敗,請賦予剪貼簿權限", }, Context: { - Toast: (x: any) => `已設置 ${x} 條前置上下文`, + Toast: (x: any) => `已設定 ${x} 條前置上下文`, Edit: "前置上下文和歷史記憶", - Add: "新增壹條", + Add: "新增一條", }, };