From 39f957f1c9fa41c7a47e1b690963f89cda14ca09 Mon Sep 17 00:00:00 2001
From: Lgf <2246221508@qq.com>
Date: Tue, 21 Apr 2026 21:27:36 +0800
Subject: [PATCH 1/2] =?UTF-8?q?feat(=E8=81=8A=E5=A4=A9=E7=95=8C=E9=9D=A2):?=
=?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E5=AF=BC=E5=87=BA=E8=81=8A=E5=A4=A9?=
=?UTF-8?q?=E8=AE=B0=E5=BD=95=E4=B8=BAMarkdown=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
在聊天界面添加新的导出按钮,支持将当前会话导出为Markdown格式文件。包含会话标题、日期、模型信息和完整的对话内容。
---
app/components/chat.tsx | 40 ++++++++++++++++++++++++++++++++++++++++
app/locales/cn.ts | 1 +
app/locales/en.ts | 3 ++-
3 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index 6691403e65b..7d1e4df291c 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -34,6 +34,7 @@ import ConfirmIcon from "../icons/confirm.svg";
import CloseIcon from "../icons/close.svg";
import CancelIcon from "../icons/cancel.svg";
import ImageIcon from "../icons/image.svg";
+import DownloadIcon from "../icons/download.svg";
import LightIcon from "../icons/light.svg";
import DarkIcon from "../icons/dark.svg";
@@ -65,6 +66,7 @@ import {
import {
autoGrowTextArea,
copyToClipboard,
+ downloadAs,
getMessageImages,
getMessageTextContent,
isDalle3,
@@ -1745,6 +1747,44 @@ function _Chat() {
}}
/>
+
+ }
+ bordered
+ title={Locale.Chat.Actions.ExportMarkdown}
+ onClick={() => {
+ const currentSession = chatStore.currentSession();
+ const messages = currentSession.messages;
+ const topic = currentSession.topic || DEFAULT_TOPIC;
+ const model = currentSession.mask.modelConfig.model;
+ const today = new Date().toISOString().split("T")[0];
+
+ let mdContent = `---
+title: "${topic}"
+date: "${today}"
+model: "${model}"
+---
+
+# ${topic}
+
+`;
+
+ messages.forEach((m) => {
+ const role =
+ m.role === "user"
+ ? Locale.Export.MessageFromYou
+ : Locale.Export.MessageFromChatGPT;
+ mdContent += `## ${role}
+
+${getMessageTextContent(m).trim()}
+
+`;
+ });
+
+ downloadAs(mdContent, `${topic}.md`);
+ }}
+ />
+
{showMaxIcon && (
Date: Tue, 21 Apr 2026 21:50:32 +0800
Subject: [PATCH 2/2] =?UTF-8?q?feat(chat):=20=E6=B7=BB=E5=8A=A0=E6=B6=88?=
=?UTF-8?q?=E6=81=AF=E9=80=89=E6=8B=A9=E6=A8=A1=E5=BC=8F=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
实现消息多选功能,包括选择模式切换、消息勾选、选择数量显示和导出选中消息为Markdown。添加相关UI组件和样式,支持批量操作聊天记录。
---
app/components/chat.module.scss | 38 +++++
app/components/chat.tsx | 266 ++++++++++++++++++++++----------
app/locales/cn.ts | 7 +
app/locales/en.ts | 7 +
4 files changed, 237 insertions(+), 81 deletions(-)
diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss
index 7560d030533..ebc5b15ff76 100644
--- a/app/components/chat.module.scss
+++ b/app/components/chat.module.scss
@@ -321,6 +321,20 @@
}
}
+.chat-message-checkbox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 8px;
+ cursor: pointer;
+
+ input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ }
+}
+
.chat-message-user {
display: flex;
flex-direction: row-reverse;
@@ -569,6 +583,30 @@
}
}
+.chat-selection-panel {
+ position: relative;
+ width: 100%;
+ padding: 16px 20px;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-top: var(--border-in-light);
+ box-shadow: var(--card-shadow);
+ background-color: var(--white);
+
+ .chat-selection-info {
+ font-size: 14px;
+ color: var(--black);
+ font-weight: 500;
+ }
+
+ .chat-selection-actions {
+ display: flex;
+ gap: 12px;
+ }
+}
+
@mixin single-line {
white-space: nowrap;
overflow: hidden;
diff --git a/app/components/chat.tsx b/app/components/chat.tsx
index 7d1e4df291c..835a7fd45ba 100644
--- a/app/components/chat.tsx
+++ b/app/components/chat.tsx
@@ -35,6 +35,7 @@ import CloseIcon from "../icons/close.svg";
import CancelIcon from "../icons/cancel.svg";
import ImageIcon from "../icons/image.svg";
import DownloadIcon from "../icons/download.svg";
+import MenuIcon from "../icons/menu.svg";
import LightIcon from "../icons/light.svg";
import DarkIcon from "../icons/dark.svg";
@@ -998,6 +999,25 @@ function _Chat() {
const fontFamily = config.fontFamily;
const [showExport, setShowExport] = useState(false);
+ const [isSelectionMode, setIsSelectionMode] = useState(false);
+ const [selectedMessageIds, setSelectedMessageIds] = useState>(new Set());
+
+ const toggleMessageSelection = (messageId: string) => {
+ setSelectedMessageIds((prev) => {
+ const newSet = new Set(prev);
+ if (newSet.has(messageId)) {
+ newSet.delete(messageId);
+ } else {
+ newSet.add(messageId);
+ }
+ return newSet;
+ });
+ };
+
+ const exitSelectionMode = () => {
+ setIsSelectionMode(false);
+ setSelectedMessageIds(new Set());
+ };
const inputRef = useRef(null);
const [userInput, setUserInput] = useState("");
@@ -1737,6 +1757,16 @@ function _Chat() {
/>
)}
+
+ }
+ bordered
+ title={Locale.Chat.Actions.SelectionMode}
+ onClick={() => {
+ setIsSelectionMode(true);
+ }}
+ />
+
}
@@ -1844,6 +1874,21 @@ ${getMessageTextContent(m).trim()}
: styles["chat-message"]
}
>
+ {isSelectionMode && (
+
{
+ e.stopPropagation();
+ toggleMessageSelection(message.id);
+ }}
+ >
+
+
+ )}
@@ -2079,92 +2124,151 @@ ${getMessageTextContent(m).trim()}
);
})}
-
-
+ {!isSelectionMode && (
+
+
-
setShowPromptModal(true)}
- scrollToBottom={scrollToBottom}
- hitBottom={hitBottom}
- uploading={uploading}
- showPromptHints={() => {
- // Click again to close
- if (promptHints.length > 0) {
- setPromptHints([]);
- return;
- }
-
- inputRef.current?.focus();
- setUserInput("/");
- onSearch("");
- }}
- setShowShortcutKeyModal={setShowShortcutKeyModal}
- setUserInput={setUserInput}
- setShowChatSidePanel={setShowChatSidePanel}
- />
-
+ )}
`已选择 ${count} 条消息`,
+ Cancel: "取消",
+ ExportMarkdown: "导出所选为 Markdown",
+ NoSelection: "请先选择消息",
+ },
Commands: {
new: "新建聊天",
newm: "从面具新建聊天",
diff --git a/app/locales/en.ts b/app/locales/en.ts
index 05016d44419..a6cc945d788 100644
--- a/app/locales/en.ts
+++ b/app/locales/en.ts
@@ -47,6 +47,7 @@ const en: LocaleType = {
CompressedHistory: "Compressed History Memory Prompt",
Export: "Export All Messages",
ExportMarkdown: "Export as Markdown",
+ SelectionMode: "Selection Mode",
Copy: "Copy",
Stop: "Stop",
Retry: "Retry",
@@ -61,6 +62,12 @@ const en: LocaleType = {
Speech: "Play",
StopSpeech: "Stop",
},
+ Selection: {
+ SelectedCount: (count: number) => `${count} messages selected`,
+ Cancel: "Cancel",
+ ExportMarkdown: "Export Selected as Markdown",
+ NoSelection: "Please select messages first",
+ },
Commands: {
new: "Start a new chat",
newm: "Start a new chat with mask",