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">) { handleCopy("markdown")}>Copy as Markdown + handleCopy("tsv")}>Copy as TSV handleCopy("csv")}>Copy as CSV @@ -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({