Skip to content

fix(chat): copy reply excludes thinking and tool calls - #1672

Closed
ArrogHie wants to merge 2 commits into
GCWing:mainfrom
ArrogHie:fix/Agent-reply-copy-repair
Closed

fix(chat): copy reply excludes thinking and tool calls#1672
ArrogHie wants to merge 2 commits into
GCWing:mainfrom
ArrogHie:fix/Agent-reply-copy-repair

Conversation

@ArrogHie

Copy link
Copy Markdown
Contributor

Problem

#1657

Copying an agent's reply copied more than the answer: the clipboard included
the model's thinking blocks, tool call inputs/results, and even the
user's own message. Users expected "copy reply" to yield only the
assistant's actual text.

Root cause: all four copy-extract paths iterated modelRounds[].items and
kept every item type (text / thinking / tool / user-steering), plus
prefixed the user message. Two of them were active:

  1. The round-footer "copy" button — ModelRoundItem.tsx (extractDialogTurnContent)
  2. The right-click context-menu "copy dialog" — FlowChatMenuProvider
    useFlowChatCopyDialog.ts

(The other two, CopyOutputButton.tsx and hooks/useCopyDialog.ts, are dead
code and left untouched per scope.)

Solution

Per the agreed minimal-filter + two-button approach: replace the single
copy action in each active entry point with two actions that only collect
type === 'text' items
, excluding thinking / tool / user-steering /
user-message:

Action Copies
复制全部回复 / Copy All Reply All assistant text items across all model rounds, joined by blank line
复制最终总结 / Copy Final Summary Only the last assistant text item

Changes

  • ModelRoundItem.tsx — footer copy button split into two (Copy icon =
    all reply, FileText icon = final summary); copied state promoted to
    'all' | 'final' | null. Dropped now-dead imports
    (projectEffectiveToolItem, formatSessionViewPreviewText).
  • FlowChatMenuProvider.ts — single "复制整个对话" menu item replaced
    with two items emitting flowchat:copy-dialog with mode: 'all' | 'final'.
  • useFlowChatCopyDialog.ts — listener reads mode; extraction delegates
    to the shared helper. DOM fallback (getElementText) left unchanged per
    minimal scope.
  • collectAssistantText.ts (new) — shared pure helper, used by both
    active paths (avoids duplication; makes the logic unit-testable without
    dragging in the component module's side effects).
  • i18n — added copyAllReply / copyFinalSummary to the contextMenu
    and modelRound sections of en-US, zh-CN, zh-TW. Old copyDialog
    keys retained to avoid dangling references.

Testing

  • New collectAssistantText.test.ts — 6 cases, all passing: all/final
    modes, exclusion of thinking/tool/user-steering, empty-text filtering,
    no-text → empty, default mode.
  • pnpm run type-check:web
  • pnpm run i18n:audit ✅ (0 warnings)
  • pnpm --dir src/web-ui run test:run src/flow_chat/utils/collectAssistantText.test.ts

Known limitation (accepted)

Per the minimal-fix scope, the getElementText DOM fallback in
useFlowChatCopyDialog is unchanged. In the rare case the store cannot
resolve a turn (unhydrated historical session), the fallback still reads the
full dialog DOM (including thinking/tools). This only affects an edge case;
rendered turns come from the store, so normal usage is correct. Can be
hardened later via a DOM-selector fallback if desired.

Checklist

  • Only assistant text is copied (thinking/tool/user excluded)
  • Both copy entry points (footer button + context menu) updated
  • No dead imports introduced
  • i18n keys added to all 3 locales
  • Type-check, i18n audit, and focused unit tests pass

ArrogHie and others added 2 commits July 22, 2026 14:39
Copy-reply actions previously included thinking content, tool calls, and the user message in the clipboard. Replace the single copy action in both active entry points (round footer button and context menu) with two actions that only collect assistant text items:

- Copy All Reply: all text items across rounds

- Copy Final Summary: only the last text item

Extract the text-collection into a shared pure helper (collectAssistantText) to avoid duplication and enable unit testing. DOM fallback left unchanged per minimal-fix scope (known limitation for unhydrated historical turns).

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Verdict: Approve — core logic is correct, well-tested, and all CI checks pass (5/5). The findings below are minor polish items, not blockers.

What's good

  • collectAssistantText.ts correctly filters only type === 'text' items, excluding thinking / tool / user-steering — exactly the fix for #1657.
  • Shared pure helper avoids duplication between the two active entry points (footer button + context menu).
  • 6 unit tests cover all/final modes, exclusion of non-text items, empty-text filtering, no-text → empty, and default mode.
  • mode parameter flows correctly: FlowChatMenuProviderglobalEventBus.emituseFlowChatCopyDialog listener → collectAssistantText.
  • Dead code (useCopyDialog.ts, CopyOutputButton.tsx) correctly left untouched.
  • i18n keys added to all 3 locales (en-US, zh-CN, zh-TW).

Minor items (non-blocking, can be addressed in follow-up)

  1. Second copy button missing refcopyButtonRef is only on the first button ("Copy All Reply"). The mousedown click-outside handler (~L252 in ModelRoundItem.tsx) checks copyButtonRef.current.contains(event.target). When copied is set and you click the second button ("Copy Final Summary"), mousedown fires first and resets copied to null (second button is outside copyButtonRef), then click fires handleCopy('final') which re-sets it. Final state is correct but there's a brief visual flicker (Check icon → icon → Check icon). Fix: wrap both buttons in a container ref, or add a second ref and check both.

  2. Indentation regression in FlowChatMenuProvider.ts — the closing brace of if (dialogTurn && items.length > 0) changed from 4-space to 3-space ( } instead of }).

  3. Unnecessary export on extractAssistantText in useFlowChatCopyDialog.ts — the function is exported but never imported elsewhere. ModelRoundItem.tsx uses its own local callback. Consider removing export.

  4. any typing in collectAssistantText.ts{ modelRounds?: { items?: any[] }[] } could use the existing FlowItem type union for better type safety, though this is consistent with the pre-existing code style.

  5. Dangling i18n keys — old copyDialog keys are retained (intentional per PR description) but no longer referenced by active code. Clean up when dead code is removed.

  6. Join separator changed from '\n\n---\n\n' (round-separated) to '\n\n' (all text items joined). Intentional per PR description, but users who relied on the --- separator will see a different format.

Known limitation (accepted by PR)

DOM fallback getElementText in useFlowChatCopyDialog is unchanged. When the store can't resolve a turn (unhydrated historical session), the fallback still reads the full dialog DOM (including thinking/tools). Only affects an edge case.


Overall a clean, well-scoped fix. Ship it. 🚀

@ArrogHie ArrogHie closed this Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants