diff --git a/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.module.css b/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.module.css index 24bc4c0e49..d988cf52f8 100644 --- a/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.module.css +++ b/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.module.css @@ -28,55 +28,17 @@ line-height: 18px; } -/* Finalized footer: copy / branch / clock (figma 43:32997). */ +/* Finalized footer offset (figma 43:32997); chrome lives in MessageIconActions. */ .actions { - display: flex; - align-items: center; - gap: 10px; - height: 28px; margin-top: 16px; /* Optical align with 28px icon hit targets that pad 6px past the glyph. */ margin-left: -6px; } -/* Clock after the icon buttons; pl 12 separates it from branch. */ -.time { - padding-left: 12px; - font-size: 14px; - line-height: 24px; - color: var(--dsw-alias-label-tertiary); - white-space: nowrap; -} - -/* Hover-capable pointers: hide until the root is hovered/focused. Touch / - hover:none keeps actions visible (opacity:0 still hit-tests). */ +/* Hover-capable pointers: reveal shared actions on root hover/focus. */ @media (hover: hover) { - .actions { - opacity: 0; - transition: opacity var(--ds-transition-duration) var(--ds-ease-in-out); - } - .root:hover .actions, .root:focus-within .actions { opacity: 1; } } - -.action { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 6px; - border: none; - border-radius: 28px; - background: transparent; - color: var(--dsw-alias-label-tertiary); - cursor: pointer; -} - -.action:hover { - background: var(--dsw-alias-interactive-bg-hover); - color: var(--dsw-alias-label-secondary); -} diff --git a/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx b/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx index a3a05b7af9..e5a89a9e88 100644 --- a/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx +++ b/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx @@ -6,14 +6,12 @@ // the turn-level loading dots live in the chat view's tail, not here. // Finalized nodes append IconActions (copy / branch / clock) once streaming ends. -import { memo, useCallback } from 'react' +import { memo } from 'react' import type { AssistantBlock } from '@deepseek-ai/dsh-client-runtime/client' import { - IconBranchOutline16, IconCopyOutline16, IconThinkOutline14, - JsonBlock, MarkdownText, Tooltip, + IconThinkOutline14, JsonBlock, MarkdownText, } from '@deepseek-ai/dsh-client-ui-primitives' -import { formatMessageClock, writeClipboard } from './message-chrome.ts' -import { useCalendarDay } from './use-calendar-day.ts' +import { MessageIconActions } from './MessageIconActions.tsx' import { ToolRow } from './ToolRow.tsx' import css from './AssistantMarkdown.module.css' @@ -55,29 +53,6 @@ function ThinkRow({ text, running }: { text: string; running: boolean }) { ) } -/** Finalized assistant IconActions (figma 43:32997): copy live; branch stub; clock. */ -function AssistantActions({ text, time }: { text: string; time: number }) { - const day = useCalendarDay() - const onCopy = useCallback(() => { - void writeClipboard(text) - }, [text]) - return ( -
- - - - - - - {formatMessageClock(time, day)} -
- ) -} - export const AssistantMarkdown = memo(function AssistantMarkdown({ blocks, streaming, interrupted, time, }: AssistantMarkdownProps) { @@ -105,7 +80,14 @@ export const AssistantMarkdown = memo(function AssistantMarkdown({ })} {interrupted && 已停止} - {showActions && } + {showActions && ( + + )} ) }) diff --git a/packages/client/ui-conversation/src/client/chat/MessageIconActions.module.css b/packages/client/ui-conversation/src/client/chat/MessageIconActions.module.css new file mode 100644 index 0000000000..30d6920609 --- /dev/null +++ b/packages/client/ui-conversation/src/client/chat/MessageIconActions.module.css @@ -0,0 +1,53 @@ +/* Shared message IconActions row (user + assistant). Parent modules own + hover-reveal selectors and layout offsets via the composed className. */ + +.actions { + display: flex; + align-items: center; + gap: 10px; + height: 28px; +} + +/* Clock before icons (user figma 388:20051) / after (assistant 43:32997). */ +.timeStart { + padding-right: 12px; + font-size: 14px; + line-height: 24px; + color: var(--dsw-alias-label-tertiary); + white-space: nowrap; +} + +.timeEnd { + padding-left: 12px; + font-size: 14px; + line-height: 24px; + color: var(--dsw-alias-label-tertiary); + white-space: nowrap; +} + +/* Hover-capable pointers: hide until a parent hover/focus rule reveals. */ +@media (hover: hover) { + .actions { + opacity: 0; + transition: opacity var(--ds-transition-duration) var(--ds-ease-in-out); + } +} + +.action { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 6px; + border: none; + border-radius: 28px; + background: transparent; + color: var(--dsw-alias-label-tertiary); + cursor: pointer; +} + +.action:hover { + background: var(--dsw-alias-interactive-bg-hover); + color: var(--dsw-alias-label-secondary); +} diff --git a/packages/client/ui-conversation/src/client/chat/MessageIconActions.tsx b/packages/client/ui-conversation/src/client/chat/MessageIconActions.tsx new file mode 100644 index 0000000000..7579a4c249 --- /dev/null +++ b/packages/client/ui-conversation/src/client/chat/MessageIconActions.tsx @@ -0,0 +1,65 @@ +// Shared IconActions chrome for user and assistant messages: copy / branch +// live (branch still a stub), date-aware clock, optional edit stub. + +import { useCallback } from 'react' +import { + IconBranchOutline16, IconCopyOutline16, IconEditOutline16, Tooltip, +} from '@deepseek-ai/dsh-client-ui-primitives' +import { formatMessageClock, writeClipboard } from './message-chrome.ts' +import { useCalendarDay } from './use-calendar-day.ts' +import css from './MessageIconActions.module.css' + +export interface MessageIconActionsProps { + /** Plain text the copy action writes. */ + text: string + /** Unix epoch ms for the clock label. */ + time: number + /** Clock before icons (user) or after (assistant). */ + clock: 'start' | 'end' + /** When true, append the stub edit control (user bubble). */ + edit?: boolean | undefined + /** Parent layout / hover-reveal class composed onto the actions row. */ + className?: string | undefined +} + +/** + * Copy / branch (/ clock) IconActions row shared by user and assistant chrome. + * @param props - Copy text, event time, clock side, optional edit, className. + * @returns The actions row element. + */ +export function MessageIconActions({ + text, time, clock, edit, className, +}: MessageIconActionsProps) { + const day = useCalendarDay() + const onCopy = useCallback(() => { + void writeClipboard(text) + }, [text]) + const clockEl = ( + + {formatMessageClock(time, day)} + + ) + return ( +
+ {clock === 'start' ? clockEl : null} + + + + + + + {edit === true && ( + + + + )} + {clock === 'end' ? clockEl : null} +
+ ) +} diff --git a/packages/client/ui-conversation/src/client/chat/MessageItem.module.css b/packages/client/ui-conversation/src/client/chat/MessageItem.module.css index a2c14fdc7a..260382d530 100644 --- a/packages/client/ui-conversation/src/client/chat/MessageItem.module.css +++ b/packages/client/ui-conversation/src/client/chat/MessageItem.module.css @@ -20,55 +20,14 @@ color: var(--dsw-alias-label-primary); } -.actions { - display: flex; - align-items: center; - gap: 10px; - height: 28px; -} - -/* Clock before the icon buttons (figma 388:20051); pr 12 separates it from copy. */ -.time { - padding-right: 12px; - font-size: 14px; - line-height: 24px; - color: var(--dsw-alias-label-tertiary); - white-space: nowrap; -} - -/* Hover-capable pointers: hide until the row is hovered/focused. Touch / - hover:none keeps actions visible (opacity:0 still hit-tests). */ +/* Hover-capable pointers: reveal shared MessageIconActions on row hover/focus. */ @media (hover: hover) { - .actions { - opacity: 0; - transition: opacity var(--ds-transition-duration) var(--ds-ease-in-out); - } - .userRow:hover .actions, .userRow:focus-within .actions { opacity: 1; } } -.action { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 6px; - border: none; - border-radius: 28px; - background: transparent; - color: var(--dsw-alias-label-tertiary); - cursor: pointer; -} - -.action:hover { - background: var(--dsw-alias-interactive-bg-hover); - color: var(--dsw-alias-label-secondary); -} - .badge { display: inline-block; margin-bottom: 4px; diff --git a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx index acd2a7023f..a149d37337 100644 --- a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx +++ b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx @@ -4,17 +4,13 @@ // the snapshot cache; memo holds across streaming because unchanged nodes // keep their references. -import { memo, useCallback } from 'react' +import { memo } from 'react' import type { ReactNode } from 'react' import type { ContextMessageNode, SteeringMessageNode, UnknownSurfaceNode, UserMessageNode, } from '@deepseek-ai/dsh-client-runtime/client' -import { - IconBranchOutline16, IconCopyOutline16, IconEditOutline16, - JsonBlock, MessageText, Tooltip, -} from '@deepseek-ai/dsh-client-ui-primitives' -import { formatMessageClock, writeClipboard } from './message-chrome.ts' -import { useCalendarDay } from './use-calendar-day.ts' +import { JsonBlock, MessageText } from '@deepseek-ai/dsh-client-ui-primitives' +import { MessageIconActions } from './MessageIconActions.tsx' import css from './MessageItem.module.css' export interface MessageItemProps { @@ -64,34 +60,6 @@ function projectUserText(text: string): ReactNode { return <>{parts} } -/** User-bubble IconActions (figma 388:20051): clock + copy live; branch/edit stubs. */ -function UserActions({ text, time }: { text: string; time: number }) { - const day = useCalendarDay() - const onCopy = useCallback(() => { - void writeClipboard(text) - }, [text]) - return ( -
- {formatMessageClock(time, day)} - - - - - - - - - -
- ) -} - export const MessageItem = memo(function MessageItem({ node }: MessageItemProps) { switch (node.kind) { case 'user': { @@ -102,7 +70,13 @@ export const MessageItem = memo(function MessageItem({ node }: MessageItemProps) {projectUserText(text)} {rest.map((block, i) => )} - + ) }