diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json
index 1496e315..847b67a4 100644
--- a/apps/client/public/locales/en-US/translation.json
+++ b/apps/client/public/locales/en-US/translation.json
@@ -253,6 +253,7 @@
"Invite link": "Invite link",
"Copy": "Copy",
"Copy to space": "Copy to space",
+ "Copy chat": "Copy chat",
"Copied": "Copied",
"Duplicate": "Duplicate",
"Select a user": "Select a user",
@@ -947,7 +948,6 @@
"Try a different search term.": "Try a different search term.",
"Try again": "Try again",
"Untitled chat": "Untitled chat",
- "Export chat": "Export chat",
"You": "You",
"What can I help you with?": "What can I help you with?",
"Are you sure you want to revoke this {{credential}}": "Are you sure you want to revoke this {{credential}}",
diff --git a/apps/client/src/features/ai-chat/components/ai-chat-window.tsx b/apps/client/src/features/ai-chat/components/ai-chat-window.tsx
index ef3aabe6..c034b4c2 100644
--- a/apps/client/src/features/ai-chat/components/ai-chat-window.tsx
+++ b/apps/client/src/features/ai-chat/components/ai-chat-window.tsx
@@ -9,8 +9,9 @@ import {
import { Group, Loader, Tooltip } from "@mantine/core";
import {
IconArrowsDiagonal,
+ IconCheck,
IconChevronDown,
- IconFileExport,
+ IconCopy,
IconGripVertical,
IconMinus,
IconPlus,
@@ -34,7 +35,9 @@ import {
} from "@/features/ai-chat/queries/ai-chat-query.ts";
import ConversationList from "@/features/ai-chat/components/conversation-list.tsx";
import ChatThread from "@/features/ai-chat/components/chat-thread.tsx";
-import { exportChatAsMarkdown } from "@/features/ai-chat/utils/export-chat.ts";
+import { buildChatMarkdown } from "@/features/ai-chat/utils/chat-markdown.ts";
+import { useClipboard } from "@/hooks/use-clipboard";
+import { notifications } from "@mantine/notifications";
import classes from "@/features/ai-chat/components/ai-chat-window.module.css";
// Default window geometry (from the GitmostAgent.jsx design).
@@ -90,6 +93,7 @@ function clampGeom(g: { left: number; top: number; width: number; height: number
*/
export default function AiChatWindow() {
const { t } = useTranslation();
+ const clipboard = useClipboard({ timeout: 500 });
const queryClient = useQueryClient();
const [windowOpen, setWindowOpen] = useAtom(aiChatWindowOpenAtom);
const [activeChatId, setActiveChatId] = useAtom(activeAiChatIdAtom);
@@ -165,16 +169,19 @@ export default function AiChatWindow() {
const canExport = !!activeChatId && !!messageRows && messageRows.length > 0;
// Build a Markdown export from the already-loaded persisted rows (no network
- // call) and trigger a browser download. The download dialog is the feedback.
- const handleExport = useCallback(() => {
+ // call) and copy it to the clipboard. The "Copied" notification is the
+ // feedback.
+ const handleCopy = useCallback(() => {
if (!activeChatId || !messageRows || messageRows.length === 0) return;
- exportChatAsMarkdown({
+ const markdown = buildChatMarkdown({
title: activeChat?.title ?? null,
chatId: activeChatId,
rows: messageRows,
t,
});
- }, [activeChatId, messageRows, activeChat, t]);
+ clipboard.copy(markdown);
+ notifications.show({ message: t("Copied") });
+ }, [activeChatId, messageRows, activeChat, clipboard, t]);
// When awaiting a new chat's id, adopt the most-recent chat (the list is
// ordered newest-first) once it appears.
@@ -334,11 +341,11 @@ export default function AiChatWindow() {
)}