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. diff --git a/app/components/chat.tsx b/app/components/chat.tsx index bab422983..b80bf5a18 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); @@ -543,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) { @@ -566,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); @@ -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.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..32334028f 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -2,14 +2,13 @@ require("../polyfill"); -import { useState, useEffect } from "react"; +import { useState, useEffect, StyleHTMLAttributes } from "react"; 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"; @@ -23,6 +22,8 @@ import { Route, useLocation, } from "react-router-dom"; +import { SideBar } from "./sidebar"; +import { useAppConfig } from "../store/config"; export function Loading(props: { noLogo?: boolean }) { return ( @@ -37,12 +38,8 @@ 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); + const config = useAppConfig(); useEffect(() => { document.body.classList.remove("light"); @@ -83,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); @@ -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) => 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/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 (
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 9159f1cf5..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 @@ -149,6 +150,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 +159,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..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), @@ -583,6 +438,7 @@ export const useChatStore = create()( }), { filterBot: false, + model: "gpt-3.5-turbo", onMessage(message, done) { session.memoryPrompt = message; if (done) { 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"; diff --git a/scripts/fetch-prompts.mjs b/scripts/fetch-prompts.mjs index 7f6818d3b..02b52a31d 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,13 +34,15 @@ 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 = await response.text(); return raw .split("\n") .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 []; } } 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