Skip to content

feat(chat): 支持多选消息复制为纯文本 - #937

Merged
AAswordman merged 3 commits into
AAswordman:mainfrom
AikingChoice:feat/multi-select-copy-text
Aug 13, 2026
Merged

feat(chat): 支持多选消息复制为纯文本#937
AAswordman merged 3 commits into
AAswordman:mainfrom
AikingChoice:feat/multi-select-copy-text

Conversation

@AikingChoice

@AikingChoice AikingChoice commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

变更说明 / Description

为聊天多选模式增加“复制文本”操作,使用户可以一次复制连续或不连续的多条消息。

背景与动机 / Context and motivation

当前聊天多选工具栏支持加入记忆、分享和删除,但无法复制选中的消息文本。用户只能退出多选模式后逐条复制。

改动范围 / Changes

  • 在多选消息工具栏增加复制按钮。
  • 按消息在聊天记录中的索引顺序合并内容,不受选择先后顺序影响。
  • 复用现有消息清理、Markdown 纯文本转换和 LaTeX 转换逻辑。
  • 过滤隐藏占位消息、无效索引以及清理后为空的消息。
  • 使用空行分隔消息,并将结果写入系统剪贴板。
  • 复制成功后显示提示并退出多选模式。
  • 调整多选工具栏的窄屏布局。
  • 在点击复制时固定消息快照,避免异步转换期间聊天记录变化导致错位。
  • 修复无结尾换行的单行用户消息在流式 Markdown 解析器中未刷新的问题。
  • 补充消息顺序、文本转换、过滤行为和单行用户消息的测试。

本次仅实现纯文本复制,不包含 Markdown 源码复制、发送者标签和格式配置选项。

兼容性与风险 / Compatibility and risks

不修改数据格式、公开 API 或配置。主要风险集中在 Compose 工具栏布局、异步复制状态和不同 Markdown 内容的复制结果。复制转换在后台调度器执行,并且只在点击时读取一次选中消息快照。

关联 Issue / Related issue

Closes #895

验证方式 / Verification

检查:git diff --check
结果:通过

检查:仓库 hygiene、Markdown links、localizations
结果:通过,0 errors / 0 warnings

测试:MessageCopyTextTest(Debug JVM)
结果:通过,7 tests / 0 failures

构建:.\gradlew.bat :app:assembleDebug --stacktrace --no-daemon
说明:未排除 native 任务,包含 app 与 mnn 的 CMake 配置和构建
结果:BUILD SUCCESSFUL,240 tasks

测试:MarkdownPlainTextRendererAndroidTest
设备:HONOR Magic7 Pro(PTP-AN10),Android 16 / API 36,arm64-v8a
结果:通过,7 tests / 0 failures

测试:仓库 Python 快速测试(WSL)
结果:通过,45 tests / 0 failures

检查:npm.cmd --prefix web-chat run typecheck
结果:通过

测试:真机手动回归
设备:HONOR Magic7 Pro(PTP-AN10),Android 16 / API 36
结果:通过。已验证仅复制用户单行消息、同时复制用户与 AI 消息、连续/非连续选择,以及顺序/逆序/乱序选择;粘贴结果均包含预期消息并保持聊天原始顺序。

上游基线说明 / Upstream baseline note

完整 :app:testDebugUnitTest 在本分支运行 1029 项,其中 1027 项通过,2 项失败:

  • MemoryAnalysisProtocolTest.parseAnalysisResult_acceptsObjectProtocolWithoutMainEvent
  • MemoryAnalysisProtocolTest.parseAnalysisResult_rejectsMissingRequiredOperationField

这两项可在完全未包含本 PR 修改的 upstream/main 独立工作树中复现,并与已合并 #941 的 Candidate checks 结果一致(1026 tests completed, 2 failed)。本 PR 未修改 memory 实现或对应测试,因此属于 base branch 已有失败,不是 #937 引入的回归。

证据 / Evidence

  • 本 PR 定向 JVM 回归:7/7 通过。
  • Android 仪器回归:7/7 通过。
  • 完整 native Debug APK:构建成功。
  • Python 仓库测试:45/45 通过。
  • WebChat TypeScript:typecheck 通过。
  • 真机手动回归:用户消息与 AI 消息均可多选复制,结果保持聊天原始顺序。
  • 上游基线问题:2 项 memory JVM 失败已在未修改的 upstream/mainfeat(memory): rebuild chat memories by window #941 CI 中交叉复现。

检查清单 / Checklist

  • 我已记录可复现验证和上游基线失败
  • 我已确认 Candidate checks 覆盖改动范围,并会处理本 PR 引入的技术失败项
  • 最终源码 diff 无无关、临时、生成、二进制或敏感内容
  • 已提供对应的回归、UI 和兼容性证据

@AAswordman AAswordman left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed the current draft. The selected-message builder preserves chat-list order, excludes hidden placeholders and non-chat senders, and reuses the existing plain-text/LaTeX conversion path. No code blocker found in this pass. This remains a draft with no Candidate checks, so mark it Ready for review and wait for the merge-candidate checks before merge consideration.

@AikingChoice
AikingChoice force-pushed the feat/multi-select-copy-text branch from 2eccc11 to 794364c Compare August 13, 2026 05:38
@AikingChoice
AikingChoice marked this pull request as ready for review August 13, 2026 05:46
@AikingChoice

Copy link
Copy Markdown
Contributor Author

Reviewed the current draft. The selected-message builder preserves chat-list order, excludes hidden placeholders and non-chat senders, and reuses the existing plain-text/LaTeX conversion path. No code blocker found in this pass. This remains a draft with no Candidate checks, so mark it Ready for review and wait for the merge-candidate checks before merge consideration.

Thanks for the review. I’ve marked the PR as Ready and rebased it onto the latest main.

I also fixed an edge case where single-line user messages without a trailing newline were omitted by the native Markdown parser. The updated branch passes 7 JVM tests and 7 instrumentation tests on an Android 16 device, together with manual multi-select copy verification.

The Candidate checks are currently awaiting repository approval to run.

@AikingChoice
AikingChoice marked this pull request as draft August 13, 2026 06:15
@AikingChoice
AikingChoice marked this pull request as ready for review August 13, 2026 06:53
@AikingChoice

Copy link
Copy Markdown
Contributor Author

Ready for review.

Validation for this PR is green: the targeted JVM tests pass (7/7), the Android instrumentation tests pass (7/7) on an Android 16 device, manual multi-select copy verification passes for both user and assistant messages, the full Debug APK build succeeds, the repository Python checks pass (45/45), and the WebChat typecheck passes.

The full :app:testDebugUnitTest suite currently reports two failures in MemoryAnalysisProtocolTest. Both failures reproduce on an unmodified upstream/main checkout and match the two failures from the merged #941 Candidate checks (1026 tests completed, 2 failed). This PR does not modify the memory implementation or those tests, so they are an existing base-branch failure rather than a regression from #937.

@AAswordman AAswordman left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Request changes on the latest head.\n\nP1: The PR description says the click handler captures a message snapshot before background conversion, but messagesToCopy is only a list of references returned by chatHistory::getOrNull. ChatMessage.content is mutable and is updated while an AI response streams. LaunchedEffect(currentChatId, messageOrder) keys only on timestamps, so a content-only streaming update neither cancels this job nor refreshes the captured list. The background conversion can therefore copy a different, partially updated message than the user selected at click time.\n\nCapture immutable content at click time, for example by copying each selected message's content into an immutable value object or a ChatMessage.copy(content = message.content) snapshot, and add a regression test that mutates the source message after capture but before conversion.\n\nThe single-line Markdown parser fix and its instrumentation coverage look appropriate. Candidate checks still need to run on the current merge candidate after this issue is fixed.

@AAswordman AAswordman left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

按功能和界面位置复核后通过。复制按钮放在多选工具栏的加入记忆与分享操作之间,位置清晰;选中消息后启用,未选中时禁用;窄屏工具栏支持横向滚动,按钮仍可访问。复制结果按聊天原始顺序合并,并复用现有 Markdown/LaTeX 纯文本转换。\n\n消息内容快照在流式更新期间可能变化属于小概率非阻断问题,不影响本次功能合入判断。当前仍需等待/运行 Candidate checks 后再合并。

@AAswordman
AAswordman merged commit e2fb823 into AAswordman:main Aug 13, 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.

[Feature] 多选消息工具栏增加“复制文本”功能

2 participants