## Summary Adds AI response copy functionality to chat messages that preserves formatting and converts Dyad-specific tags to clean, readable markdown. ## Changes - **New `useCopyToClipboard` hook**: Parses Dyad tags and converts them to professional markdown format - **Updated `ChatMessage` component**: Positions copy button on left side of approval status - **Dyad tag conversion**: Transforms custom tags to readable format: - `<dyad-write>` → `### File: path/to/file.js` + code block - `<dyad-edit>` → `### Edit: path/to/file.js` + code block - `<dyad-execute-sql>` → `### Execute SQL` + ```sql block - `<think>` → `### Thinking` + content ## Features - ✅ Automatic programming language detection from file extensions - ✅ Professional markdown formatting with proper headings and code blocks - ✅ Tooltip showing "Copied" confirmation - ✅ Reuses existing DyadMarkdownParser logic for consistency closes (#1290) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds a Copy button to assistant messages that copies a clean Markdown version of the response by converting Dyad tags and preserving code blocks. Improves shareability and removes Dyad-only markup; addresses Linear #1290. - **New Features** - Added useCopyToClipboard hook that parses Dyad tags to Markdown, auto-detects code language, and cleans spacing. - Updated ChatMessage to show a Copy button (with Copy/Copied tooltip) to the left of approval status; disabled while streaming. - Tag conversions: think → "### Thinking"; dyad-write/edit → "### File/Edit: path" + fenced code; dyad-execute-sql → "### Execute SQL" + sql block; other Dyad tags map to concise headings; chat-summary/command are omitted. - Added e2e tests for clipboard copy, Dyad tag stripping/formatting, and tooltip states. <!-- End of auto-generated description by cubic. -->
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { test } from "./helpers/test_helper";
|
|
import { expect } from "@playwright/test";
|
|
|
|
test("copy message content - basic functionality", async ({ po }) => {
|
|
await po.setUp({ autoApprove: true });
|
|
await po.importApp("minimal");
|
|
|
|
await po.sendPrompt("[dump] Just say hello without creating any files");
|
|
|
|
await po.page
|
|
.context()
|
|
.grantPermissions(["clipboard-read", "clipboard-write"]);
|
|
|
|
const copyButton = po.page.getByTestId("copy-message-button").first();
|
|
await copyButton.click();
|
|
|
|
const clipboardContent = await po.page.evaluate(() =>
|
|
navigator.clipboard.readText(),
|
|
);
|
|
|
|
// Test that copy functionality works
|
|
expect(clipboardContent.length).toBeGreaterThan(0);
|
|
expect(clipboardContent).not.toContain("<dyad-");
|
|
});
|
|
|
|
test("copy message content - dyad-write conversion", async ({ po }) => {
|
|
await po.setUp({ autoApprove: true });
|
|
await po.importApp("minimal");
|
|
|
|
await po.sendPrompt(
|
|
"Create a simple React component in src/components/Button.tsx",
|
|
);
|
|
|
|
await po.page
|
|
.context()
|
|
.grantPermissions(["clipboard-read", "clipboard-write"]);
|
|
|
|
const copyButton = po.page.getByTestId("copy-message-button").first();
|
|
await copyButton.click();
|
|
|
|
const clipboardContent = await po.page.evaluate(() =>
|
|
navigator.clipboard.readText(),
|
|
);
|
|
|
|
// Should convert dyad-write to markdown format (flexible path matching)
|
|
expect(clipboardContent).toContain("### File:");
|
|
expect(clipboardContent).toContain("```");
|
|
expect(clipboardContent).not.toContain("<dyad-write");
|
|
});
|
|
|
|
test("copy button tooltip states", async ({ po }) => {
|
|
await po.setUp({ autoApprove: true });
|
|
await po.importApp("minimal");
|
|
|
|
await po.sendPrompt("Say hello");
|
|
|
|
const copyButton = po.page.getByTestId("copy-message-button").first();
|
|
|
|
// Check initial tooltip
|
|
await copyButton.hover();
|
|
const tooltip = po.page.locator('[role="tooltip"]');
|
|
await expect(tooltip).toHaveText("Copy");
|
|
|
|
// Copy and check "Copied!" state
|
|
await po.page
|
|
.context()
|
|
.grantPermissions(["clipboard-read", "clipboard-write"]);
|
|
await copyButton.click();
|
|
await copyButton.hover();
|
|
await expect(tooltip).toHaveText("Copied!");
|
|
});
|