forked from XiaoMo/ChatGPT-Next-Web
Merge pull request #1220 from Yidadaa/bugfix-0503
feat: #782 select prompt with arrow down / up
This commit is contained in:
commit
e0053d57f7
@ -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 />}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user