diff --git a/docs/development/ui-testids-CN.md b/docs/development/ui-testids-CN.md index ab6058e28..d7a80ab07 100644 --- a/docs/development/ui-testids-CN.md +++ b/docs/development/ui-testids-CN.md @@ -159,6 +159,9 @@ | 会话菜单按钮 | `nav-session-menu-btn` | 打开行操作菜单。配合 `data-session-id` 使用。 | | 会话菜单 | `nav-session-menu` | 单个会话的 portal 菜单。配合 `data-session-id` 使用。 | | 会话重命名项 | `nav-session-menu-rename` | 开始重命名会话。 | +| 会话复制 ID 项 | `nav-session-menu-copy-id` | 复制会话 ID。配合 `data-session-id` 使用。 | +| 会话定时任务项 | `nav-session-menu-scheduled-jobs` | 打开该会话的定时任务。配合 `data-session-id` 使用。 | +| 会话归档项 | `nav-session-menu-archive` | 归档会话。配合 `data-session-id` 使用。 | | 会话删除项 | `nav-session-menu-delete` | 删除会话。 | | 会话列表展开按钮 | `nav-session-list-toggle` | 展开/折叠长会话列表。 | diff --git a/docs/development/ui-testids.md b/docs/development/ui-testids.md index 5adc253b0..38697873a 100644 --- a/docs/development/ui-testids.md +++ b/docs/development/ui-testids.md @@ -160,6 +160,9 @@ Avoid adding IDs to these surfaces unless there is a clear automated workflow. | Session menu button | `nav-session-menu-btn` | Opens row action menu. Pair with `data-session-id`. | | Session menu | `nav-session-menu` | Portal menu for one session. Pair with `data-session-id`. | | Session rename item | `nav-session-menu-rename` | Starts session rename. | +| Session copy ID item | `nav-session-menu-copy-id` | Copies the session ID. Pair with `data-session-id`. | +| Session scheduled jobs item | `nav-session-menu-scheduled-jobs` | Opens scheduled jobs for the session. Pair with `data-session-id`. | +| Session archive item | `nav-session-menu-archive` | Archives the session. Pair with `data-session-id`. | | Session delete item | `nav-session-menu-delete` | Deletes session. | | Session list toggle | `nav-session-list-toggle` | Expands/collapses long session lists. | diff --git a/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSection.tsx b/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSection.tsx index 842dc3ad0..561b6e4ac 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSection.tsx +++ b/src/web-ui/src/app/components/NavPanel/sections/sessions/SessionsSection.tsx @@ -7,7 +7,7 @@ import React, { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; -import { Pencil, Trash2, Check, X, Bot, Code2, ClipboardList, Panda, MoreHorizontal, Loader2, Archive, Clock3 } from 'lucide-react'; +import { Pencil, Trash2, Check, X, Bot, Code2, ClipboardList, Panda, MoreHorizontal, Loader2, Archive, Clock3, Copy } from 'lucide-react'; import { IconButton, Input, Tooltip } from '@/component-library'; import { useI18n } from '@/infrastructure/i18n'; import { flowChatStore } from '../../../../../flow_chat/store/FlowChatStore'; @@ -48,6 +48,8 @@ import type { } from '@/flow_chat/utils/backgroundSubagentActivity'; import { computeFixedPopoverPosition } from '@/shared/utils/fixedPopoverViewport'; import { confirmWarning } from '@/component-library/components/ConfirmDialog/confirmService'; +import { notificationService } from '@/shared/notification-system'; +import { copyTextToClipboard } from '@/shared/utils/textSelection'; import { scheduleAfterStartupPaint, scheduleAfterStartupSignal } from '@/shared/utils/startupTaskScheduling'; import { SESSION_METADATA_DEFERRED_FALLBACK_MS, @@ -774,7 +776,7 @@ const SessionsSection: React.FC = ({ } const btn = e.currentTarget as HTMLElement; const rect = btn.getBoundingClientRect(); - const { top, left } = computeFixedPopoverPosition(rect, 160, 96, 4, 8); + const { top, left } = computeFixedPopoverPosition(rect, 160, 120, 4, 8); setSessionMenuPosition({ top, left }); setOpenMenuSessionId(sessionId); }, @@ -811,6 +813,19 @@ const SessionsSection: React.FC = ({ [t] ); + const handleCopySessionId = useCallback( + async (e: React.MouseEvent, sessionId: string) => { + e.stopPropagation(); + const copied = await copyTextToClipboard(sessionId); + if (copied) { + notificationService.success(t('nav.sessions.copySessionIdSuccess'), { duration: 2000 }); + } else { + notificationService.error(t('nav.sessions.copySessionIdFailed'), { duration: 3000 }); + } + }, + [t] + ); + const handleStartEdit = useCallback( (e: React.MouseEvent, session: Session) => { e.stopPropagation(); @@ -1166,6 +1181,8 @@ const SessionsSection: React.FC = ({ ref={openMenuSessionId === session.sessionId ? sessionMenuAnchorRef : undefined} className={`bitfun-nav-panel__inline-item-action-btn${openMenuSessionId === session.sessionId ? ' is-open' : ''}`} onClick={e => handleMenuOpen(e, session.sessionId)} + data-testid="nav-session-menu-btn" + data-session-id={session.sessionId} > @@ -1176,15 +1193,29 @@ const SessionsSection: React.FC = ({ className="bitfun-nav-panel__inline-item-menu-popover" role="menu" style={{ top: `${sessionMenuPosition.top}px`, left: `${sessionMenuPosition.left}px` }} + data-testid="nav-session-menu" + data-session-id={session.sessionId} > +