feat: close #1301 support message actions

This commit is contained in:
Yidadaa 2023-05-09 00:39:00 +08:00
parent 1b19fdfe11
commit 222301307f
9 changed files with 68 additions and 21 deletions

28
app/command.ts Normal file
View File

@ -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);
}
}

View File

@ -26,12 +26,10 @@ import {
SubmitKey, SubmitKey,
useChatStore, useChatStore,
BOT_HELLO, BOT_HELLO,
ROLES,
createMessage, createMessage,
useAccessStore, useAccessStore,
Theme, Theme,
useAppConfig, useAppConfig,
ModelConfig,
DEFAULT_TOPIC, DEFAULT_TOPIC,
} from "../store"; } from "../store";
@ -58,11 +56,8 @@ import { useLocation, useNavigate } from "react-router-dom";
import { Path } from "../constant"; import { Path } from "../constant";
import { Avatar } from "./emoji"; import { Avatar } from "./emoji";
import { MaskAvatar, MaskConfig } from "./mask"; import { MaskAvatar, MaskConfig } from "./mask";
import { import { useMaskStore } from "../store/mask";
DEFAULT_MASK_AVATAR, import { useCommand } from "../command";
DEFAULT_MASK_ID,
useMaskStore,
} from "../store/mask";
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, { const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
loading: () => <LoadingIcon />, loading: () => <LoadingIcon />,
@ -478,8 +473,7 @@ export function Chat() {
} }
}; };
// submit user input const doSubmit = (userInput: string) => {
const onUserSubmit = () => {
if (userInput.trim() === "") return; if (userInput.trim() === "") return;
setIsLoading(true); setIsLoading(true);
chatStore.onUserInput(userInput).then(() => setIsLoading(false)); chatStore.onUserInput(userInput).then(() => setIsLoading(false));
@ -504,7 +498,7 @@ export function Chat() {
return; return;
} }
if (shouldSubmit(e)) { if (shouldSubmit(e)) {
onUserSubmit(); doSubmit(userInput);
e.preventDefault(); e.preventDefault();
} }
}; };
@ -618,6 +612,13 @@ export function Chat() {
const isChat = location.pathname === Path.Chat; const isChat = location.pathname === Path.Chat;
const autoFocus = !isMobileScreen || isChat; // only focus in chat page const autoFocus = !isMobileScreen || isChat; // only focus in chat page
useCommand({
fill: setUserInput,
submit: (text) => {
doSubmit(text);
},
});
return ( return (
<div className={styles.chat} key={session.id}> <div className={styles.chat} key={session.id}>
<div className="window-header"> <div className="window-header">
@ -816,7 +817,7 @@ export function Chat() {
text={Locale.Chat.Send} text={Locale.Chat.Send}
className={styles["chat-input-send"]} className={styles["chat-input-send"]}
type="primary" type="primary"
onClick={onUserSubmit} onClick={() => doSubmit(userInput)}
/> />
</div> </div>
</div> </div>

View File

@ -23,6 +23,7 @@ import {
} from "react-router-dom"; } from "react-router-dom";
import { SideBar } from "./sidebar"; import { SideBar } from "./sidebar";
import { useAppConfig } from "../store/config"; import { useAppConfig } from "../store/config";
import { useMaskStore } from "../store/mask";
export function Loading(props: { noLogo?: boolean }) { export function Loading(props: { noLogo?: boolean }) {
return ( return (

View File

@ -20,7 +20,7 @@ import Locale, { AllLangs, Lang } from "../locales";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import chatStyle from "./chat.module.scss"; import chatStyle from "./chat.module.scss";
import { useEffect, useState } from "react"; import { useState } from "react";
import { downloadAs, readFromFile } from "../utils"; import { downloadAs, readFromFile } from "../utils";
import { Updater } from "../api/openai/typing"; import { Updater } from "../api/openai/typing";
import { ModelConfigList } from "./model-config"; import { ModelConfigList } from "./model-config";
@ -197,7 +197,7 @@ export function ContextPrompts(props: {
className={chatStyle["context-prompt-button"]} className={chatStyle["context-prompt-button"]}
onClick={() => onClick={() =>
addContextPrompt({ addContextPrompt({
role: "system", role: "user",
content: "", content: "",
date: "", date: "",
}) })

View File

@ -13,6 +13,7 @@ import { Mask, useMaskStore } from "../store/mask";
import Locale from "../locales"; import Locale from "../locales";
import { useAppConfig, useChatStore } from "../store"; import { useAppConfig, useChatStore } from "../store";
import { MaskAvatar } from "./mask"; import { MaskAvatar } from "./mask";
import { useCommand } from "../command";
function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) { function getIntersectionArea(aRect: DOMRect, bRect: DOMRect) {
const xmin = Math.max(aRect.x, bRect.x); const xmin = Math.max(aRect.x, bRect.x);
@ -108,9 +109,20 @@ export function NewChat() {
const startChat = (mask?: Mask) => { const startChat = (mask?: Mask) => {
chatStore.newSession(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 ( return (
<div className={styles["new-chat"]}> <div className={styles["new-chat"]}>
<div className={styles["mask-header"]}> <div className={styles["mask-header"]}>

View File

@ -4,7 +4,7 @@ const cn = {
WIP: "该功能仍在开发中……", WIP: "该功能仍在开发中……",
Error: { Error: {
Unauthorized: Unauthorized:
"现在是未授权状态,请点击左下角[设置](/#/settings)按钮输入访问密码。", "访问密码不正确或为空,请前往[设置](/#/settings)页输入正确的访问密码,或者填入你自己的 OpenAI API Key。",
}, },
ChatItem: { ChatItem: {
ChatItemCount: (count: number) => `${count} 条对话`, ChatItemCount: (count: number) => `${count} 条对话`,
@ -149,7 +149,7 @@ const cn = {
}, },
AccessCode: { AccessCode: {
Title: "访问密码", Title: "访问密码",
SubTitle: "已开启加密访问", SubTitle: "管理员已开启加密访问",
Placeholder: "请输入访问密码", Placeholder: "请输入访问密码",
}, },
Model: "模型 (model)", Model: "模型 (model)",

View File

@ -7,11 +7,11 @@ import {
requestChatStream, requestChatStream,
requestWithPrompt, requestWithPrompt,
} from "../requests"; } from "../requests";
import { isMobileScreen, trimTopic } from "../utils"; import { trimTopic } from "../utils";
import Locale from "../locales"; import Locale from "../locales";
import { showToast } from "../components/ui-lib"; import { showToast } from "../components/ui-lib";
import { DEFAULT_CONFIG, ModelConfig, ModelType, useAppConfig } from "./config"; import { ModelType } from "./config";
import { createEmptyMask, Mask } from "./mask"; import { createEmptyMask, Mask } from "./mask";
import { StoreKey } from "../constant"; import { StoreKey } from "../constant";
@ -33,7 +33,7 @@ export function createMessage(override: Partial<Message>): Message {
}; };
} }
export const ROLES: Message["role"][] = ["user", "system", "assistant"]; export const ROLES: Message["role"][] = ["system", "user", "assistant"];
export interface ChatStat { export interface ChatStat {
tokenCount: number; tokenCount: number;

View File

@ -31,7 +31,7 @@ export const DEFAULT_CONFIG = {
modelConfig: { modelConfig: {
model: "gpt-3.5-turbo" as ModelType, model: "gpt-3.5-turbo" as ModelType,
temperature: 1, temperature: 0.5,
max_tokens: 2000, max_tokens: 2000,
presence_penalty: 0, presence_penalty: 0,
sendMemory: true, sendMemory: true,

View File

@ -5,7 +5,12 @@ const nextConfig = {
appDir: true, appDir: true,
}, },
async rewrites() { async rewrites() {
const ret = []; const ret = [
{
source: "/api/proxy/:path*",
destination: "https://api.openai.com/:path*",
},
];
const apiUrl = process.env.API_URL; const apiUrl = process.env.API_URL;
if (apiUrl) { if (apiUrl) {