diff --git a/src/components/ChatBox/MessageItem/AgentMessageCard.tsx b/src/components/ChatBox/MessageItem/AgentMessageCard.tsx index f5fb2036..60699d08 100644 --- a/src/components/ChatBox/MessageItem/AgentMessageCard.tsx +++ b/src/components/ChatBox/MessageItem/AgentMessageCard.tsx @@ -12,11 +12,15 @@ // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= -import { Copy, FileText } from 'lucide-react'; -import { useMemo } from 'react'; +import { Check, Copy, FileText } from 'lucide-react'; +import { useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; import { Button } from '../../ui/button'; import { MarkDown } from './MarkDown'; +const COPIED_RESET_MS = 2000; + interface AgentMessageCardProps { id: string; content: string; @@ -48,6 +52,9 @@ export function AgentMessageCard({ // if completed, disable typewriter effect const enableTypewriter = !isCompleted; + const [copied, setCopied] = useState(false); + const { t } = useTranslation(); + // when typewriter effect is completed, record to global Map const handleTypingComplete = () => { if (!isCompleted) { @@ -58,9 +65,16 @@ export function AgentMessageCard({ } }; - const handleCopy = () => { - navigator.clipboard.writeText(content); - }; + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(content); + toast.success(t('setting.copied-to-clipboard')); + setCopied(true); + setTimeout(() => setCopied(false), COPIED_RESET_MS); + } catch { + toast.error('Failed to copy to clipboard'); + } + }, [content, t]); return (
(null); + const { t } = useTranslation(); - const handleCopy = () => { - navigator.clipboard.writeText(content); - }; + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(content); + toast.success(t('setting.copied-to-clipboard')); + setCopied(true); + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = window.setTimeout(() => { + setCopied(false); + timeoutRef.current = null; + }, COPIED_RESET_MS); + } catch { + toast.error(t('setting.failed-to-copy-to-clipboard')); + } + }, [content, t]); + + useEffect(() => { + return () => { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + } + }; + }, []); return (
diff --git a/src/components/ChatBox/MessageItem/UserMessageCard.tsx b/src/components/ChatBox/MessageItem/UserMessageCard.tsx index bf0183cf..4c408224 100644 --- a/src/components/ChatBox/MessageItem/UserMessageCard.tsx +++ b/src/components/ChatBox/MessageItem/UserMessageCard.tsx @@ -13,11 +13,15 @@ // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= import { cn } from '@/lib/utils'; -import { Copy, FileText, Image } from 'lucide-react'; -import { useRef, useState } from 'react'; +import { Check, Copy, FileText, Image } from 'lucide-react'; +import { useCallback, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; import { Button } from '../../ui/button'; import { Popover, PopoverContent, PopoverTrigger } from '../../ui/popover'; +const COPIED_RESET_MS = 2000; + interface UserMessageCardProps { id: string; content: string; @@ -33,11 +37,20 @@ export function UserMessageCard({ }: UserMessageCardProps) { const [_hoveredFilePath, setHoveredFilePath] = useState(null); const [isRemainingOpen, setIsRemainingOpen] = useState(false); + const [copied, setCopied] = useState(false); const hoverCloseTimerRef = useRef(null); + const { t } = useTranslation(); - const handleCopy = () => { - navigator.clipboard.writeText(content); - }; + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(content); + toast.success(t('setting.copied-to-clipboard')); + setCopied(true); + setTimeout(() => setCopied(false), COPIED_RESET_MS); + } catch { + toast.error('Failed to copy to clipboard'); + } + }, [content, t]); // Popover handles outside clicks; no manual listener needed const openRemainingPopover = () => { @@ -73,7 +86,11 @@ export function UserMessageCard({ >
diff --git a/src/i18n/locales/ar/setting.json b/src/i18n/locales/ar/setting.json index a20b363e..293108ee 100644 --- a/src/i18n/locales/ar/setting.json +++ b/src/i18n/locales/ar/setting.json @@ -42,6 +42,7 @@ "validate-failed": "فشل التحقق", "copy": "نسخ", "copied-to-clipboard": "تم النسخ إلى الحافظة", + "failed-to-copy-to-clipboard": "فشل النسخ إلى الحافظة", "endpoint-url-can-not-be-empty": "!لا يمكن أن يكون عنوان يورل لنقطة النهاية فارغًا", "verification-failed-please-check-endpoint-url": "فشل التحقق، يرجى المراجعة على النقطة النهائية يورل", "eigent-cloud-version": "إصدار أيجنت السحابي", diff --git a/src/i18n/locales/de/setting.json b/src/i18n/locales/de/setting.json index 0bbc3be4..285fd2b5 100644 --- a/src/i18n/locales/de/setting.json +++ b/src/i18n/locales/de/setting.json @@ -42,6 +42,7 @@ "validate-failed": "Validierung fehlgeschlagen", "copy": "Kopieren", "copied-to-clipboard": "In die Zwischenablage kopiert", + "failed-to-copy-to-clipboard": "Kopieren in die Zwischenablage fehlgeschlagen", "endpoint-url-can-not-be-empty": "Endpunkt-URL darf nicht leer sein!", "verification-failed-please-check-endpoint-url": "Verifizierung fehlgeschlagen, bitte überprüfen Sie die Endpunkt-URL", "eigent-cloud-version": "Eigent Cloud-Version", diff --git a/src/i18n/locales/en-us/setting.json b/src/i18n/locales/en-us/setting.json index f04446e4..6e89f50d 100644 --- a/src/i18n/locales/en-us/setting.json +++ b/src/i18n/locales/en-us/setting.json @@ -42,6 +42,7 @@ "validate-failed": "Validate failed", "copy": "Copy", "copied-to-clipboard": "Copied to clipboard", + "failed-to-copy-to-clipboard": "Failed to copy to clipboard", "endpoint-url-can-not-be-empty": "Endpoint URL can not be empty!", "verification-failed-please-check-endpoint-url": "Verification failed, please check Endpoint URL", "eigent-cloud-version": "Eigent Cloud Version", @@ -258,6 +259,7 @@ "validate-failed": "Validate failed", "copy": "Copy", "copied-to-clipboard": "Copied to clipboard", + "failed-to-copy-to-clipboard": "Failed to copy to clipboard", "endpoint-url-can-not-be-empty": "Endpoint URL can not be empty!", "verification-failed-please-check-endpoint-url": "Verification failed, please check Endpoint URL", "eigent-cloud-version": "Eigent Cloud Version", diff --git a/src/i18n/locales/es/setting.json b/src/i18n/locales/es/setting.json index 04e299d2..e78d5a3d 100644 --- a/src/i18n/locales/es/setting.json +++ b/src/i18n/locales/es/setting.json @@ -42,6 +42,7 @@ "validate-failed": "Validación fallida", "copy": "Copiar", "copied-to-clipboard": "Copiado al portapapeles", + "failed-to-copy-to-clipboard": "Error al copiar al portapapeles", "endpoint-url-can-not-be-empty": "Endpoint URL no puede estar vacío!", "verification-failed-please-check-endpoint-url": "Verificación fallida, por favor verifique Endpoint URL", "eigent-cloud-version": "Eigent Cloud Version", diff --git a/src/i18n/locales/fr/setting.json b/src/i18n/locales/fr/setting.json index 2d95585a..0ed9bcda 100644 --- a/src/i18n/locales/fr/setting.json +++ b/src/i18n/locales/fr/setting.json @@ -42,6 +42,7 @@ "validate-failed": "Validate failed", "copy": "Copy", "copied-to-clipboard": "Copied to clipboard", + "failed-to-copy-to-clipboard": "Échec de la copie dans le presse-papiers", "endpoint-url-can-not-be-empty": "Endpoint URL can not be empty!", "verification-failed-please-check-endpoint-url": "Verification failed, please check Endpoint URL", "eigent-cloud-version": "Eigent Cloud Version", diff --git a/src/i18n/locales/it/setting.json b/src/i18n/locales/it/setting.json index 127d8936..01ee885a 100644 --- a/src/i18n/locales/it/setting.json +++ b/src/i18n/locales/it/setting.json @@ -42,6 +42,7 @@ "validate-failed": "Validazione fallita", "copy": "Copia", "copied-to-clipboard": "Copiato negli appunti", + "failed-to-copy-to-clipboard": "Impossibile copiare negli appunti", "endpoint-url-can-not-be-empty": "L'URL dell'endpoint non può essere vuoto!", "verification-failed-please-check-endpoint-url": "Verifica fallita, controlla l'URL dell'endpoint", "eigent-cloud-version": "Versione cloud di Eigent", diff --git a/src/i18n/locales/ja/setting.json b/src/i18n/locales/ja/setting.json index 41d6d63b..1f0a07af 100644 --- a/src/i18n/locales/ja/setting.json +++ b/src/i18n/locales/ja/setting.json @@ -42,6 +42,7 @@ "validate-failed": "検証失敗", "copy": "コピー", "copied-to-clipboard": "クリップボードにコピーしました", + "failed-to-copy-to-clipboard": "クリップボードへのコピーに失敗しました", "endpoint-url-can-not-be-empty": "エンドポイントURLは空にできません!", "verification-failed-please-check-endpoint-url": "検証に失敗しました。エンドポイントURLを確認してください", "eigent-cloud-version": "Eigentクラウドバージョン", @@ -110,6 +111,7 @@ "validate-failed": "検証失敗", "copy": "コピー", "copied-to-clipboard": "クリップボードにコピーしました", + "failed-to-copy-to-clipboard": "クリップボードにコピーに失敗しました", "endpoint-url-can-not-be-empty": "エンドポイントURLは空にできません!", "verification-failed-please-check-endpoint-url": "検証に失敗しました。エンドポイントURLを確認してください", "eigent-cloud-version": "Eigentクラウドバージョン", diff --git a/src/i18n/locales/ko/setting.json b/src/i18n/locales/ko/setting.json index ec0df8ea..029e0138 100644 --- a/src/i18n/locales/ko/setting.json +++ b/src/i18n/locales/ko/setting.json @@ -42,6 +42,7 @@ "validate-failed": "유효성 검사 실패", "copy": "복사", "copied-to-clipboard": "클립보드에 복사됨", + "failed-to-copy-to-clipboard": "클립보드로 복사하지 못했습니다", "endpoint-url-can-not-be-empty": "엔드포인트 URL은 비워둘 수 없습니다!", "verification-failed-please-check-endpoint-url": "확인 실패, 엔드포인트 URL을 확인하세요", "eigent-cloud-version": "Eigent 클라우드 버전", @@ -110,6 +111,7 @@ "validate-failed": "유효성 검사 실패", "copy": "복사", "copied-to-clipboard": "클립보드에 복사됨", + "failed-to-copy-to-clipboard": "클립보드에 복사에 실패했습니다", "endpoint-url-can-not-be-empty": "엔드포인트 URL은 비워둘 수 없습니다!", "verification-failed-please-check-endpoint-url": "확인 실패, 엔드포인트 URL을 확인하세요", "eigent-cloud-version": "Eigent 클라우드 버전", diff --git a/src/i18n/locales/ru/setting.json b/src/i18n/locales/ru/setting.json index a96aa1b5..b158677e 100644 --- a/src/i18n/locales/ru/setting.json +++ b/src/i18n/locales/ru/setting.json @@ -42,6 +42,7 @@ "validate-failed": "Проверка не удалась", "copy": "Копировать", "copied-to-clipboard": "Скопировано в буфер обмена", + "failed-to-copy-to-clipboard": "Не удалось скопировать в буфер обмена", "endpoint-url-can-not-be-empty": "URL конечной точки не может быть пустым!", "verification-failed-please-check-endpoint-url": "Проверка не удалась, проверьте URL конечной точки", "eigent-cloud-version": "Eigent Cloud Версия", diff --git a/src/i18n/locales/zh-Hans/setting.json b/src/i18n/locales/zh-Hans/setting.json index 3497d1ea..0c59d252 100644 --- a/src/i18n/locales/zh-Hans/setting.json +++ b/src/i18n/locales/zh-Hans/setting.json @@ -44,6 +44,7 @@ "validate-failed": "验证失败", "copy": "复制", "copied-to-clipboard": "复制到剪贴板", + "failed-to-copy-to-clipboard": "复制到剪贴板失败", "endpoint-url-can-not-be-empty": "Endpoint URL 不能为空!", "verification-failed-please-check-endpoint-url": "验证失败,请检查 Endpoint URL", "eigent-cloud-version": "Eigent 云端版本", diff --git a/src/i18n/locales/zh-Hant/setting.json b/src/i18n/locales/zh-Hant/setting.json index 93b120a2..069735df 100644 --- a/src/i18n/locales/zh-Hant/setting.json +++ b/src/i18n/locales/zh-Hant/setting.json @@ -41,6 +41,7 @@ "validate-failed": "驗證失敗", "copy": "複製", "copied-to-clipboard": "已複製到剪貼板", + "failed-to-copy-to-clipboard": "複製到剪貼板失敗", "endpoint-url-can-not-be-empty": "端點 URL 不可為空!", "verification-failed-please-check-endpoint-url": "驗證失敗,請檢查端點 URL", "eigent-cloud-version": "Eigent 雲端版本",