Claude: Support downloading the conversation history as PNG

Using the html2canvas-pro library
This commit is contained in:
Alessandro Pignotti 2025-03-04 10:07:11 +01:00
parent 680df7ace1
commit b0f9249b0b
4 changed files with 68 additions and 2 deletions

50
package-lock.json generated
View File

@ -20,6 +20,7 @@
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"autoprefixer": "^10.4.20",
"html2canvas-pro": "^1.5.8",
"labs": "git@github.com:leaningtech/labs.git",
"node-html-parser": "^6.1.13",
"postcss": "^8.4.47",
@ -1119,6 +1120,15 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"dev": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -1355,6 +1365,15 @@
"node": ">= 8"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"dev": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
@ -1960,6 +1979,19 @@
"he": "bin/he"
}
},
"node_modules/html2canvas-pro": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-1.5.8.tgz",
"integrity": "sha512-bVGAU7IvhBwBlRAmX6QhekX8lsaxmYoF6zIwf/HNlHscjx+KN8jw/U4PQRYqeEVm9+m13hcS1l5ChJB9/e29Lw==",
"dev": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
@ -3059,6 +3091,15 @@
"node": ">=14.0.0"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"dev": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -3164,6 +3205,15 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"dev": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/vite": {
"version": "5.4.14",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",

View File

@ -7,6 +7,7 @@
"build": "vite build"
},
"devDependencies": {
"@anthropic-ai/sdk": "^0.33.0",
"@fortawesome/fontawesome-free": "^6.6.0",
"@leaningtech/cheerpx": "latest",
"@oddbird/popover-polyfill": "^0.4.4",
@ -26,7 +27,7 @@
"tailwindcss": "^3.4.9",
"vite": "^5.0.3",
"vite-plugin-static-copy": "^1.0.6",
"@anthropic-ai/sdk": "^0.33.0"
"html2canvas-pro": "^1.5.8"
},
"type": "module"
}

View File

@ -25,6 +25,7 @@ export default {
case '.fa-hourglass-half:before':
case '.fa-hand:before':
case '.fa-brain:before':
case '.fa-download:before':
case '.fa-keyboard:before':
case '.fa-brands:before':
case '.fa-solid:before':

View File

@ -5,6 +5,7 @@
import PanelButton from './PanelButton.svelte';
import SmallButton from './SmallButton.svelte';
import { aiActivity } from './activities.js';
import html2canvas from 'html2canvas-pro';
export let handleTool;
let stopRequested = false;
function handleKeyEnter(e)
@ -112,6 +113,18 @@
stopRequested = false;
}
async function handleDownload() {
const messageListElement = document.getElementById('message-list');
// Temporarily add padding and background for the list
messageListElement.classList.add("p-1");
const canvas = await html2canvas(messageListElement);
messageListElement.classList.remove("p-1");
const link = document.createElement('a');
link.href = canvas.toDataURL('image/png');
link.download = 'WebVM_Claude.png';
link.click();
}
function toggleThinkingMode() {
enableThinking.set(!get(enableThinking));
}
@ -122,12 +135,13 @@
<div class="flex grow flex-col overflow-y-hidden gap-2">
<p class="flex flex-row gap-2">
<span class="mr-auto flex items-center">Conversation history</span>
<SmallButton buttonIcon="fa-solid fa-download" clickHandler={handleDownload} buttonTooltip="Save conversation as image"></SmallButton>
<SmallButton buttonIcon="fa-solid fa-brain" clickHandler={toggleThinkingMode} buttonTooltip="{$enableThinking ? "Disable" : "Enable"} thinking mode" bgColor={$enableThinking ? "bg-neutral-500" : "bg-neutral-700"}></SmallButton>
<SmallButton buttonIcon="fa-solid fa-trash-can" clickHandler={clearMessageHistory} buttonTooltip="Clear conversation history"></SmallButton>
</p>
<div class="flex grow overflow-y-scroll scrollbar" use:scrollMessage={$messageList}>
<div class="h-full w-full">
<div class="w-full min-h-full flex flex-col gap-2 justify-end">
<div class="w-full min-h-full flex flex-col gap-2 justify-end bg-neutral-600" id="message-list">
{#each $messageList as msg}
{@const details = getMessageDetails(msg)}
{#if details.isToolUse}