diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index 7176399cc36a..73542fc67f1f 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -646,3 +646,51 @@ bottom: 30px; } } + +.shortcut-key-container { + padding: 10px; + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.shortcut-key-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 16px; +} + +.shortcut-key-item { + display: flex; + justify-content: space-between; + align-items: center; + overflow: hidden; + padding: 10px; + background-color: var(--white); +} + +.shortcut-key-title { + font-size: 14px; + color: var(--black); +} + +.shortcut-key-keys { + display: flex; + gap: 8px; +} + +.shortcut-key { + display: flex; + align-items: center; + justify-content: center; + border: var(--border-in-light); + border-radius: 8px; + padding: 4px; + background-color: var(--gray); + min-width: 32px; +} + +.shortcut-key span { + font-size: 12px; + color: var(--black); +} \ No newline at end of file diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 77d351f309f8..300367151c77 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -42,6 +42,7 @@ import SizeIcon from "../icons/size.svg"; import QualityIcon from "../icons/hd.svg"; import StyleIcon from "../icons/palette.svg"; import PluginIcon from "../icons/plugin.svg"; +import ShortcutkeyIcon from "../icons/shortcutkey.svg"; import { ChatMessage, @@ -191,7 +192,7 @@ function PromptToast(props: { return (
- {props.showToast && ( + {props.showToast && context.length > 0 && (
void; hitBottom: boolean; uploading: boolean; + setShowShortcutKeyModal: React.Dispatch>; }) { const config = useAppConfig(); const navigate = useNavigate(); @@ -758,6 +760,12 @@ export function ChatActions(props: { }} /> )} + + props.setShowShortcutKeyModal(true)} + text={Locale.Chat.ShortcutKey.Title} + icon={} + />
); } @@ -832,6 +840,67 @@ export function DeleteImageButton(props: { deleteImage: () => void }) { ); } +export function ShortcutKeyModal(props: { onClose: () => void }) { + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + const shortcuts = [ + { + title: Locale.Chat.ShortcutKey.newChat, + keys: isMac ? ["⌘", "Shift", "O"] : ["Ctrl", "Shift", "O"], + }, + { title: Locale.Chat.ShortcutKey.focusInput, keys: ["Shift", "Esc"] }, + { + title: Locale.Chat.ShortcutKey.copyLastCode, + keys: isMac ? ["⌘", "Shift", ";"] : ["Ctrl", "Shift", ";"], + }, + { + title: Locale.Chat.ShortcutKey.copyLastMessage, + keys: isMac ? ["⌘", "Shift", "C"] : ["Ctrl", "Shift", "C"], + }, + { + title: Locale.Chat.ShortcutKey.showShortcutKey, + keys: isMac ? ["⌘", "/"] : ["Ctrl", "/"], + }, + ]; + return ( +
+ } + key="ok" + onClick={() => { + props.onClose(); + }} + />, + ]} + > +
+
+ {shortcuts.map((shortcut, index) => ( +
+
+ {shortcut.title} +
+
+ {shortcut.keys.map((key, i) => ( +
+ {key} +
+ ))} +
+
+ ))} +
+
+
+
+ ); +} + function _Chat() { type RenderMessage = ChatMessage & { preview?: boolean }; @@ -1376,6 +1445,70 @@ function _Chat() { setAttachImages(images); } + // 快捷键 shortcut keys + const [showShortcutKeyModal, setShowShortcutKeyModal] = useState(false); + + useEffect(() => { + const handleKeyDown = (event: any) => { + // 打开新聊天 command + shift + o + if ( + (event.metaKey || event.ctrlKey) && + event.shiftKey && + event.key.toLowerCase() === "o" + ) { + event.preventDefault(); + setTimeout(() => { + chatStore.newSession(); + navigate(Path.Chat); + }, 10); + } + // 聚焦聊天输入 shift + esc + else if (event.shiftKey && event.key.toLowerCase() === "escape") { + event.preventDefault(); + inputRef.current?.focus(); + } + // 复制最后一个代码块 command + shift + ; + else if ( + (event.metaKey || event.ctrlKey) && + event.shiftKey && + event.code === "Semicolon" + ) { + event.preventDefault(); + const copyCodeButton = + document.querySelectorAll(".copy-code-button"); + if (copyCodeButton.length > 0) { + copyCodeButton[copyCodeButton.length - 1].click(); + } + } + // 复制最后一个回复 command + shift + c + else if ( + (event.metaKey || event.ctrlKey) && + event.shiftKey && + event.key.toLowerCase() === "c" + ) { + event.preventDefault(); + const lastNonUserMessage = messages + .filter((message) => message.role !== "user") + .pop(); + if (lastNonUserMessage) { + const lastMessageContent = getMessageTextContent(lastNonUserMessage); + copyToClipboard(lastMessageContent); + } + } + // 展示快捷键 command + / + else if ((event.metaKey || event.ctrlKey) && event.key === "/") { + event.preventDefault(); + setShowShortcutKeyModal(true); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [messages, chatStore, navigate]); + return (
@@ -1692,6 +1825,7 @@ function _Chat() { setUserInput("/"); onSearch(""); }} + setShowShortcutKeyModal={setShowShortcutKeyModal} />
); } diff --git a/app/icons/shortcutkey.svg b/app/icons/shortcutkey.svg new file mode 100644 index 000000000000..32a4e7d3e044 --- /dev/null +++ b/app/icons/shortcutkey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 33e368f69f4c..92e81bcb1ba3 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -1,3 +1,4 @@ +import { ShortcutKeyModal } from "../components/chat"; import { getClientConfig } from "../config/client"; import { SubmitKey } from "../store/config"; @@ -81,6 +82,14 @@ const cn = { SaveAs: "存为面具", }, IsContext: "预设提示词", + ShortcutKey: { + Title: "键盘快捷方式", + newChat: "打开新聊天", + focusInput: "聚焦输入框", + copyLastMessage: "复制最后一个回复", + copyLastCode: "复制最后一个代码块", + showShortcutKey: "显示快捷方式", + }, }, Export: { Title: "分享聊天记录", @@ -495,8 +504,8 @@ const cn = { }, }, Copy: { - Success: "已写入剪切板", - Failed: "复制失败,请赋予剪切板权限", + Success: "已写入剪贴板", + Failed: "复制失败,请赋予剪贴板权限", }, Download: { Success: "内容已下载到您的目录。", diff --git a/app/locales/en.ts b/app/locales/en.ts index 403b9b687e74..09b76f1fa12c 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -83,6 +83,14 @@ const en: LocaleType = { SaveAs: "Save as Mask", }, IsContext: "Contextual Prompt", + ShortcutKey: { + Title: "Keyboard Shortcuts", + newChat: "Open New Chat", + focusInput: "Focus Input Field", + copyLastMessage: "Copy Last Reply", + copyLastCode: "Copy Last Code Block", + showShortcutKey: "Show Shortcuts", + }, }, Export: { Title: "Export Messages", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 6b2c0fd65b1b..c54a7b8c5aec 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -81,6 +81,14 @@ const tw = { SaveAs: "另存新檔", }, IsContext: "預設提示詞", + ShortcutKey: { + Title: "鍵盤快捷方式", + newChat: "打開新聊天", + focusInput: "聚焦輸入框", + copyLastMessage: "複製最後一個回覆", + copyLastCode: "複製最後一個代碼塊", + showShortcutKey: "顯示快捷方式", + }, }, Export: { Title: "將聊天記錄匯出為 Markdown",