mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 17:55:26 -04:00
fix(chat): copy only the displayed reply from the message copy buttons (#3731)
The AI-message copy buttons copied dataset.raw, which is the full accumulated model output — still containing the <think time="..."> reasoning block and any tool-call markup that the renderer strips for display. Pasting therefore leaked the model's thinking, and the first heading after </think> lost its markdown formatting because it was glued to the closing tag. Add chatRenderer.copyMessageText(), which mirrors the display pipeline (stripToolBlocks then extractThinkingBlocks) and falls back to the raw text when stripping leaves nothing (thinking-only turns), and route both copy handlers — the message footer and the slash-reply footer — through it. The interrupted-turn Continue flow intentionally keeps reading dataset.raw. Fixes #3722 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -862,6 +862,20 @@ export function stripToolBlocks(text) {
|
||||
return cleaned.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain-text payload for the message copy buttons: the reply as the renderer
|
||||
* displays it — tool blocks and <think> reasoning stripped. dataset.raw keeps
|
||||
* the full model output (chat.js even embeds the elapsed time into the
|
||||
* <think> tag for reload persistence), so copying it verbatim leaks the
|
||||
* thinking block (#3722). Falls back to the raw text when stripping leaves
|
||||
* nothing (e.g. turns interrupted mid-thinking).
|
||||
*/
|
||||
export function copyMessageText(msgElement) {
|
||||
const raw = msgElement.dataset.raw || msgElement.querySelector('.body')?.textContent || '';
|
||||
const { content } = markdownModule.extractThinkingBlocks(stripToolBlocks(raw));
|
||||
return content || raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a collapsible sources box (used by both research and web search).
|
||||
*/
|
||||
@@ -1372,7 +1386,7 @@ export function createMsgFooter(msgElement) {
|
||||
{ id: 'copy', icon: COPY_ICON, title: 'Copy message', cls: 'footer-copy-btn', html: true, handler(e) {
|
||||
e.stopPropagation();
|
||||
const btn = e.currentTarget;
|
||||
uiModule.copyToClipboard(msgElement.dataset.raw || msgElement.querySelector('.body')?.textContent || '');
|
||||
uiModule.copyToClipboard(copyMessageText(msgElement));
|
||||
btn.innerHTML = CHECK_ICON;
|
||||
setTimeout(() => { btn.innerHTML = COPY_ICON; }, 1500);
|
||||
}},
|
||||
@@ -2444,6 +2458,7 @@ const chatRenderer = {
|
||||
updateSessionCostUI,
|
||||
roleTimestamp,
|
||||
stripToolBlocks,
|
||||
copyMessageText,
|
||||
safeToolScreenshotSrc,
|
||||
safeDisplayImageSrc,
|
||||
buildSourcesBox,
|
||||
|
||||
@@ -380,7 +380,7 @@ function _slashFooter(msgEl) {
|
||||
copyBtn.innerHTML = _copySvg;
|
||||
copyBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
uiModule.copyToClipboard(msgEl.dataset.raw || msgEl.querySelector('.body')?.textContent || '');
|
||||
uiModule.copyToClipboard(chatRenderer.copyMessageText(msgEl));
|
||||
copyBtn.innerHTML = _checkSvg;
|
||||
setTimeout(() => { copyBtn.innerHTML = _copySvg; }, 1500);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user