From 1387131f57d246f912f2875c2ffa870a1542712c Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 9 Jan 2026 12:04:00 +0100 Subject: [PATCH] Gemini Test --- GeminiTest/Myc.Api.MarkdownStream.pas | 99 ++++++++++++++++++--------- 1 file changed, 67 insertions(+), 32 deletions(-) diff --git a/GeminiTest/Myc.Api.MarkdownStream.pas b/GeminiTest/Myc.Api.MarkdownStream.pas index 9d77d3b..de6a95a 100644 --- a/GeminiTest/Myc.Api.MarkdownStream.pas +++ b/GeminiTest/Myc.Api.MarkdownStream.pas @@ -14,12 +14,9 @@ uses type TBlockType = (btMarkdown, btRawText, btHtml); - /// - /// Manages an incremental Markdown/HTML stream within a TWebBrowser. - /// Supports block-based rendering and collapsible raw text sections. - /// + // Manages an incremental Markdown/HTML stream within a TWebBrowser. + // Supports block-based rendering and collapsible raw text sections. IMarkdownStream = interface - ['{A1B2C3D4-E5F6-4788-9900-112233445566}'] procedure InitializeBrowser(const Browser: TWebBrowser); procedure BeginBlock(const AType: TBlockType; const ATitle: string = ''); procedure Append(const Content: string); @@ -202,6 +199,7 @@ begin + @@ -282,8 +280,10 @@ begin let currentTitle = ""; let currentBuffer = ""; - // Inline SVG für das Icon + // Icons const chevronSvg = ''; + const iconCopy = ''; + const iconCheck = ''; function toggleBlock(header) { const wrapper = header.closest('.raw-wrapper'); @@ -294,6 +294,35 @@ begin } } + function addCopyButtons(container) { + const pres = container.querySelectorAll("pre"); + pres.forEach(function(preBlock) { + if (preBlock.parentNode.classList.contains("code-wrapper")) return; + + const wrapper = document.createElement("div"); + wrapper.className = "code-wrapper"; + preBlock.parentNode.insertBefore(wrapper, preBlock); + wrapper.appendChild(preBlock); + + const button = document.createElement("button"); + button.className = "copy-btn"; + button.innerHTML = iconCopy; + button.title = "Copy to Clipboard"; + + button.addEventListener("click", function() { + const code = preBlock.querySelector("code"); + if (!code) return; + const text = code.innerText; + navigator.clipboard.writeText(text).then(function() { + button.innerHTML = iconCheck; + setTimeout(function() { button.innerHTML = iconCopy; }, 2000); + }, function(err) { console.error("Clipboard failed", err); }); + }); + + wrapper.insertBefore(button, preBlock); + }); + } + function renderTo(target, content, mode, title, isFinal) { target.innerHTML = ""; if (mode === "markdown") { @@ -306,6 +335,13 @@ begin const c = document.createElement("div"); c.innerHTML = (typeof marked !== "undefined") ? marked.parse(content) : content; target.appendChild(c); + + // Highlight and add Copy Buttons + if (typeof hljs !== "undefined") { + c.querySelectorAll("pre code").forEach((block) => { hljs.highlightElement(block); }); + } + addCopyButtons(c); + } else if (mode === "text") { const wrapper = document.createElement("div"); wrapper.className = "raw-wrapper " + (isFinal ? "collapsed" : "expanded"); @@ -313,7 +349,6 @@ begin const header = document.createElement("div"); header.className = "raw-header"; header.onclick = function() { toggleBlock(this); }; - header.innerHTML = chevronSvg + '' + (title || "Raw Output") + ''; const code = document.createElement("pre");