diff --git a/app/command.ts b/app/command.ts new file mode 100644 index 00000000000..40bad92b3a9 --- /dev/null +++ b/app/command.ts @@ -0,0 +1,28 @@ +import { useSearchParams } from "react-router-dom"; + +type Command = (param: string) => void; +interface Commands { + fill?: Command; + submit?: Command; + mask?: Command; +} + +export function useCommand(commands: Commands = {}) { + const [searchParams, setSearchParams] = useSearchParams(); + + if (commands === undefined) return; + + let shouldUpdate = false; + searchParams.forEach((param, name) => { + const commandName = name as keyof Commands; + if (typeof commands[commandName] === "function") { + commands[commandName]!(param); + searchParams.delete(name); + shouldUpdate = true; + } + }); + + if (shouldUpdate) { + setSearchParams(searchParams); + } +} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index ca51a06af28..8786877baba 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -26,12 +26,10 @@ import { SubmitKey, useChatStore, BOT_HELLO, - ROLES, createMessage, useAccessStore, Theme, useAppConfig, - ModelConfig, DEFAULT_TOPIC, } from "../store"; @@ -58,11 +56,8 @@ import { useLocation, useNavigate } from "react-router-dom"; import { Path } from "../constant"; import { Avatar } from "./emoji"; import { MaskAvatar, MaskConfig } from "./mask"; -import { - DEFAULT_MASK_AVATAR, - DEFAULT_MASK_ID, - useMaskStore, -} from "../store/mask"; +import { useMaskStore } from "../store/mask"; +import { useCommand } from "../command"; const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { loading: () => , @@ -478,8 +473,7 @@ export function Chat() { } }; - // submit user input - const onUserSubmit = () => { + const doSubmit = (userInput: string) => { if (userInput.trim() === "") return; setIsLoading(true); chatStore.onUserInput(userInput).then(() => setIsLoading(false)); @@ -504,7 +498,7 @@ export function Chat() { return; } if (shouldSubmit(e)) { - onUserSubmit(); + doSubmit(userInput); e.preventDefault(); } }; @@ -618,6 +612,13 @@ export function Chat() { const isChat = location.pathname === Path.Chat; const autoFocus = !isMobileScreen || isChat; // only focus in chat page + useCommand({ + fill: setUserInput, + submit: (text) => { + doSubmit(text); + }, + }); + return (
@@ -816,7 +817,7 @@ export function Chat() { text={Locale.Chat.Send} className={styles["chat-input-send"]} type="primary" - onClick={onUserSubmit} + onClick={() => doSubmit(userInput)} />
diff --git a/app/components/home.tsx b/app/components/home.tsx index a83a779829b..4c3d0a6464f 100644 --- a/app/components/home.tsx +++ b/app/components/home.tsx @@ -23,6 +23,7 @@ import { } from "react-router-dom"; import { SideBar } from "./sidebar"; import { useAppConfig } from "../store/config"; +import { useMaskStore } from "../store/mask"; export function Loading(props: { noLogo?: boolean }) { return ( diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index 9e71bb048c7..fb37fdc43fb 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -12,15 +12,21 @@ import mermaid from "mermaid"; import LoadingIcon from "../icons/three-dots.svg"; import React from "react"; -export function Mermaid(props: { code: string }) { +export function Mermaid(props: { code: string; onError: () => void }) { const ref = useRef(null); useEffect(() => { if (props.code && ref.current) { - mermaid.run({ - nodes: [ref.current], - }); + mermaid + .run({ + nodes: [ref.current], + }) + .catch((e) => { + props.onError(); + console.error("[Mermaid] ", e.message); + }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.code]); function viewSvgInNewWindow() { @@ -38,7 +44,7 @@ export function Mermaid(props: { code: string }) { return (
viewSvgInNewWindow()} > @@ -60,7 +66,7 @@ export function PreCode(props: { children: any }) { }, [props.children]); if (mermaidCode) { - return ; + return setMermaidCode("")} />; } return ( @@ -147,7 +153,7 @@ export function Markdown( } }; - checkInView(); + setTimeout(() => checkInView(), 1); return (
void; + remove: () => void; +}) { + const [focusingInput, setFocusingInput] = useState(false); + + return ( +
+ {!focusingInput && ( + + )} + setFocusingInput(true)} + onBlur={() => setFocusingInput(false)} + onInput={(e) => + props.update({ + ...props.prompt, + content: e.currentTarget.value as any, + }) + } + /> + {!focusingInput && ( + } + className={chatStyle["context-delete-button"]} + onClick={() => props.remove()} + bordered + /> + )} +
+ ); +} + export function ContextPrompts(props: { context: Message[]; updateContext: (updater: (context: Message[]) => void) => void; @@ -128,42 +181,12 @@ export function ContextPrompts(props: { <>
{context.map((c, i) => ( -
- - - updateContextPrompt(i, { - ...c, - content: e.currentTarget.value as any, - }) - } - /> - } - className={chatStyle["context-delete-button"]} - onClick={() => removeContextPrompt(i)} - bordered - /> -
+ updateContextPrompt(i, prompt)} + remove={() => removeContextPrompt(i)} + /> ))}
@@ -174,7 +197,7 @@ export function ContextPrompts(props: { className={chatStyle["context-prompt-button"]} onClick={() => addContextPrompt({ - role: "system", + role: "user", content: "", date: "", }) diff --git a/app/components/model-config.tsx b/app/components/model-config.tsx index 32c2f5c0d4f..fe9319e0fd9 100644 --- a/app/components/model-config.tsx +++ b/app/components/model-config.tsx @@ -1,4 +1,3 @@ -import styles from "./settings.module.scss"; import { ALL_MODELS, ModalConfigValidator, ModelConfig } from "../store"; import Locale from "../locales"; diff --git a/app/components/new-chat.tsx b/app/components/new-chat.tsx index 42612e0adc0..81858fb0218 100644 --- a/app/components/new-chat.tsx +++ b/app/components/new-chat.tsx @@ -13,6 +13,7 @@ import { Mask, useMaskStore } from "../store/mask"; import Locale from "../locales"; import { useAppConfig, useChatStore } from "../store"; import { MaskAvatar } from "./mask"; +import { useCommand } from "../command"; function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) { const xmin = Math.max(aRect.x, bRect.x); @@ -108,9 +109,20 @@ export function NewChat() { const startChat = (mask?: Mask) => { chatStore.newSession(mask); - navigate(Path.Chat); + setTimeout(() => navigate(Path.Chat), 1); }; + useCommand({ + mask: (id) => { + try { + const mask = maskStore.get(parseInt(id)); + startChat(mask ?? undefined); + } catch { + console.error("[New Chat] failed to create chat from mask id=", id); + } + }, + }); + return (
diff --git a/app/components/ui-lib.module.scss b/app/components/ui-lib.module.scss index 465fc0de4dc..ce512dab444 100644 --- a/app/components/ui-lib.module.scss +++ b/app/components/ui-lib.module.scss @@ -124,6 +124,18 @@ } } +@media screen and (max-width: 600px) { + .modal-container { + width: 100vw; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + .modal-content { + max-height: 50vh; + } + } +} + .show { opacity: 1; transition: all ease 0.3s; @@ -191,13 +203,3 @@ resize: none; min-width: 50px; } - -@media only screen and (max-width: 600px) { - .modal-container { - width: 90vw; - - .modal-content { - max-height: 50vh; - } - } -} diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 8bec7c40eef..112b3b5cfc0 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -4,7 +4,7 @@ const cn = { WIP: "该功能仍在开发中……", Error: { Unauthorized: - "现在是未授权状态,请点击左下角[设置](/#/settings)按钮输入访问密码。", + "访问密码不正确或为空,请前往[设置](/#/settings)页输入正确的访问密码,或者填入你自己的 OpenAI API Key。", }, ChatItem: { ChatItemCount: (count: number) => `${count} 条对话`, @@ -149,7 +149,7 @@ const cn = { }, AccessCode: { Title: "访问密码", - SubTitle: "已开启加密访问", + SubTitle: "管理员已开启加密访问", Placeholder: "请输入访问密码", }, Model: "模型 (model)", diff --git a/app/store/chat.ts b/app/store/chat.ts index 5abd8129872..c938d787991 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -7,11 +7,11 @@ import { requestChatStream, requestWithPrompt, } from "../requests"; -import { isMobileScreen, trimTopic } from "../utils"; +import { trimTopic } from "../utils"; import Locale from "../locales"; import { showToast } from "../components/ui-lib"; -import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config"; +import { ModelType } from "./config"; import { createEmptyMask, Mask } from "./mask"; import { StoreKey } from "../constant"; diff --git a/app/store/config.ts b/app/store/config.ts index da77c7b3b84..926c296f4c9 100644 --- a/app/store/config.ts +++ b/app/store/config.ts @@ -31,7 +31,7 @@ export const DEFAULT_CONFIG = { modelConfig: { model: "gpt-3.5-turbo" as ModelType, - temperature: 1, + temperature: 0.5, max_tokens: 2000, presence_penalty: 0, sendMemory: true, diff --git a/app/styles/globals.scss b/app/styles/globals.scss index f849516a05e..1ae908be591 100644 --- a/app/styles/globals.scss +++ b/app/styles/globals.scss @@ -248,6 +248,10 @@ div.math { display: flex; align-items: center; justify-content: center; + + @media screen and (max-width: 600px) { + align-items: flex-end; + } } .link { diff --git a/next.config.mjs b/next.config.mjs index 3f7c2fb6bc9..c62f8840904 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,7 +5,12 @@ const nextConfig = { appDir: true, }, async rewrites() { - const ret = []; + const ret = [ + { + source: "/api/proxy/:path*", + destination: "https://api.openai.com/:path*", + }, + ]; const apiUrl = process.env.API_URL; if (apiUrl) {