From 34bd7e44e6eb426405323f203d424cde47a94672 Mon Sep 17 00:00:00 2001
From: Sven Olderaan <s.olderaan@jdi.nl>
Date: Sun, 16 Mar 2025 13:37:02 +0100
Subject: [PATCH] Fix duplicate image rendering in chat bubble tool calls

---
 index.js | 81 ++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 50 insertions(+), 31 deletions(-)

diff --git a/index.js b/index.js
index 2d9fc62..ba8f392 100644
--- a/index.js
+++ b/index.js
@@ -241,25 +241,35 @@ function processToolCallMessages() {
                     renderContainer.html(tool.result);
                   }
                   
-                  // Remove any existing rendered images
+                  // Clean up all images for this message
                   $(this).find('.sillybubble-rendered-image').remove();
-                  $('.sillybubble-collapsed-image').remove();
+                  $(this).find('.sillybubble-collapsed-image').remove();
                   
                   // Create a special always-visible container for collapsed state
                   const collapsedContainer = $('<div class="sillybubble-collapsed-image"></div>');
                   collapsedContainer.html(renderContainer.html());
                   
-                  // Add the image to the mes_block, BEFORE the details element
-                  // This ensures it's visible when details is collapsed
-                  detailsElement.before(collapsedContainer);
+                  // Get the message text element (where tool calls are shown)
+                  const messageTextDiv = $(this).find('.mes_text');
                   
-                  // Also add inside the details for when it's open
-                  if (summaryElement.length) {
-                    // Add inside the details content for when it's expanded
-                    const reasoningDiv = $(this).find('.mes_reasoning');
-                    if (reasoningDiv.length) {
-                      reasoningDiv.prepend(renderContainer.clone());
-                    }
+                  // Check if the details element is part of mes_text (most common case)
+                  const detailsInText = messageTextDiv.find('details').length > 0;
+                  
+                  if (detailsInText) {
+                    // For typical tool calls, add collapsed image just before the details element in mes_text
+                    messageTextDiv.find('details').before(collapsedContainer);
+                  } else {
+                    // Fallback - add it before the details element wherever it is
+                    detailsElement.before(collapsedContainer);
+                  }
+                  
+                  // ONLY add image in the reasoning div, NOT in the details summary
+                  const reasoningDiv = $(this).find('.mes_reasoning');
+                  if (reasoningDiv.length) {
+                    // Make sure no existing image is there
+                    reasoningDiv.find('.sillybubble-rendered-image').remove();
+                    // Add the image to the reasoning section
+                    reasoningDiv.prepend(renderContainer);
                   }
                   
                   // Mark this message as processed
@@ -285,29 +295,29 @@ function processToolCallMessages() {
 
 // Helper function to handle visibility of images based on details open state
 function handleDetailsVisibility(messageElement, detailsElement) {
-  if (detailsElement.prop('open')) {
-    // If details is open, hide the collapsed version
-    messageElement.find('.sillybubble-collapsed-image').hide();
-  } else {
-    // If details is closed, show the collapsed version
-    messageElement.find('.sillybubble-collapsed-image').show();
-  }
+  // Add a class to the details element to mark it as containing a bubble image
+  detailsElement.addClass('has-bubble-image');
   
-  // Set up a mutation observer to watch for the open attribute changing
-  const observer = new MutationObserver((mutations) => {
-    mutations.forEach((mutation) => {
-      if (mutation.attributeName === 'open') {
-        if (detailsElement.prop('open')) {
-          messageElement.find('.sillybubble-collapsed-image').hide();
-        } else {
-          messageElement.find('.sillybubble-collapsed-image').show();
-        }
+  // Set up a click handler for the details/summary to ensure proper rendering
+  detailsElement.on('click', function() {
+    // Small delay to ensure the open state has changed
+    setTimeout(() => {
+      // Use the CSS-based visibility approach defined in our styles
+      // This is more reliable than show/hide in the DOM
+      if (detailsElement.prop('open')) {
+        messageElement.addClass('details-open');
+      } else {
+        messageElement.removeClass('details-open');
       }
-    });
+    }, 10);
   });
   
-  // Start observing the details element
-  observer.observe(detailsElement[0], { attributes: true });
+  // Set initial state
+  if (detailsElement.prop('open')) {
+    messageElement.addClass('details-open');
+  } else {
+    messageElement.removeClass('details-open');
+  }
 }
 
 // Observer function to watch for new messages
@@ -436,6 +446,15 @@ jQuery(async () => {
           box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
         }
         
+        /* Hide the collapsed image when details is open using classes */
+        .details-open .sillybubble-collapsed-image,
+        details[open] ~ .sillybubble-collapsed-image,
+        details[open] + .sillybubble-collapsed-image,
+        .mes_text details[open] ~ .sillybubble-collapsed-image,
+        .mes_text details[open] + .sillybubble-collapsed-image {
+          display: none !important;
+        }
+        
         /* Hide the "Tool calls: Chat Bubble Image" summary text when collapsed */
         .smallSysMes.toolCall[data-sb-processed="true"] summary {
           font-size: 0.8em;