Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 402f7a7487 |
@@ -16,6 +16,7 @@ and this project adheres to
|
||||
### Changed
|
||||
|
||||
- ⬆️(dependencies) upgrade Next.js 15 to 16, upgrade python dependencies
|
||||
- ✨(front) rich text copy for Word/Docs paste
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Message, SourceUIPart, ToolInvocationUIPart } from '@ai-sdk/ui-utils';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Icon, Loader, Text } from '@/components';
|
||||
import { Box, Icon, Loader, Text, useToast } from '@/components';
|
||||
import { AttachmentList } from '@/features/chat/components/AttachmentList';
|
||||
import { FeedbackButtons } from '@/features/chat/components/FeedbackButtons';
|
||||
import {
|
||||
@@ -185,6 +185,8 @@ const MessageItemComponent: React.FC<MessageItemProps> = ({
|
||||
getMetadata,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToast();
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const shouldApplyStreamingHeight =
|
||||
isLastAssistantMessage &&
|
||||
@@ -240,8 +242,26 @@ const MessageItemComponent: React.FC<MessageItemProps> = ({
|
||||
}, [isCurrentlyStreaming, message.content]);
|
||||
|
||||
const handleCopy = React.useCallback(() => {
|
||||
onCopyToClipboard(message.content);
|
||||
}, [onCopyToClipboard, message.content]);
|
||||
const html = contentRef.current?.innerHTML;
|
||||
if (
|
||||
html &&
|
||||
typeof ClipboardItem !== 'undefined' &&
|
||||
navigator.clipboard.write
|
||||
) {
|
||||
// Write both formats: Word/Docs use text/html (preserving formatting),
|
||||
// while code editors and plain-text apps use text/plain (raw markdown).
|
||||
const item = new ClipboardItem({
|
||||
'text/html': new Blob([html], { type: 'text/html' }),
|
||||
'text/plain': new Blob([message.content], { type: 'text/plain' }),
|
||||
});
|
||||
navigator.clipboard.write([item]).then(
|
||||
() => showToast('success', t('Copied'), 'content_copy', 3000),
|
||||
() => onCopyToClipboard(message.content),
|
||||
);
|
||||
} else {
|
||||
onCopyToClipboard(message.content);
|
||||
}
|
||||
}, [contentRef, message.content, onCopyToClipboard, showToast, t]);
|
||||
|
||||
const handleCopyKeyDown = React.useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
@@ -310,6 +330,7 @@ const MessageItemComponent: React.FC<MessageItemProps> = ({
|
||||
{/* Message content */}
|
||||
{message.content && (
|
||||
<Box
|
||||
ref={contentRef}
|
||||
className="mainContent-chat"
|
||||
data-testid={
|
||||
message.role === 'assistant'
|
||||
|
||||
+5
-1
@@ -4,6 +4,8 @@ import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { ToastProvider } from '@/components/ToastProvider';
|
||||
|
||||
import {
|
||||
MessageItem,
|
||||
splitIntoBlocks,
|
||||
@@ -367,7 +369,9 @@ describe('MessageItem', () => {
|
||||
const renderWithProviders = (ui: React.ReactNode) => {
|
||||
return render(
|
||||
<CunninghamProvider>
|
||||
<Suspense fallback={null}>{ui}</Suspense>
|
||||
<ToastProvider>
|
||||
<Suspense fallback={null}>{ui}</Suspense>
|
||||
</ToastProvider>
|
||||
</CunninghamProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user