mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-03-04 21:47:43 +00:00
feat: add copy icon for user messages (#449)
* feat: add copy icon for user messages Expose a copy control on user chat bubbles so previous content can be reused quickly. * fix: Copy control is effectively hidden on touch devices * fix: copyTextToClipboard doesn't need timer --------- Co-authored-by: dev <dev@host.local> Co-authored-by: Haileyesus <118998054+blackmammoth@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
import { Markdown } from './Markdown';
|
||||
import { formatUsageLimitText } from '../../utils/chatFormatting';
|
||||
import { getClaudePermissionSuggestion } from '../../utils/chatPermissions';
|
||||
import { copyTextToClipboard } from '../../../../utils/clipboard';
|
||||
import type { Project } from '../../../../types/app';
|
||||
import { ToolRenderer, shouldHideToolResult } from '../../tools';
|
||||
|
||||
@@ -53,6 +54,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const permissionSuggestion = getClaudePermissionSuggestion(message, provider);
|
||||
const [permissionGrantState, setPermissionGrantState] = React.useState<PermissionGrantState>('idle');
|
||||
const [messageCopied, setMessageCopied] = React.useState(false);
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -100,7 +102,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
{message.type === 'user' ? (
|
||||
/* User message bubble on the right */
|
||||
<div className="flex items-end space-x-0 sm:space-x-3 w-full sm:w-auto sm:max-w-[85%] md:max-w-md lg:max-w-lg xl:max-w-xl">
|
||||
<div className="bg-blue-600 text-white rounded-2xl rounded-br-md px-3 sm:px-4 py-2 shadow-sm flex-1 sm:flex-initial">
|
||||
<div className="bg-blue-600 text-white rounded-2xl rounded-br-md px-3 sm:px-4 py-2 shadow-sm flex-1 sm:flex-initial group">
|
||||
<div className="text-sm whitespace-pre-wrap break-words">
|
||||
{message.content}
|
||||
</div>
|
||||
@@ -117,8 +119,45 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs text-blue-100 mt-1 text-right">
|
||||
{formattedTime}
|
||||
<div className="flex items-center justify-end gap-1 mt-1 text-xs text-blue-100">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const text = String(message.content || '');
|
||||
if (!text) return;
|
||||
|
||||
copyTextToClipboard(text).then((success) => {
|
||||
if (!success) return;
|
||||
setMessageCopied(true);
|
||||
});
|
||||
}}
|
||||
title={messageCopied ? t('copyMessage.copied') : t('copyMessage.copy')}
|
||||
aria-label={messageCopied ? t('copyMessage.copied') : t('copyMessage.copy')}
|
||||
>
|
||||
{messageCopied ? (
|
||||
<svg className="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-3.5 h-3.5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<span>{formattedTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
{!isGrouped && (
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"copied": "Copied",
|
||||
"copyCode": "Copy code"
|
||||
},
|
||||
"copyMessage": {
|
||||
"copy": "Copy message",
|
||||
"copied": "Message copied"
|
||||
},
|
||||
"messageTypes": {
|
||||
"user": "U",
|
||||
"error": "Error",
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"copied": "コピーしました",
|
||||
"copyCode": "コードをコピー"
|
||||
},
|
||||
"copyMessage": {
|
||||
"copy": "メッセージをコピー",
|
||||
"copied": "メッセージをコピーしました"
|
||||
},
|
||||
"messageTypes": {
|
||||
"user": "U",
|
||||
"error": "エラー",
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"copied": "복사됨",
|
||||
"copyCode": "코드 복사"
|
||||
},
|
||||
"copyMessage": {
|
||||
"copy": "메시지 복사",
|
||||
"copied": "메시지 복사됨"
|
||||
},
|
||||
"messageTypes": {
|
||||
"user": "U",
|
||||
"error": "오류",
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"copied": "已复制",
|
||||
"copyCode": "复制代码"
|
||||
},
|
||||
"copyMessage": {
|
||||
"copy": "复制消息",
|
||||
"copied": "消息已复制"
|
||||
},
|
||||
"messageTypes": {
|
||||
"user": "U",
|
||||
"error": "错误",
|
||||
|
||||
Reference in New Issue
Block a user