diff --git a/package-lock.json b/package-lock.json index 6a7fdeb2c..5bedd09d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "marked": "^9.1.0", "mermaid": "^10.9.1", "paneforge": "^0.0.6", + "panzoom": "^9.4.3", "pyodide": "^0.26.1", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.2", @@ -2435,6 +2436,14 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/amator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz", + "integrity": "sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==", + "dependencies": { + "bezier-easing": "^2.0.3" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -2725,6 +2734,11 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -7178,6 +7192,11 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/ngraph.events": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.2.tgz", + "integrity": "sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ==" + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -7375,6 +7394,16 @@ "svelte": "^4.0.0 || ^5.0.0-next.1" } }, + "node_modules/panzoom": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz", + "integrity": "sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==", + "dependencies": { + "amator": "^1.1.0", + "ngraph.events": "^1.2.2", + "wheel": "^1.0.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10481,6 +10510,11 @@ "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" }, + "node_modules/wheel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz", + "integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index a56392f4a..3acfcd775 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "marked": "^9.1.0", "mermaid": "^10.9.1", "paneforge": "^0.0.6", + "panzoom": "^9.4.3", "pyodide": "^0.26.1", "socket.io-client": "^4.2.0", "sortablejs": "^1.15.2", diff --git a/src/lib/components/chat/Artifacts.svelte b/src/lib/components/chat/Artifacts.svelte index c6b702210..ec96e900a 100644 --- a/src/lib/components/chat/Artifacts.svelte +++ b/src/lib/components/chat/Artifacts.svelte @@ -9,12 +9,13 @@ import { copyToClipboard, createMessagesList } from '$lib/utils'; import ArrowsPointingOut from '../icons/ArrowsPointingOut.svelte'; import Tooltip from '../common/Tooltip.svelte'; + import SvgPanZoom from '../common/SVGPanZoom.svelte'; export let overlay = false; export let history; let messages = []; - let contents: Array<{ content: string }> = []; + let contents: Array<{ type: string; content: string }> = []; let selectedContentIdx = 0; let copied = false; @@ -32,24 +33,32 @@ contents = []; messages.forEach((message) => { if (message.content) { + const codeBlockContents = message.content.match(/```[\s\S]*?```/g); + let codeBlocks = []; + + if (codeBlockContents) { + codeBlockContents.forEach((block) => { + const lang = block.split('\n')[0].replace('```', '').trim().toLowerCase(); + const code = block.replace(/```[\s\S]*?\n/, '').replace(/```$/, ''); + codeBlocks.push({ lang, code }); + }); + } + let htmlContent = ''; let cssContent = ''; let jsContent = ''; - const codeBlocks = message.content.match(/```[\s\S]*?```/g); - if (codeBlocks) { - codeBlocks.forEach((block) => { - const lang = block.split('\n')[0].replace('```', '').trim().toLowerCase(); - const code = block.replace(/```[\s\S]*?\n/, '').replace(/```$/, ''); - if (lang === 'html') { - htmlContent += code + '\n'; - } else if (lang === 'css') { - cssContent += code + '\n'; - } else if (lang === 'javascript' || lang === 'js') { - jsContent += code + '\n'; - } - }); - } + codeBlocks.forEach((block) => { + const { lang, code } = block; + + if (lang === 'html') { + htmlContent += code + '\n'; + } else if (lang === 'css') { + cssContent += code + '\n'; + } else if (lang === 'javascript' || lang === 'js') { + jsContent += code + '\n'; + } + }); const inlineHtml = message.content.match(/[\s\S]*?<\/html>/gi); const inlineCss = message.content.match(/