diff --git a/src/renderer/src/windows/mini/home/HomeWindow.tsx b/src/renderer/src/windows/mini/home/HomeWindow.tsx index 59881fd803..5e806a5d48 100644 --- a/src/renderer/src/windows/mini/home/HomeWindow.tsx +++ b/src/renderer/src/windows/mini/home/HomeWindow.tsx @@ -7,7 +7,7 @@ import { uuid } from '@renderer/utils' import { Divider } from 'antd' import dayjs from 'dayjs' import { isEmpty } from 'lodash' -import React, { FC, useCallback, useEffect, useState } from 'react' +import React, { FC, useCallback, useEffect, useRef, useState } from 'react' import { useHotkeys } from 'react-hotkeys-hook' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -15,7 +15,7 @@ import styled from 'styled-components' import ChatWindow from '../chat/ChatWindow' import TranslateWindow from '../translate/TranslateWindow' import ClipboardPreview from './components/ClipboardPreview' -import FeatureMenus from './components/FeatureMenus' +import FeatureMenus, { FeatureMenusRef } from './components/FeatureMenus' import Footer from './components/Footer' import InputBar from './components/InputBar' @@ -31,12 +31,14 @@ const HomeWindow: FC = () => { const { defaultModel: model } = useDefaultModel() const { language } = useSettings() const { t } = useTranslation() + const inputBarRef = useRef(null) + const featureMenusRef = useRef(null) const referenceText = selectedText || clipboardText || text const content = isFirstMessage ? (referenceText === text ? text : `${referenceText}\n\n${text}`).trim() : text.trim() - const onReadClipboard = useCallback(async () => { + const readClipboard = useCallback(async () => { const text = await navigator.clipboard.readText().catch(() => null) if (text && text !== lastClipboardText) { setLastClipboardText(text) @@ -44,9 +46,24 @@ const HomeWindow: FC = () => { } }, [lastClipboardText]) + const focusInput = () => { + if (inputBarRef.current) { + const input = inputBarRef.current.querySelector('input') + if (input) { + input.focus() + } + } + } + + const onWindowShow = useCallback(async () => { + featureMenusRef.current?.resetSelectedIndex() + readClipboard().then() + focusInput() + }, [readClipboard]) + useEffect(() => { - onReadClipboard() - }, [onReadClipboard]) + readClipboard() + }, [readClipboard]) useEffect(() => { i18n.changeLanguage(language || navigator.language || 'en-US') @@ -55,31 +72,65 @@ const HomeWindow: FC = () => { const onCloseWindow = () => window.api.miniWindow.hide() const handleKeyDown = (e: React.KeyboardEvent) => { - const isEnterPressed = e.code === 'Enter' - const isBackspacePressed = e.code === 'Backspace' - - if (e.code === 'Escape') { - setText('') - setRoute('home') - route === 'home' && onCloseWindow() + // 使用非直接输入法时(例如中文、日文输入法),存在输入法键入过程 + // 键入过程不应有任何响应 + // 例子,中文输入法候选词过程使用`Enter`直接上屏字母,日文输入法候选词过程使用`Enter`输入假名 + // 输入法可以`Esc`终止候选词过程 + // 这两个例子的`Enter`和`Esc`快捷助手都不应该响应 + if (e.key === 'Process') { return } - if (isEnterPressed) { - e.preventDefault() - if (content) { - setRoute('chat') - onSendMessage() - setTimeout(() => setText(''), 100) - } - } - - if (isBackspacePressed) { - textChange(() => { - if (text.length === 0) { - clearClipboard() + switch (e.code) { + case 'Enter': + { + e.preventDefault() + if (content) { + if (route === 'home') { + featureMenusRef.current?.useFeature() + setText('') + } else { + // 目前文本框只在'chat'时可以继续输入,这里相当于 route === 'chat' + setRoute('chat') + onSendMessage().then() + focusInput() + setTimeout(() => setText(''), 100) + } + } } - }) + break + case 'Backspace': + { + textChange(() => { + if (text.length === 0) { + clearClipboard() + } + }) + } + break + case 'ArrowUp': + { + if (route === 'home') { + e.preventDefault() + featureMenusRef.current?.prevFeature() + } + } + break + case 'ArrowDown': + { + if (route === 'home') { + e.preventDefault() + featureMenusRef.current?.nextFeature() + } + } + break + case 'Escape': + { + setText('') + setRoute('home') + route === 'home' && onCloseWindow() + } + break } } @@ -116,6 +167,7 @@ const HomeWindow: FC = () => { setSelectedText('') } + // If the input is focused, the `Esc` callback will not be triggered here. useHotkeys('esc', () => { if (route === 'home') { onCloseWindow() @@ -126,7 +178,7 @@ const HomeWindow: FC = () => { }) useEffect(() => { - window.electron.ipcRenderer.on('show-mini-window', onReadClipboard) + window.electron.ipcRenderer.on('show-mini-window', onWindowShow) window.electron.ipcRenderer.on('selection-action', (_, { action, selectedText }) => { selectedText && setSelectedText(selectedText) action && setRoute(action) @@ -137,7 +189,7 @@ const HomeWindow: FC = () => { window.electron.ipcRenderer.removeAllListeners('show-mini-window') window.electron.ipcRenderer.removeAllListeners('selection-action') } - }, [onReadClipboard, onSendMessage, setRoute]) + }, [onWindowShow, onSendMessage, setRoute]) // 当路由为home时,初始化isFirstMessage为true useEffect(() => { @@ -158,6 +210,7 @@ const HomeWindow: FC = () => { placeholder={t('miniwindow.input.placeholder.empty', { model: model.name })} handleKeyDown={handleKeyDown} handleChange={handleChange} + ref={inputBarRef} /> @@ -197,11 +250,12 @@ const HomeWindow: FC = () => { } handleKeyDown={handleKeyDown} handleChange={handleChange} + ref={inputBarRef} />
- +