diff --git a/ui-svelte/package-lock.json b/ui-svelte/package-lock.json index 1958d64d..8c86b603 100644 --- a/ui-svelte/package-lock.json +++ b/ui-svelte/package-lock.json @@ -925,7 +925,6 @@ "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", @@ -1308,7 +1307,6 @@ "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1441,7 +1439,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3452,7 +3449,6 @@ "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -3565,7 +3561,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.48.5.tgz", "integrity": "sha512-NB3o70OxfmnE5UPyLr8uH3IV02Q43qJVAuWigYmsSOYsS0s/rHxP0TF81blG0onF/xkhNvZw4G8NfzIX+By5ZQ==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -3721,7 +3716,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3900,7 +3894,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/ui-svelte/src/components/playground/ChatMessage.svelte b/ui-svelte/src/components/playground/ChatMessage.svelte index 2bbb5fcc..a1eae79a 100644 --- a/ui-svelte/src/components/playground/ChatMessage.svelte +++ b/ui-svelte/src/components/playground/ChatMessage.svelte @@ -116,6 +116,47 @@ cancelEdit(); } } + + const COPY_SVG = ``; + const CHECK_SVG = ``; + + function codeBlockCopy(node: HTMLElement) { + function attachButtons() { + node.querySelectorAll('pre:not([data-copy-btn])').forEach(pre => { + pre.setAttribute('data-copy-btn', 'true'); + const btn = document.createElement('button'); + btn.className = 'code-copy-btn'; + btn.title = 'Copy code'; + btn.innerHTML = COPY_SVG; + btn.addEventListener('click', async () => { + const text = pre.querySelector('code')?.textContent ?? pre.textContent ?? ''; + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text); + } else { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.cssText = 'position:fixed;left:-9999px'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + } + btn.innerHTML = CHECK_SVG; + btn.classList.add('copied'); + setTimeout(() => { btn.innerHTML = COPY_SVG; btn.classList.remove('copied'); }, 2000); + } catch (e) { + console.error('copy failed', e); + } + }); + pre.appendChild(btn); + }); + } + attachButtons(); + const mo = new MutationObserver(attachButtons); + mo.observe(node, { childList: true, subtree: true }); + return { destroy: () => mo.disconnect() }; + } @@ -174,7 +215,7 @@ {#if showRaw} {textContent} {:else} - + {#each renderedParts.blocks as block (block.id)} {@html block.html} {/each} @@ -299,14 +340,42 @@