diff --git a/desktop/src/__tests__/generalSettings.test.tsx b/desktop/src/__tests__/generalSettings.test.tsx index cf9cecfe..21a7a494 100644 --- a/desktop/src/__tests__/generalSettings.test.tsx +++ b/desktop/src/__tests__/generalSettings.test.tsx @@ -446,6 +446,10 @@ describe('Settings > General tab', () => { expect(clipboardMock.copyTextToClipboard).toHaveBeenCalledWith( 'http://192.168.0.102:3456/?serverUrl=http%3A%2F%2F192.168.0.102%3A3456&h5Token=h5_default_generated_token', ) + expect(useUIStore.getState().toasts[useUIStore.getState().toasts.length - 1]).toMatchObject({ + type: 'success', + message: 'QR link copied.', + }) }) it('guides enabled H5 users to generate a token before the QR code exists', async () => { @@ -517,6 +521,10 @@ describe('Settings > General tab', () => { }) expect(clipboardMock.copyTextToClipboard).toHaveBeenCalledWith('https://phone.example/app') + expect(useUIStore.getState().toasts[useUIStore.getState().toasts.length - 1]).toMatchObject({ + type: 'success', + message: 'H5 URL copied.', + }) }) it('shows the H5-specific store error when the H5 settings load failed', () => { diff --git a/desktop/src/components/workspace/WorkspacePanel.test.tsx b/desktop/src/components/workspace/WorkspacePanel.test.tsx index 5063b41b..334aaa2e 100644 --- a/desktop/src/components/workspace/WorkspacePanel.test.tsx +++ b/desktop/src/components/workspace/WorkspacePanel.test.tsx @@ -245,6 +245,7 @@ import { useSettingsStore } from '../../stores/settingsStore' import { useChatStore } from '../../stores/chatStore' import { useWorkspaceChatContextStore } from '../../stores/workspaceChatContextStore' import { useWorkspacePanelStore } from '../../stores/workspacePanelStore' +import { useUIStore } from '../../stores/uiStore' import { WorkspacePanel } from './WorkspacePanel' describe('WorkspacePanel', () => { @@ -1258,6 +1259,10 @@ describe('WorkspacePanel', () => { expect(execCommand).toHaveBeenCalledWith('copy') }) expect(writeText).toHaveBeenCalledWith('/repo/src/App.tsx') + expect(useUIStore.getState().toasts[useUIStore.getState().toasts.length - 1]).toMatchObject({ + type: 'success', + message: 'Path copied.', + }) } finally { Object.defineProperty(document, 'execCommand', { configurable: true, diff --git a/desktop/src/components/workspace/WorkspacePanel.tsx b/desktop/src/components/workspace/WorkspacePanel.tsx index 020e3bdd..1adfa216 100644 --- a/desktop/src/components/workspace/WorkspacePanel.tsx +++ b/desktop/src/components/workspace/WorkspacePanel.tsx @@ -16,6 +16,7 @@ import { } from '../../stores/workspacePanelStore' import { useChatStore } from '../../stores/chatStore' import { useWorkspaceChatContextStore } from '../../stores/workspaceChatContextStore' +import { useUIStore } from '../../stores/uiStore' import { copyTextToClipboard } from '../chat/clipboard' import { MarkdownRenderer } from '../markdown/MarkdownRenderer' import { @@ -888,6 +889,7 @@ function TreeNode({ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { const t = useTranslation() + const addToast = useUIStore((state) => state.addToast) const [filterQuery, setFilterQuery] = useState('') const [isViewMenuOpen, setIsViewMenuOpen] = useState(false) const [previewTabContextMenu, setPreviewTabContextMenu] = useState<{ tabId: string; x: number; y: number } | null>(null) @@ -1069,8 +1071,12 @@ export function WorkspacePanel({ sessionId }: WorkspacePanelProps) { const copyWorkspacePath = async (path: string) => { const resolvedPath = resolveWorkspaceAttachmentPath(status?.workDir, path) - await copyTextToClipboard(resolvedPath) + const copied = await copyTextToClipboard(resolvedPath) setFileContextMenu(null) + addToast({ + type: copied ? 'success' : 'error', + message: copied ? t('workspace.pathCopied') : t('common.copyFailed'), + }) } const renderChangedView = () => { diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index 66c8f129..35d040c2 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -14,6 +14,7 @@ export const en = { 'common.enable': 'Enable', 'common.disable': 'Disable', 'common.active': 'ACTIVE', + 'common.copyFailed': 'Copy failed.', // ─── Sidebar ────────────────────────────────────── 'sidebar.newSession': 'New session', @@ -92,6 +93,7 @@ export const en = { 'workspace.addToChat': 'Add to chat', 'workspace.addSelectionToChat': 'Add to chat', 'workspace.copyPath': 'Copy path', + 'workspace.pathCopied': 'Path copied.', 'workspace.localComment': 'Local comment', 'workspace.commentLine': 'Comment line {line}', 'workspace.commentLineTarget': 'Line {line}', @@ -760,6 +762,8 @@ export const en = { 'settings.general.h5AccessCopy': 'Copy', 'settings.general.h5AccessCopyUrl': 'Copy H5 URL', 'settings.general.h5AccessCopyLaunchUrl': 'Copy QR link', + 'settings.general.h5AccessUrlCopied': 'H5 URL copied.', + 'settings.general.h5AccessLaunchUrlCopied': 'QR link copied.', 'settings.general.h5AccessHideToken': 'Hide token', 'settings.general.h5AccessTokenNotAvailable': 'Regenerate to show a new token.', 'settings.general.h5AccessPublicUrl': 'Public URL', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index 704e5bc3..89d9347a 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -16,6 +16,7 @@ export const zh: Record = { 'common.enable': '启用', 'common.disable': '禁用', 'common.active': '已激活', + 'common.copyFailed': '复制失败。', // ─── Sidebar ────────────────────────────────────── 'sidebar.newSession': '新建会话', @@ -94,6 +95,7 @@ export const zh: Record = { 'workspace.addToChat': '添加到聊天', 'workspace.addSelectionToChat': '添加至对话', 'workspace.copyPath': '复制路径', + 'workspace.pathCopied': '路径已复制。', 'workspace.localComment': '本地评论', 'workspace.commentLine': '评论第 {line} 行', 'workspace.commentLineTarget': '第 {line} 行', @@ -762,6 +764,8 @@ export const zh: Record = { 'settings.general.h5AccessCopy': '复制', 'settings.general.h5AccessCopyUrl': '复制 H5 链接', 'settings.general.h5AccessCopyLaunchUrl': '复制扫码链接', + 'settings.general.h5AccessUrlCopied': 'H5 链接已复制。', + 'settings.general.h5AccessLaunchUrlCopied': '扫码链接已复制。', 'settings.general.h5AccessHideToken': '隐藏令牌', 'settings.general.h5AccessTokenNotAvailable': '重新生成后可显示新的令牌。', 'settings.general.h5AccessPublicUrl': '公开访问 URL', diff --git a/desktop/src/pages/Settings.tsx b/desktop/src/pages/Settings.tsx index 01eb6e26..a5755c3b 100644 --- a/desktop/src/pages/Settings.tsx +++ b/desktop/src/pages/Settings.tsx @@ -1835,6 +1835,7 @@ function H5AccessSettings() { updateH5AccessSettings, } = useSettingsStore() const t = useTranslation() + const addToast = useUIStore((s) => s.addToast) const [h5PublicBaseUrlDraft, setH5PublicBaseUrlDraft] = useState(h5Access.publicBaseUrl ?? '') const [h5GeneratedToken, setH5GeneratedToken] = useState(null) const [h5TokenVisible, setH5TokenVisible] = useState(false) @@ -1895,12 +1896,20 @@ function H5AccessSettings() { const handleH5UrlCopy = async () => { if (!h5AccessUrl) return - await copyTextToClipboard(h5AccessUrl) + const copied = await copyTextToClipboard(h5AccessUrl) + addToast({ + type: copied ? 'success' : 'error', + message: copied ? t('settings.general.h5AccessUrlCopied') : t('common.copyFailed'), + }) } const handleH5LaunchUrlCopy = async () => { if (!h5LaunchUrl) return - await copyTextToClipboard(h5LaunchUrl) + const copied = await copyTextToClipboard(h5LaunchUrl) + addToast({ + type: copied ? 'success' : 'error', + message: copied ? t('settings.general.h5AccessLaunchUrlCopied') : t('common.copyFailed'), + }) } const handleH5EnableConfirm = async () => {