From 07adeab0367bcee2cecfc2e4df84fe4c593214f1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:41:11 +0800 Subject: [PATCH] feat: Add copy button for tool names in server tool list (#435) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com> --- frontend/src/components/ui/ToolCard.tsx | 53 ++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ui/ToolCard.tsx b/frontend/src/components/ui/ToolCard.tsx index 3ed0d5c..31c20fb 100644 --- a/frontend/src/components/ui/ToolCard.tsx +++ b/frontend/src/components/ui/ToolCard.tsx @@ -1,9 +1,10 @@ import { useState, useCallback, useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Tool } from '@/types' -import { ChevronDown, ChevronRight, Play, Loader, Edit, Check } from '@/components/icons/LucideIcons' +import { ChevronDown, ChevronRight, Play, Loader, Edit, Check, Copy } from '@/components/icons/LucideIcons' import { callTool, ToolCallResult, updateToolDescription } from '@/services/toolService' import { useSettingsData } from '@/hooks/useSettingsData' +import { useToast } from '@/contexts/ToastContext' import { Switch } from './ToggleGroup' import DynamicForm from './DynamicForm' import ToolResult from './ToolResult' @@ -26,6 +27,7 @@ function isEmptyValue(value: any): boolean { const ToolCard = ({ tool, server, onToggle, onDescriptionUpdate }: ToolCardProps) => { const { t } = useTranslation() + const { showToast } = useToast() const { nameSeparator } = useSettingsData() const [isExpanded, setIsExpanded] = useState(false) const [showRunForm, setShowRunForm] = useState(false) @@ -36,6 +38,7 @@ const ToolCard = ({ tool, server, onToggle, onDescriptionUpdate }: ToolCardProps const descriptionInputRef = useRef(null) const descriptionTextRef = useRef(null) const [textWidth, setTextWidth] = useState(0) + const [copiedToolName, setCopiedToolName] = useState(false) // Focus the input when editing mode is activated useEffect(() => { @@ -108,6 +111,41 @@ const ToolCard = ({ tool, server, onToggle, onDescriptionUpdate }: ToolCardProps } } + const handleCopyToolName = async (e: React.MouseEvent) => { + e.stopPropagation() + + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(tool.name) + setCopiedToolName(true) + showToast(t('common.copySuccess'), 'success') + setTimeout(() => setCopiedToolName(false), 2000) + } else { + // Fallback for HTTP or unsupported clipboard API + const textArea = document.createElement('textarea') + textArea.value = tool.name + textArea.style.position = 'fixed' + textArea.style.left = '-9999px' + document.body.appendChild(textArea) + textArea.focus() + textArea.select() + try { + document.execCommand('copy') + setCopiedToolName(true) + showToast(t('common.copySuccess'), 'success') + setTimeout(() => setCopiedToolName(false), 2000) + } catch (err) { + showToast(t('common.copyFailed'), 'error') + console.error('Copy to clipboard failed:', err) + } + document.body.removeChild(textArea) + } + } catch (error) { + showToast(t('common.copyFailed'), 'error') + console.error('Copy to clipboard failed:', error) + } + } + const handleRunTool = async (arguments_: Record) => { setIsRunning(true) try { @@ -149,8 +187,19 @@ const ToolCard = ({ tool, server, onToggle, onDescriptionUpdate }: ToolCardProps onClick={() => setIsExpanded(!isExpanded)} >
-

+

{tool.name.replace(server + nameSeparator, '')} + {isEditingDescription ? ( <>