mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-12 01:35:45 +08: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 { Markdown } from './Markdown';
|
||||||
import { formatUsageLimitText } from '../../utils/chatFormatting';
|
import { formatUsageLimitText } from '../../utils/chatFormatting';
|
||||||
import { getClaudePermissionSuggestion } from '../../utils/chatPermissions';
|
import { getClaudePermissionSuggestion } from '../../utils/chatPermissions';
|
||||||
|
import { copyTextToClipboard } from '../../../../utils/clipboard';
|
||||||
import type { Project } from '../../../../types/app';
|
import type { Project } from '../../../../types/app';
|
||||||
import { ToolRenderer, shouldHideToolResult } from '../../tools';
|
import { ToolRenderer, shouldHideToolResult } from '../../tools';
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
|||||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||||
const permissionSuggestion = getClaudePermissionSuggestion(message, provider);
|
const permissionSuggestion = getClaudePermissionSuggestion(message, provider);
|
||||||
const [permissionGrantState, setPermissionGrantState] = React.useState<PermissionGrantState>('idle');
|
const [permissionGrantState, setPermissionGrantState] = React.useState<PermissionGrantState>('idle');
|
||||||
|
const [messageCopied, setMessageCopied] = React.useState(false);
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -100,7 +102,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
|||||||
{message.type === 'user' ? (
|
{message.type === 'user' ? (
|
||||||
/* User message bubble on the right */
|
/* 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="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">
|
<div className="text-sm whitespace-pre-wrap break-words">
|
||||||
{message.content}
|
{message.content}
|
||||||
</div>
|
</div>
|
||||||
@@ -117,8 +119,45 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-xs text-blue-100 mt-1 text-right">
|
<div className="flex items-center justify-end gap-1 mt-1 text-xs text-blue-100">
|
||||||
{formattedTime}
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{!isGrouped && (
|
{!isGrouped && (
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
"copied": "Copied",
|
"copied": "Copied",
|
||||||
"copyCode": "Copy code"
|
"copyCode": "Copy code"
|
||||||
},
|
},
|
||||||
|
"copyMessage": {
|
||||||
|
"copy": "Copy message",
|
||||||
|
"copied": "Message copied"
|
||||||
|
},
|
||||||
"messageTypes": {
|
"messageTypes": {
|
||||||
"user": "U",
|
"user": "U",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
"copied": "コピーしました",
|
"copied": "コピーしました",
|
||||||
"copyCode": "コードをコピー"
|
"copyCode": "コードをコピー"
|
||||||
},
|
},
|
||||||
|
"copyMessage": {
|
||||||
|
"copy": "メッセージをコピー",
|
||||||
|
"copied": "メッセージをコピーしました"
|
||||||
|
},
|
||||||
"messageTypes": {
|
"messageTypes": {
|
||||||
"user": "U",
|
"user": "U",
|
||||||
"error": "エラー",
|
"error": "エラー",
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
"copied": "복사됨",
|
"copied": "복사됨",
|
||||||
"copyCode": "코드 복사"
|
"copyCode": "코드 복사"
|
||||||
},
|
},
|
||||||
|
"copyMessage": {
|
||||||
|
"copy": "메시지 복사",
|
||||||
|
"copied": "메시지 복사됨"
|
||||||
|
},
|
||||||
"messageTypes": {
|
"messageTypes": {
|
||||||
"user": "U",
|
"user": "U",
|
||||||
"error": "오류",
|
"error": "오류",
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
"copied": "已复制",
|
"copied": "已复制",
|
||||||
"copyCode": "复制代码"
|
"copyCode": "复制代码"
|
||||||
},
|
},
|
||||||
|
"copyMessage": {
|
||||||
|
"copy": "复制消息",
|
||||||
|
"copied": "消息已复制"
|
||||||
|
},
|
||||||
"messageTypes": {
|
"messageTypes": {
|
||||||
"user": "U",
|
"user": "U",
|
||||||
"error": "错误",
|
"error": "错误",
|
||||||
|
|||||||
Reference in New Issue
Block a user