mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-23 12:44:45 +00:00
Co-authored-by: bytecii <994513625@qq.com>
This commit is contained in:
parent
f93643e93f
commit
167d112e04
14 changed files with 99 additions and 18 deletions
|
|
@ -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 (
|
||||
<div
|
||||
|
|
@ -69,7 +83,11 @@ export function AgentMessageCard({
|
|||
>
|
||||
<div className="absolute bottom-[0px] right-1 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Button onClick={handleCopy} variant="ghost" size="icon">
|
||||
<Copy />
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-text-success" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<MarkDown
|
||||
|
|
|
|||
|
|
@ -13,8 +13,12 @@
|
|||
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Copy } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Check, Copy } from 'lucide-react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const COPIED_RESET_MS = 2000;
|
||||
|
||||
interface FeedbackCardProps {
|
||||
id: string;
|
||||
|
|
@ -34,10 +38,34 @@ export function FeedbackCard({
|
|||
className,
|
||||
}: FeedbackCardProps) {
|
||||
const [_isHovered, setIsHovered] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const timeoutRef = useRef<number | null>(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 (
|
||||
<div
|
||||
|
|
@ -49,7 +77,11 @@ export function FeedbackCard({
|
|||
{/* Copy button - appears on hover */}
|
||||
<div className="absolute bottom-1 right-1 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Button onClick={handleCopy} variant="ghost" size="icon">
|
||||
<Copy className="h-4 w-4" />
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-text-success" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string | null>(null);
|
||||
const [isRemainingOpen, setIsRemainingOpen] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const hoverCloseTimerRef = useRef<number | null>(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({
|
|||
>
|
||||
<div className="absolute bottom-[0px] right-1 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<Button onClick={handleCopy} variant="ghost" size="icon">
|
||||
<Copy />
|
||||
{copied ? (
|
||||
<Check className="h-4 w-4 text-text-success" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap break-words text-body-sm text-text-body">
|
||||
|
|
|
|||
|
|
@ -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": "إصدار أيجنت السحابي",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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クラウドバージョン",
|
||||
|
|
|
|||
|
|
@ -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 클라우드 버전",
|
||||
|
|
|
|||
|
|
@ -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 Версия",
|
||||
|
|
|
|||
|
|
@ -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 云端版本",
|
||||
|
|
|
|||
|
|
@ -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 雲端版本",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue