mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
Merge #810: a Copy Markdown button in the response viewer
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
commit
b4e44c4f6a
9 files changed
+405
-11
No files matched your search
+51
-6
@@ -774,6 +774,7 @@ class CodemanApp {
|
||||
this.terminal = null;
|
||||
this.fitAddon = null;
|
||||
this.activeSessionId = null;
|
||||
this._responseViewerMarkdown = '';
|
||||
|
||||
// ── Session detach / undock (beta) ───────────────────────────────────
|
||||
// A "solo window" is a popped-out browser window showing exactly one
|
||||
@@ -2948,12 +2949,7 @@ class CodemanApp {
|
||||
ev.stopPropagation();
|
||||
const code = copyBtn.closest('.rv-code-wrap')?.querySelector('pre code');
|
||||
const ok = code ? await this._copyText(code.textContent || '') : false;
|
||||
copyBtn.classList.remove('rv-copied', 'rv-copy-failed');
|
||||
copyBtn.classList.add(ok ? 'rv-copied' : 'rv-copy-failed');
|
||||
clearTimeout(copyBtn._resetTimer);
|
||||
copyBtn._resetTimer = setTimeout(() => {
|
||||
copyBtn.classList.remove('rv-copied', 'rv-copy-failed');
|
||||
}, 1500);
|
||||
this._setCopyControlState(copyBtn, ok);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2997,6 +2993,50 @@ class CodemanApp {
|
||||
}
|
||||
}
|
||||
|
||||
_setCopyControlState(btn, success) {
|
||||
if (!btn) return;
|
||||
btn.classList.remove('rv-copied', 'rv-copy-failed');
|
||||
btn.classList.add(success ? 'rv-copied' : 'rv-copy-failed');
|
||||
clearTimeout(btn._resetTimer);
|
||||
btn._resetTimer = setTimeout(() => {
|
||||
btn.classList.remove('rv-copied', 'rv-copy-failed');
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw Markdown of the newest answer in a message list: every row of its
|
||||
* turn, or the last row when the reader emits no numeric turns (Codex, the
|
||||
* pane parser). Tool and status blocks share the assistant role but are not
|
||||
* the answer.
|
||||
*/
|
||||
_responseViewerMarkdownFrom(messages) {
|
||||
const answers = messages.filter(
|
||||
(msg) => msg && msg.role === 'assistant' && (!msg.kind || msg.kind === 'response') && msg.text?.trim()
|
||||
);
|
||||
const last = answers[answers.length - 1];
|
||||
if (!last) return '';
|
||||
if (typeof last.turn !== 'number') return last.text;
|
||||
return answers
|
||||
.filter((msg) => msg.turn === last.turn)
|
||||
.map((msg) => msg.text)
|
||||
.join('\n\n');
|
||||
}
|
||||
|
||||
/** Copy the raw Markdown source of the answer the response viewer is showing. */
|
||||
async copyResponseViewerMarkdown() {
|
||||
const button = document.getElementById('responseViewerCopyMarkdown');
|
||||
const markdown = this._responseViewerMarkdown || '';
|
||||
if (!markdown) {
|
||||
this._setCopyControlState(button, false);
|
||||
this.showToast('No response content to copy', 'warning');
|
||||
return;
|
||||
}
|
||||
const copied = await this._copyText(markdown);
|
||||
this._setCopyControlState(button, copied);
|
||||
if (copied) this.showToast('Copied response as Markdown', 'success');
|
||||
else this.showToast('Failed to copy response as Markdown', 'error');
|
||||
}
|
||||
|
||||
/** Build one response-viewer message so the brief and full views share markup and CSS. */
|
||||
_buildResponseViewerMessage(text, role, agentLabel, meta) {
|
||||
const div = document.createElement('div');
|
||||
@@ -3241,6 +3281,8 @@ class CodemanApp {
|
||||
}
|
||||
}
|
||||
|
||||
this._responseViewerMarkdown = this._responseViewerMarkdownFrom(turnMessages) || lastResponse;
|
||||
|
||||
const body = document.getElementById('responseViewerBody');
|
||||
if (turnMessages.length > 0) {
|
||||
// The whole last turn, rendered exactly as the full view renders that
|
||||
@@ -3320,6 +3362,9 @@ class CodemanApp {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep the brief view's answer when this history holds none of its own.
|
||||
this._responseViewerMarkdown = this._responseViewerMarkdownFrom(messages) || this._responseViewerMarkdown;
|
||||
|
||||
// Render conversation thread
|
||||
const agentLabel = this._getResponseViewerAgentLabel();
|
||||
body.innerHTML = '';
|
||||
|
||||
@@ -245,6 +245,11 @@
|
||||
'No full conversation history available for this session': '此会话没有可显示的完整对话历史',
|
||||
'Last Response': '最近一次回复',
|
||||
More: '更多',
|
||||
'Copy Markdown': '复制 Markdown',
|
||||
'Copy response as Markdown': '将回复复制为 Markdown',
|
||||
'Copied response as Markdown': '已将回复复制为 Markdown',
|
||||
'Failed to copy response as Markdown': '无法将回复复制为 Markdown',
|
||||
'No response content to copy': '没有可复制的回复内容',
|
||||
'Codeman version': '{name}版本',
|
||||
Stop: '停止',
|
||||
Watching: '监视中',
|
||||
|
||||
@@ -733,6 +733,7 @@
|
||||
<span id="responseViewerTitle">Last Response</span>
|
||||
<div class="response-viewer-actions">
|
||||
<button class="response-viewer-more" id="responseViewerMore" onclick="app.loadFullContext()">More</button>
|
||||
<button class="response-viewer-more response-viewer-copy-markdown" id="responseViewerCopyMarkdown" type="button" onclick="app.copyResponseViewerMarkdown()" title="Copy response as Markdown" aria-label="Copy response as Markdown">Copy Markdown</button>
|
||||
<button class="response-viewer-close" onclick="app.toggleResponseViewer()">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1804,6 +1804,27 @@ html.mobile-init .file-browser-panel {
|
||||
max-height: 92dvh;
|
||||
}
|
||||
|
||||
.response-viewer-header {
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.response-viewer-header > span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.response-viewer-actions {
|
||||
flex-shrink: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.response-viewer-more {
|
||||
padding: 3px 7px;
|
||||
}
|
||||
|
||||
.response-viewer-body {
|
||||
font-size: 14.5px;
|
||||
line-height: 1.65;
|
||||
|
||||
@@ -14946,6 +14946,20 @@ html:is([data-skin="paper-gray"], [data-skin="solarized-light"], [data-skin="cat
|
||||
background: var(--control-bg-hover);
|
||||
}
|
||||
|
||||
.response-viewer-copy-markdown {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.response-viewer-copy-markdown.rv-copied {
|
||||
color: var(--green);
|
||||
border-color: var(--green);
|
||||
}
|
||||
|
||||
.response-viewer-copy-markdown.rv-copy-failed {
|
||||
color: var(--red);
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
||||
.response-viewer-close {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
Reference in new issue
Block a user