diff --git a/apps/web/package.json b/apps/web/package.json
index 8b8aec8ffa577..0a322d21ec71f 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -69,6 +69,7 @@
"react-dom": "19.2.6",
"react-markdown": "^10.1.0",
"remark-gfm": "catalog:",
+ "remark-parse": "catalog:",
"tailwind-merge": "^3.4.0",
"unbash": "^5.0.0",
"zustand": "^5.0.11"
diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx
index fcf193a45590e..0e58c94f7c8b2 100644
--- a/apps/web/src/components/ChatMarkdown.tsx
+++ b/apps/web/src/components/ChatMarkdown.tsx
@@ -52,6 +52,7 @@ import {
type EnvironmentId,
type ScopedThreadRef,
type ServerProviderSkill,
+ type TableCopyFormat,
type ThreadPullRequestKey,
} from "@t3tools/contracts";
import { faviconUrlForOrigin } from "@t3tools/shared/favicon";
@@ -162,7 +163,7 @@ import { useTheme } from "../hooks/useTheme";
import { getClientSettings, useClientSettings } from "../hooks/useSettings";
import {
chatMarkdownClipboardPayload,
- serializeTableElementToCsv,
+ serializeTableElementToDelimited,
serializeTableElementToMarkdown,
} from "../markdown-clipboard";
import {
@@ -350,7 +351,7 @@ const MAX_HIGHLIGHT_CACHE_MEMORY_BYTES = 50 * 1024 * 1024;
interface MarkdownActionFailureContext {
readonly operation: string;
readonly target?: string;
- readonly format?: "markdown" | "csv";
+ readonly format?: TableCopyFormat;
readonly language?: string;
readonly fenceTitle?: string;
readonly copyTarget?: string;
@@ -573,7 +574,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
setExpanded((value) => !value);
}
- const handleCopy = useCallback((format: "markdown" | "csv") => {
+ const handleCopy = useCallback((format: TableCopyFormat) => {
const table = containerRef.current?.querySelector("table");
if (!table || typeof navigator === "undefined" || navigator.clipboard == null) {
return;
@@ -581,7 +582,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
const text =
format === "markdown"
? serializeTableElementToMarkdown(table)
- : serializeTableElementToCsv(table);
+ : serializeTableElementToDelimited(table, format);
void navigator.clipboard
.writeText(text)
.then(() => {
@@ -660,6 +661,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
+
@@ -2404,7 +2406,7 @@ function useChatMarkdownState({
const handleCopy = useCallback((event: ReactClipboardEvent) => {
const selection = window.getSelection();
if (!selection || selection.isCollapsed || !event.clipboardData) return;
- const payload = chatMarkdownClipboardPayload(selection);
+ const payload = chatMarkdownClipboardPayload(selection, getClientSettings().tableCopyFormat);
if (!payload) return;
event.preventDefault();
event.clipboardData.setData("text/plain", payload.text);
diff --git a/apps/web/src/components/chat/MessageCopyButton.tsx b/apps/web/src/components/chat/MessageCopyButton.tsx
index 361f9a6c2a5bc..4238297169613 100644
--- a/apps/web/src/components/chat/MessageCopyButton.tsx
+++ b/apps/web/src/components/chat/MessageCopyButton.tsx
@@ -20,7 +20,7 @@ export const MessageCopyButton = memo(function MessageCopyButton({
variant = "outline",
className,
}: {
- text: string;
+ text: string | (() => string);
label?: string;
icon?: typeof Copy;
extraFlavors?: Readonly>;
@@ -43,7 +43,7 @@ export const MessageCopyButton = memo(function MessageCopyButton({