mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	Merge pull request #1220 from Yidadaa/bugfix-0503
feat: #782 select prompt with arrow down / up
This commit is contained in:
		@@ -54,7 +54,7 @@ import styles from "./home.module.scss";
 | 
			
		||||
import chatStyle from "./chat.module.scss";
 | 
			
		||||
 | 
			
		||||
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 { Avatar } from "./emoji";
 | 
			
		||||
import { MaskAvatar, MaskConfig } from "./mask";
 | 
			
		||||
@@ -224,15 +224,63 @@ export function PromptHints(props: {
 | 
			
		||||
  prompts: Prompt[];
 | 
			
		||||
  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 (
 | 
			
		||||
    <div className={styles["prompt-hints"]}>
 | 
			
		||||
      {props.prompts.map((prompt, i) => (
 | 
			
		||||
        <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()}
 | 
			
		||||
          onClick={() => props.onPromptSelect(prompt)}
 | 
			
		||||
          onMouseEnter={() => setSelectIndex(i)}
 | 
			
		||||
        >
 | 
			
		||||
          <div className={styles["hint-title"]}>{prompt.title}</div>
 | 
			
		||||
          <div className={styles["hint-content"]}>{prompt.content}</div>
 | 
			
		||||
@@ -370,7 +418,7 @@ export function Chat() {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const onChatBodyScroll = (e: HTMLElement) => {
 | 
			
		||||
    const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20;
 | 
			
		||||
    const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 100;
 | 
			
		||||
    setHitBottom(isTouchBottom);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@@ -397,7 +445,7 @@ export function Chat() {
 | 
			
		||||
    () => {
 | 
			
		||||
      const rows = inputRef.current ? autoGrowTextArea(inputRef.current) : 1;
 | 
			
		||||
      const inputRows = Math.min(
 | 
			
		||||
        5,
 | 
			
		||||
        20,
 | 
			
		||||
        Math.max(2 + Number(!isMobileScreen), rows),
 | 
			
		||||
      );
 | 
			
		||||
      setInputRows(inputRows);
 | 
			
		||||
@@ -566,12 +614,9 @@ export function Chat() {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Auto focus
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (isMobileScreen) return;
 | 
			
		||||
    inputRef.current?.focus();
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, []);
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
  const isChat = location.pathname === Path.Chat;
 | 
			
		||||
  const autoFocus = !isMobileScreen || isChat; // only focus in chat page
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.chat} key={session.id}>
 | 
			
		||||
@@ -762,16 +807,9 @@ export function Chat() {
 | 
			
		||||
            value={userInput}
 | 
			
		||||
            onKeyDown={onInputKeyDown}
 | 
			
		||||
            onFocus={() => setAutoScroll(true)}
 | 
			
		||||
            onBlur={() => {
 | 
			
		||||
              setTimeout(() => {
 | 
			
		||||
                if (document.activeElement !== inputRef.current) {
 | 
			
		||||
                  setAutoScroll(false);
 | 
			
		||||
                  setPromptHints([]);
 | 
			
		||||
                }
 | 
			
		||||
              }, 100);
 | 
			
		||||
            }}
 | 
			
		||||
            autoFocus
 | 
			
		||||
            onBlur={() => setAutoScroll(false)}
 | 
			
		||||
            rows={inputRows}
 | 
			
		||||
            autoFocus={autoFocus}
 | 
			
		||||
          />
 | 
			
		||||
          <IconButton
 | 
			
		||||
            icon={<SendWhiteIcon />}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ export const AllLangs = [
 | 
			
		||||
export type Lang = (typeof AllLangs)[number];
 | 
			
		||||
 | 
			
		||||
const LANG_KEY = "lang";
 | 
			
		||||
const DEFAULT_LANG = "en";
 | 
			
		||||
 | 
			
		||||
function getItem(key: string) {
 | 
			
		||||
  try {
 | 
			
		||||
@@ -41,7 +42,8 @@ function getLanguage() {
 | 
			
		||||
  try {
 | 
			
		||||
    return navigator.language.toLowerCase();
 | 
			
		||||
  } 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) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user