diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss
index 7176399cc36..98972f623bd 100644
--- a/app/components/chat.module.scss
+++ b/app/components/chat.module.scss
@@ -646,3 +646,52 @@
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(250px, 1fr));
+ gap: 16px;
+}
+
+.shortcut-key-item {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ border: 1px solid #ddd;
+ border-radius: 10px;
+ padding: 10px;
+ background-color: #fff;
+}
+
+.shortcut-key-title {
+ font-size: 14px;
+ color: #333;
+ margin-bottom: 8px;
+}
+
+.shortcut-key-keys {
+ display: flex;
+ gap: 8px;
+}
+
+.shortcut-key {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ padding: 4px;
+ background-color: #f9f9f9;
+ min-width: 32px;
+}
+
+.shortcut-key span {
+ font-size: 12px;
+}
\ No newline at end of file
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index dad1933ace9..ab94ab41dcd 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -829,6 +829,57 @@ export function DeleteImageButton(props: { deleteImage: () => void }) {
);
}
+export function ShortcutKeyModal(props: { onClose: () => void }) {
+ const shortcuts = [
+ { title: Locale.Chat.ShortcutKey.newChat, keys: ["⌘", "Shift", "o"] },
+ { title: Locale.Chat.ShortcutKey.focusInput, keys: ["Shift", "Esc"] },
+ { title: Locale.Chat.ShortcutKey.copyLastCode, keys: ["⌘", "Shift", ";"] },
+ {
+ title: Locale.Chat.ShortcutKey.copyLastMessage,
+ keys: ["⌘", "Shift", "c"],
+ },
+ { title: Locale.Chat.ShortcutKey.showShortcutKey, keys: ["⌘", "/"] },
+ ];
+ 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 };
@@ -1373,6 +1424,69 @@ function _Chat() {
setAttachImages(images);
}
+ // 快捷键
+ const [showShortcutKeyModal, setShowShortcutKeyModal] = useState(false);
+
+ useEffect(() => {
+ const handleKeyDown = (event) => {
+ // 打开新聊天 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 (
@@ -1760,6 +1874,10 @@ function _Chat() {
}}
/>
)}
+
+ {showShortcutKeyModal && (
+ setShowShortcutKeyModal(false)} />
+ )}
);
}
diff --git a/app/locales/cn.ts b/app/locales/cn.ts
index 33e368f69f4..24f707e4018 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: "分享聊天记录",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 403b9b687e7..09b76f1fa12 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 6b2c0fd65b1..c54a7b8c5ae 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",