From dc3883ed1aa8bc4c7b25216f52774a4a860623e4 Mon Sep 17 00:00:00 2001 From: Yidadaa <yidadaa@qq.com> Date: Sun, 16 Apr 2023 18:07:43 +0800 Subject: [PATCH] feat: close #118 add stop all button --- app/components/chat.tsx | 16 ++++++++++++++-- app/icons/bottom.svg | 2 +- app/icons/pause.svg | 1 + app/requests.ts | 15 +++++++++++---- app/store/app.ts | 2 +- package.json | 3 ++- scripts/.gitignore | 1 + scripts/init-proxy.sh | 5 +++++ scripts/proxychains.template.conf | 12 ++++++++++++ 9 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 app/icons/pause.svg create mode 100644 scripts/.gitignore create mode 100644 scripts/init-proxy.sh create mode 100644 scripts/proxychains.template.conf diff --git a/app/components/chat.tsx b/app/components/chat.tsx index f7b67b293..9e4a2bd9a 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -19,6 +19,7 @@ import LightIcon from "../icons/light.svg"; import DarkIcon from "../icons/dark.svg"; import AutoIcon from "../icons/auto.svg"; import BottomIcon from "../icons/bottom.svg"; +import StopIcon from "../icons/pause.svg"; import { Message, @@ -38,7 +39,6 @@ import { isMobileScreen, selectOrCopy, autoGrowTextArea, - getCSSVar, } from "../utils"; import dynamic from "next/dynamic"; @@ -355,8 +355,8 @@ export function ChatActions(props: { }) { const chatStore = useChatStore(); + // switch themes const theme = chatStore.config.theme; - function nextTheme() { const themes = [Theme.Auto, Theme.Light, Theme.Dark]; const themeIndex = themes.indexOf(theme); @@ -365,8 +365,20 @@ export function ChatActions(props: { chatStore.updateConfig((config) => (config.theme = nextTheme)); } + // stop all responses + const couldStop = ControllerPool.hasPending(); + const stopAll = () => ControllerPool.stopAll(); + return ( <div className={chatStyle["chat-input-actions"]}> + {couldStop && ( + <div + className={`${chatStyle["chat-input-action"]} clickable`} + onClick={stopAll} + > + <StopIcon /> + </div> + )} {!props.hitBottom && ( <div className={`${chatStyle["chat-input-action"]} clickable`} diff --git a/app/icons/bottom.svg b/app/icons/bottom.svg index 06c663ab2..e2cfba2c7 100644 --- a/app/icons/bottom.svg +++ b/app/icons/bottom.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(8.002766666666666 2) rotate(0 0 4.649916666666667)" d="M0,9.3L0,0 " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 7.333333333333333) rotate(0 4 2)" d="M8,0L4,4L0,0 " /><path id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 14) rotate(0 4 0)" d="M8,0L0,0 " /></g></g></svg> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 4) rotate(0 4 2)" d="M8,0L4,4L0,0 " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 8) rotate(0 4 2)" d="M8,0L4,4L0,0 " /></g></g></svg> diff --git a/app/icons/pause.svg b/app/icons/pause.svg new file mode 100644 index 000000000..382f7a939 --- /dev/null +++ b/app/icons/pause.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0) rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(1.3333333333333333 1.3333333333333333) rotate(0 6.666666666666666 6.666666666666666)" d="M13.33,6.67C13.33,2.98 10.35,0 6.67,0C2.98,0 0,2.98 0,6.67C0,10.35 2.98,13.33 6.67,13.33C10.35,13.33 13.33,10.35 13.33,6.67Z " /><path id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.333333333333333 6) rotate(0 0 2)" d="M0,0L0,4 " /><path id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(9.666666666666666 6) rotate(0 0 2)" d="M0,0L0,4 " /></g></g></svg> diff --git a/app/requests.ts b/app/requests.ts index c48ef2817..db91f6fc3 100644 --- a/app/requests.ts +++ b/app/requests.ts @@ -2,7 +2,7 @@ import type { ChatRequest, ChatResponse } from "./api/openai/typing"; import { Message, ModelConfig, useAccessStore, useChatStore } from "./store"; import { showToast } from "./components/ui-lib"; -const TIME_OUT_MS = 30000; +const TIME_OUT_MS = 60000; const makeRequestParam = ( messages: Message[], @@ -167,15 +167,14 @@ export async function requestChatStream( options?.onController?.(controller); while (true) { - // handle time out, will stop if no response in 10 secs const resTimeoutId = setTimeout(() => finish(), TIME_OUT_MS); const content = await reader?.read(); clearTimeout(resTimeoutId); - + if (!content || !content.value) { break; } - + const text = decoder.decode(content.value, { stream: true }); responseText += text; @@ -235,6 +234,14 @@ export const ControllerPool = { controller?.abort(); }, + stopAll() { + Object.values(this.controllers).forEach((v) => v.abort()); + }, + + hasPending() { + return Object.values(this.controllers).length > 0; + }, + 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 813668905..c4c1c3411 100644 --- a/app/store/app.ts +++ b/app/store/app.ts @@ -421,7 +421,7 @@ export const useChatStore = create<ChatStore>()( onError(error, statusCode) { if (statusCode === 401) { botMessage.content = Locale.Error.Unauthorized; - } else { + } else if (!error.message.includes("aborted")) { botMessage.content += "\n\n" + Locale.Store.Error; } botMessage.streaming = false; diff --git a/package.json b/package.json index 9fcb74e77..9a3e6d41c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "start": "next start", "lint": "next lint", "fetch": "node ./scripts/fetch-prompts.mjs", - "prepare": "husky install" + "prepare": "husky install", + "proxy-dev": "sh ./scripts/init-proxy.sh && proxychains -f ./scripts/proxychains.conf yarn dev" }, "dependencies": { "@hello-pangea/dnd": "^16.2.0", diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 000000000..80fe56c37 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +proxychains.conf diff --git a/scripts/init-proxy.sh b/scripts/init-proxy.sh new file mode 100644 index 000000000..acba064f4 --- /dev/null +++ b/scripts/init-proxy.sh @@ -0,0 +1,5 @@ +dir="$(dirname "$0")" +config=$dir/proxychains.conf +host_ip=$(grep nameserver /etc/resolv.conf | sed 's/nameserver //') +cp $dir/proxychains.template.conf $config +sed -i "\$s/.*/http $host_ip 7890/" $config diff --git a/scripts/proxychains.template.conf b/scripts/proxychains.template.conf new file mode 100644 index 000000000..e78b96a68 --- /dev/null +++ b/scripts/proxychains.template.conf @@ -0,0 +1,12 @@ +strict_chain +proxy_dns + +remote_dns_subnet 224 + +tcp_read_time_out 15000 +tcp_connect_time_out 8000 + +localnet 127.0.0.0/255.0.0.0 + +[ProxyList] +socks4 127.0.0.1 9050