How to update a Chrome extension popup with server response using a loading page?

I’m currently working on a chrome extension that scrapes a website for it’s recipe using llm and then uses open ai chat gpt to modify the recipe to user preferences. I’m working on the front end part right now. i’ve created a pop window that opens up when clicking on the extension. then the user selects its preferences and submits. then the pop window changes to the loading window. now i’m trying to implement the following. once the server finishes modifying the recipe, i want to display it in the popup. i’ve tried various implementation, but for some reason the window never shows. the server runs properly but the pop up is “stuck” on the loading stage.

here is my current code :

server.py

import os
import sys
from dotenv import load_dotenv
from scrapegraphai.graphs import SmartScraperGraph
import json
import openai
import tinify
from flask import Flask, request, jsonify, make_response

load_dotenv()

OPENAI_API_KEY = "MY_API_KEY"
client = openai.OpenAI(api_key=OPENAI_API_KEY)
tinify.key = "MY_TINIFY_KEY"

graph_config = {
    "llm": {
        "api_key": OPENAI_API_KEY,
        "model": "gpt-3.5-turbo",
    },
}

# Function to run the scraper
def run_scraper(url):
    smart_scraper_graph = SmartScraperGraph(
        prompt="""You are an expert in web scraping and extracting information from web pages. I will provide you the source, 
        and you need to extract the recipe information from it. Please extract the following details:

        - Name of the dish
        - List of ingredients
        - Instructions
        - Amount of servings
        - Cooking time
        """,
        source=url,
        config=graph_config
    )

    result = smart_scraper_graph.run()
    return result


def modify_recipe(recipe_data, user_request):
    prompt = f"""
    You are a culinary expert. Here is a recipe I scraped:

    {json.dumps(recipe_data, indent=2)}

    The user has requested the following changes: {user_request}
    Please provide a whole recipe, modified with proper alternative for the problematic ingredients. 
    Please provide the modified recipe in the same JSON format.
    For example, if the recipe includes cheese and the user request to change the recipe to vegan, give an alternative for the cheese.
    Try to provide an alternative that will be similar to the original recipe taste and texture.
    The JSON should include the following details if present in the original recipe:

        - Name of the dish
        - List of ingredients
        - Instructions
        - Amount of servings
        - Cooking time
    """

    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a culinary expert."},
            {"role": "user", "content": prompt}
        ]
    )

    modified_recipe = response.choices[0].message.content
    try:
        modified_recipe_json = json.loads(modified_recipe)
    except json.JSONDecodeError:
        modified_recipe_json = {"error": "The response could not be parsed as JSON. Please try again."}
    
    return modified_recipe_json

# Function to flatten the ingredients list
def flatten_ingredients(ingredients_list):
    flat_ingredients = []
    for item in ingredients_list:
        if isinstance(item, dict):
            for key in item.keys():
                flat_ingredients.append(key)
        else:
            flat_ingredients.append(item)
    return flat_ingredients

#Function to generate an image using DALL-E 2
def generate_dish_image(modified_recipe):
    dish_name = modified_recipe.get("Name of the dish", "")
    ingredients = modified_recipe.get("List of ingredients", [])
    # Ensure ingredients is a list of strings
    ingredients_list = ', '.join(flatten_ingredients(ingredients))
    prompt = f"A delicious dish called {dish_name} made with the following ingredients: {ingredients_list}.. A high-quality, detailed, and appetizing image."

    response = client.images.generate(
        model="dall-e-2",
        prompt=prompt,
        n=1,
        size="1024x1024"
    )
    
    if response.data and len(response.data) > 0 and response.data[0].url:
        image_url = response.data[0].url
        return image_url
    else:
        print("Error generating image. The response did not contain a valid URL.")
        return None

#Function to compress the image from URL
def compress_image(image_url, output_path):
    # Compress the image using TinyPNG
    source = tinify.from_url(image_url)
    source.to_file(output_path)

    return output_path

app = Flask(__name__)

@app.route('/receive_url', methods=['OPTIONS', 'POST'])
def receive_url():
    if request.method == 'OPTIONS':
        response = make_response()
        response.headers["Access-Control-Allow-Origin"] = "*"
        response.headers["Access-Control-Allow-Methods"] = "POST, OPTIONS"
        response.headers["Access-Control-Allow-Headers"] = "Content-Type"
        return response

    data = request.get_json()
    url = data.get('url')
    user_request = data.get('request')
    if url and user_request:
        print(f"Received URL: {url}")
        print(f"User Request: {user_request}")
        
        # Run the scraper and modify the recipe
        scraped_data = run_scraper(url)
        modified_recipe = modify_recipe(scraped_data, user_request)
        print("Modified Recipe:n", modified_recipe)
        
        # Generate image URL
        image_url = generate_dish_image(modified_recipe)
        if image_url:
            #print(f"Generated Image URL: {image_url}") #for testing
            # Compress the image
            compressed_image_path = compress_image(image_url, "compressed_image.png")

            # Displaying the image path
            absolute_path = os.path.abspath(compressed_image_path)
            print(f"Image available at: {absolute_path}")
            print(f"To view the image, open the following path in an image viewer: {absolute_path}")
        else:
            print("Could not generate the image. Dish name or ingredients are missing.")
            compressed_image_path = None

        response_data = {
             "modified_recipe": modified_recipe,
             "compressed_image_path": compressed_image_path
        }
        
        response = make_response(jsonify(response_data), 200)
        response.headers["Access-Control-Allow-Origin"] = "*"
        return response

    response = make_response('No URL or request provided', 400)
    response.headers["Access-Control-Allow-Origin"] = "*"
    return response

if __name__ == '__main__':
    app.run(port=5000)

background.js:

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: "recipeRemixer",
    title: "Recipe Remixer",
    contexts: ["page"]
  });
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === "recipeRemixer") {
    chrome.scripting.executeScript({
      target: { tabId: tab.id },
      func: function() {
        return { url: window.location.href };
      }
    }, (results) => {
      if (results && results[0] && results[0].result) {
        const recipeURL = results[0].result.url;
        chrome.storage.local.set({ recipeURL: recipeURL }, () => {
          chrome.windows.create({
            url: chrome.runtime.getURL("popup.html"),
            type: "popup",
            width: 500,
            height: 600
          });
        });
      }
    });
  }
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'sendRequest') {
    const { url, userRequest } = message.data;
    console.log('Received message in background script:', message); // Log the received message
    sendURLToServer(url, userRequest).then(response => {
      sendResponse(response);
    }).catch(error => {
      sendResponse({ success: false, error: error.message });
    });
    return true; // Indicates that we will respond asynchronously
  }
});

async function sendURLToServer(url, userRequest) {
  try {
    const response = await fetch('http://127.0.0.1:5000/receive_url', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ url: url, request: userRequest })
    });

    if (response.ok) {
      const responseData = await response.json(); // Assume the server returns JSON
      console.log('URL and request sent to server successfully');
      return { success: true, data: responseData };
    } else {
      console.error('Failed to send URL and request to server');
      return { success: false, error: 'Failed to send URL and request to server' };
    }
  } catch (error) {
    console.error('Error:', error);
    return { success: false, error: error.message };
  }
}

manifest.json

{
  "manifest_version": 3,
  "name": "Recipe Remixer",
  "version": "1.0",
  "description": "Modify recipes to match dietary preferences.",
  "permissions": [
    "contextMenus",
    "activeTab",
    "storage",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "48": "icon/icon48.png",
      "64": "icon/icon64.png",
      "128": "icon/icon128.png"
    }
  },
  "icons": {
    "48": "icon/icon48.png",
    "64": "icon/icon64.png",
    "128": "icon/icon128.png"
  },
  "web_accessible_resources": [
    {
      "resources": ["loading.html", "loading.js"],
      "matches": ["<all_urls>"]
    }
  ]
}

popup.js:

document.addEventListener('DOMContentLoaded', () => {
  console.log('Popup loaded');

  const checkboxes = document.querySelectorAll('input[name="diet"]');
  checkboxes.forEach(checkbox => {
    checkbox.addEventListener('change', handleCheckboxChange);
  });

  document.getElementById('diet-form').addEventListener('submit', function(event) {
    event.preventDefault();

    const selectedDiets = [];
    checkboxes.forEach(checkbox => {
      if (checkbox.checked) {
        selectedDiets.push(checkbox.value);
      }
    });

    if (selectedDiets.length < 1 || selectedDiets.length > 2) {
      alert('Please select one or two options only.');
      return;
    }

    console.log('Selected Diets:', selectedDiets);

    chrome.storage.local.get('recipeURL', (data) => {
      const recipeURL = data.recipeURL;
      console.log('Recipe URL:', recipeURL);

      if (recipeURL) {
        openLoadingWindow();
        chrome.runtime.sendMessage({
          type: 'sendRequest',
          data: { url: recipeURL, userRequest: selectedDiets }
        }, (response) => {
          console.log('Message sent to background script', response);

          if (response.success) {
            chrome.storage.local.set({ modifiedRecipe: response.data }, () => {
              chrome.windows.getCurrent((currentWindow) => {
                chrome.windows.remove(currentWindow.id, () => {
                  chrome.windows.create({
                    url: chrome.runtime.getURL("modified_recipe.html"),
                    type: "popup",
                    width: 500,
                    height: 600
                  });
                });
              });
            });
          } else {
            alert('Failed to get modified recipe: ' + response.error);
            chrome.windows.getCurrent((currentWindow) => {
              chrome.windows.remove(currentWindow.id);
            });
          }
        });
      } else {
        console.error('Recipe URL not found in storage');
      }
    });
  });

  function handleCheckboxChange() {
    const selectedCheckboxes = Array.from(checkboxes).filter(checkbox => checkbox.checked);
    if (selectedCheckboxes.length >= 2) {
      checkboxes.forEach(checkbox => {
        if (!checkbox.checked) {
          checkbox.disabled = true;
        }
      });
    } else {
      checkboxes.forEach(checkbox => {
        checkbox.disabled = false;
      });
    }
  }

  function openLoadingWindow() {
    chrome.windows.create({
      url: chrome.runtime.getURL("loading.html"),
      type: "popup",
      width: 500,
      height: 600
    }, () => {
      window.close();
    });
  }
});

loading.js

document.addEventListener('DOMContentLoaded', () => {
  console.log('Loading popup loaded');
});

i also have html files for each popup.
any idea of how to create the new popup window and to make sure it pops once the server is finished modifying the recipe is greatly appreciated