Browse Source

Claude: Support downloading the conversation history as PNG

Using the html2canvas-pro library
Alessandro Pignotti 4 months ago
parent
commit
b0f9249b0b
4 changed files with 68 additions and 2 deletions
  1. 50 0
      package-lock.json
  2. 2 1
      package.json
  3. 1 0
      postcss.config.js
  4. 15 1
      src/lib/AnthropicTab.svelte

+ 50 - 0
package-lock.json

@@ -20,6 +20,7 @@
 				"@xterm/addon-web-links": "^0.11.0",
 				"@xterm/addon-web-links": "^0.11.0",
 				"@xterm/xterm": "^5.5.0",
 				"@xterm/xterm": "^5.5.0",
 				"autoprefixer": "^10.4.20",
 				"autoprefixer": "^10.4.20",
+				"html2canvas-pro": "^1.5.8",
 				"labs": "git@github.com:leaningtech/labs.git",
 				"labs": "git@github.com:leaningtech/labs.git",
 				"node-html-parser": "^6.1.13",
 				"node-html-parser": "^6.1.13",
 				"postcss": "^8.4.47",
 				"postcss": "^8.4.47",
@@ -1119,6 +1120,15 @@
 			"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
 			"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
 			"dev": true
 			"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": {
 		"node_modules/binary-extensions": {
 			"version": "2.3.0",
 			"version": "2.3.0",
 			"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
 			"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -1355,6 +1365,15 @@
 				"node": ">= 8"
 				"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": {
 		"node_modules/css-select": {
 			"version": "5.1.0",
 			"version": "5.1.0",
 			"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
 			"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
@@ -1960,6 +1979,19 @@
 				"he": "bin/he"
 				"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": {
 		"node_modules/humanize-ms": {
 			"version": "1.2.1",
 			"version": "1.2.1",
 			"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
 			"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
@@ -3059,6 +3091,15 @@
 				"node": ">=14.0.0"
 				"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": {
 		"node_modules/thenify": {
 			"version": "3.3.1",
 			"version": "3.3.1",
 			"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
 			"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -3164,6 +3205,15 @@
 			"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
 			"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
 			"dev": true
 			"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": {
 		"node_modules/vite": {
 			"version": "5.4.14",
 			"version": "5.4.14",
 			"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
 			"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",

+ 2 - 1
package.json

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

+ 1 - 0
postcss.config.js

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

+ 15 - 1
src/lib/AnthropicTab.svelte

@@ -5,6 +5,7 @@
 	import PanelButton from './PanelButton.svelte';
 	import PanelButton from './PanelButton.svelte';
 	import SmallButton from './SmallButton.svelte';
 	import SmallButton from './SmallButton.svelte';
 	import { aiActivity } from './activities.js';
 	import { aiActivity } from './activities.js';
+	import html2canvas from 'html2canvas-pro';
 	export let handleTool;
 	export let handleTool;
 	let stopRequested = false;
 	let stopRequested = false;
 	function handleKeyEnter(e)
 	function handleKeyEnter(e)
@@ -112,6 +113,18 @@
 		stopRequested = false;
 		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() {
 	function toggleThinkingMode() {
 		enableThinking.set(!get(enableThinking));
 		enableThinking.set(!get(enableThinking));
 	}
 	}
@@ -122,12 +135,13 @@
 <div class="flex grow flex-col overflow-y-hidden gap-2">
 <div class="flex grow flex-col overflow-y-hidden gap-2">
 	<p class="flex flex-row gap-2">
 	<p class="flex flex-row gap-2">
 		<span class="mr-auto flex items-center">Conversation history</span>
 		<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-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>
 		<SmallButton buttonIcon="fa-solid fa-trash-can" clickHandler={clearMessageHistory} buttonTooltip="Clear conversation history"></SmallButton>
 	</p>
 	</p>
 	<div class="flex grow overflow-y-scroll scrollbar" use:scrollMessage={$messageList}>
 	<div class="flex grow overflow-y-scroll scrollbar" use:scrollMessage={$messageList}>
 		<div class="h-full w-full">
 		<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}
 				{#each $messageList as msg}
 					{@const details = getMessageDetails(msg)}
 					{@const details = getMessageDetails(msg)}
 					{#if details.isToolUse}
 					{#if details.isToolUse}