Merge pull request #1220 from Yidadaa/bugfix-0503

feat: #782 select prompt with arrow down / up
This commit is contained in:
Yifei Zhang 2023-05-03 16:26:27 +08:00 committed by GitHub
commit e0053d57f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 22 deletions

View File

@ -54,7 +54,7 @@ import styles from "./home.module.scss";
import chatStyle from "./chat.module.scss"; import chatStyle from "./chat.module.scss";
import { ListItem, Modal, showModal } from "./ui-lib"; import { ListItem, Modal, showModal } from "./ui-lib";
import { useNavigate } from "react-router-dom"; 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";
@ -224,15 +224,63 @@ export function PromptHints(props: {
prompts: Prompt[]; prompts: Prompt[];
onPromptSelect: (prompt: Prompt) => void; onPromptSelect: (prompt: Prompt) => void;
}) { }) {
if (props.prompts.length === 0) return null; const noPrompts = props.prompts.length === 0;
const [selectIndex, setSelectIndex] = useState(0);
const selectedRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setSelectIndex(0);
}, [props.prompts.length]);
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (noPrompts) return;
// arrow up / down to select prompt
const changeIndex = (delta: number) => {
e.stopPropagation();
e.preventDefault();
const nextIndex = Math.max(
0,
Math.min(props.prompts.length - 1, selectIndex + delta),
);
setSelectIndex(nextIndex);
selectedRef.current?.scrollIntoView({
block: "center",
});
};
if (e.key === "ArrowUp") {
changeIndex(1);
} else if (e.key === "ArrowDown") {
changeIndex(-1);
} else if (e.key === "Enter") {
const selectedPrompt = props.prompts.at(selectIndex);
if (selectedPrompt) {
props.onPromptSelect(selectedPrompt);
}
}
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [noPrompts, selectIndex]);
if (noPrompts) return null;
return ( return (
<div className={styles["prompt-hints"]}> <div className={styles["prompt-hints"]}>
{props.prompts.map((prompt, i) => ( {props.prompts.map((prompt, i) => (
<div <div
className={styles["prompt-hint"]} ref={i === selectIndex ? selectedRef : null}
className={
styles["prompt-hint"] +
` ${i === selectIndex ? styles["prompt-hint-selected"] : ""}`
}
key={prompt.title + i.toString()} key={prompt.title + i.toString()}
onClick={() => props.onPromptSelect(prompt)} onClick={() => props.onPromptSelect(prompt)}
onMouseEnter={() => setSelectIndex(i)}
> >
<div className={styles["hint-title"]}>{prompt.title}</div> <div className={styles["hint-title"]}>{prompt.title}</div>
<div className={styles["hint-content"]}>{prompt.content}</div> <div className={styles["hint-content"]}>{prompt.content}</div>
@ -370,7 +418,7 @@ export function Chat() {
const navigate = useNavigate(); const navigate = useNavigate();
const onChatBodyScroll = (e: HTMLElement) => { const onChatBodyScroll = (e: HTMLElement) => {
const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20; const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 100;
setHitBottom(isTouchBottom); setHitBottom(isTouchBottom);
}; };
@ -397,7 +445,7 @@ export function Chat() {
() => { () => {
const rows = inputRef.current ? autoGrowTextArea(inputRef.current) : 1; const rows = inputRef.current ? autoGrowTextArea(inputRef.current) : 1;
const inputRows = Math.min( const inputRows = Math.min(
5, 20,
Math.max(2 + Number(!isMobileScreen), rows), Math.max(2 + Number(!isMobileScreen), rows),
); );
setInputRows(inputRows); setInputRows(inputRows);
@ -566,12 +614,9 @@ export function Chat() {
} }
}; };
// Auto focus const location = useLocation();
useEffect(() => { const isChat = location.pathname === Path.Chat;
if (isMobileScreen) return; const autoFocus = !isMobileScreen || isChat; // only focus in chat page
inputRef.current?.focus();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ( return (
<div className={styles.chat} key={session.id}> <div className={styles.chat} key={session.id}>
@ -762,16 +807,9 @@ export function Chat() {
value={userInput} value={userInput}
onKeyDown={onInputKeyDown} onKeyDown={onInputKeyDown}
onFocus={() => setAutoScroll(true)} onFocus={() => setAutoScroll(true)}
onBlur={() => { onBlur={() => setAutoScroll(false)}
setTimeout(() => {
if (document.activeElement !== inputRef.current) {
setAutoScroll(false);
setPromptHints([]);
}
}, 100);
}}
autoFocus
rows={inputRows} rows={inputRows}
autoFocus={autoFocus}
/> />
<IconButton <IconButton
icon={<SendWhiteIcon />} icon={<SendWhiteIcon />}

View File

@ -22,6 +22,7 @@ export const AllLangs = [
export type Lang = (typeof AllLangs)[number]; export type Lang = (typeof AllLangs)[number];
const LANG_KEY = "lang"; const LANG_KEY = "lang";
const DEFAULT_LANG = "en";
function getItem(key: string) { function getItem(key: string) {
try { try {
@ -41,7 +42,8 @@ function getLanguage() {
try { try {
return navigator.language.toLowerCase(); return navigator.language.toLowerCase();
} catch { } catch {
return "cn"; console.log("[Lang] failed to detect user lang.");
return DEFAULT_LANG;
} }
} }
@ -60,7 +62,7 @@ export function getLang(): Lang {
} }
} }
return "en"; return DEFAULT_LANG;
} }
export function changeLang(lang: Lang) { export function changeLang(lang: Lang) {