class SimilarityMapTool(BaseTool):
    name: str = Field(default="Generate Similarity Map")
    description: str = Field(default="Generates a similarity map between two molecules.")

    def _run(self, smiles1: str, smiles2: str) -> str:
        try:
            mol1 = Chem.MolFromSmiles(smiles1)
            mol2 = Chem.MolFromSmiles(smiles2)
            if mol1 is None or mol2 is None:
                return "Invalid SMILES string(s)" 

            # Generate the similarity map
            fig, maxweight = SimilarityMaps.GetSimilarityMapForFingerprint(mol1, mol2, SimilarityMaps.GetMorganFingerprint)

            # Convert figure to image
            buf = BytesIO()
            fig.savefig(buf, format='png')
            buf.seek(0)
            img_str = base64.b64encode(buf.getvalue()).decode()

            # Display image in Streamlit
            html_img = f'<img src="data:image/png;base64,{img_str}" alt="Similarity Map"/>'
            st.markdown(html_img, unsafe_allow_html=True)

            return "Similarity map displayed."
        except Exception as e:
            print(f"Error generating similarity map: {str(e)}")
            return f"Error generating similarity map: {str(e)}"

    class Config:
        arbitrary_types_allowed = True
        extra = "allow"


class SimilarityMapTool(BaseTool):
    name: str = Field(default="Generate Similarity Map")
    description: str = Field(default="Generates a similarity map between two molecules.")

    def _run(self, smiles1: str, smiles2: str) -> str:
        try:
            mol1 = Chem.MolFromSmiles(smiles1)
            mol2 = Chem.MolFromSmiles(smiles2)
            if mol1 is None or mol2 is None:
                return "Invalid SMILES string(s)" 

            # Generate the similarity map
            fig, maxweight = SimilarityMaps.GetSimilarityMapForFingerprint(mol1, mol2, SimilarityMaps.GetMorganFingerprint)

            # Convert figure to image
            buf = BytesIO()
            fig.savefig(buf, format='png')
            buf.seek(0)
            img_str = base64.b64encode(buf.getvalue()).decode()

            # Display image in Streamlit
            html_img = f'<img src="data:image/png;base64,{img_str}" alt="Similarity Map"/>'
            st.markdown(html_img, unsafe_allow_html=True)

            return "Similarity map displayed."
        except Exception as e:
            print(f"Error generating similarity map: {str(e)}")
            return f"Error generating similarity map: {str(e)}"

    class Config:
        arbitrary_types_allowed = True
        extra = "allow"


still i am not getting the similarity map as an image instread i am images of the two molecules for the prompt 



 Ensure the OpenAI API key is set
if "OPENAI_API_KEY" not in os.environ or os.environ["OPENAI_API_KEY"] == "your_openai_api_key_here":
    st.error("Please set your OpenAI API key as an environment variable.")
    st.stop()

# Setup Manager LLM
manager_llm = ChatOpenAI(model_name=OPENAI_MODEL_NAME, temperature=0)

# Streamlit App
st.set_page_config(page_title="Molecule Design Assistant", page_icon="🧪", layout="wide")

# Add GIF at the heading
st.markdown(
    """
    <div style='text-align: center;'>
        <img src='https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExaWNobDVjMjJpNTc1a2J6dm5oZW40eTdlbWMxcnN1NHZzOW43NWF1MiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/VcAt04a901woawqLv2/giphy.gif' alt='GIF Image' width='200'>
    </div>
    """,
    unsafe_allow_html=True,
)

st.markdown("<h1 style='text-align: center;'>🧪 Molecule Design Assistant</h1>", unsafe_allow_html=True)
st.markdown("<h3 style='text-align: center;'>Interact with AI agents to design and optimize molecules.</h3>", unsafe_allow_html=True)

# Initialize session state variables
if "generated" not in st.session_state:
    st.session_state["generated"] = []
if "past" not in st.session_state:
    st.session_state["past"] = []
if "input_text" not in st.session_state:
    st.session_state["input_text"] = ""
if "results" not in st.session_state:
    st.session_state["results"] = []

# Main Tabs
tab1, tab2 = st.tabs(["Main App", "Results Table"])

# Tab 1: Main Application
with tab1:
    # Create columns for layout
    col1, col2 = st.columns([3, 1])

    # User Input Section
    with col2:
        st.header("User Input")
        def get_text():
            input_text = st.text_area(
                label="Your Message",
                placeholder="Enter your request here...",
                value=st.session_state["input_text"],
                key="input",
                height=100,
            )
            return input_text

        user_input = get_text()
        submit_button = st.button(label="Submit", key="submit_btn")

        if submit_button:
            if user_input:
                st.session_state["past"].append(user_input)
                st.session_state["input_text"] = ""  # Clear input after submission

    # Agent Outputs Section
    with col1:
        st.header("Agent Outputs")
        agent_expander = st.expander("💬 Agent Conversations", expanded=False)

    # Conversation History Section
    with col2:
        if st.session_state["generated"]:
            st.subheader("Conversation History")
            for i in range(len(st.session_state["generated"])):
                with st.container():
                    st.markdown(f"**You:** {st.session_state['past'][i]}")
                    st.markdown(f"**Assistant:** {st.session_state['generated'][i]}")
                    st.markdown("---")

    # Function to Process User Request
    def process_user_request(input_text: str):
        try:
            sys.stdout = StreamToExpander(agent_expander)  # Redirect stdout to expander

            # Create agents with verbose output
            agents = create_agents(verbose=True)

            # Create Crew with Hierarchical Process
            crew = Crew(
                agents=agents,
                process=Process.hierarchical,
                manager_llm=manager_llm,
                memory=True,
                planning=True,
                verbose=True,
            )

            task = Task(
                description=input_text,
                expected_output="Provide the appropriate output based on the user's request.",
                agent=None,
            )
            crew.tasks = [task]
            result = crew.kickoff()

            sys.stdout = sys.__stdout__  # Reset stdout
            return result, agents
        except Exception as e:
            st.error(f"An error occurred during processing: {e}")
            sys.stdout = sys.__stdout__  # Reset stdout
            return None, None

    # Processing User Input
    if "past" in st.session_state and len(st.session_state["past"]) > len(st.session_state["generated"]):
        user_input = st.session_state["past"][-1]
        with col1:  # Place the spinner in the Agent Outputs section
            with st.spinner("🤖 **Agents at work...**"):
                output, agents = process_user_request(user_input)
        if output:
            st.session_state["generated"].append(output.raw if hasattr(output, "raw") else str(output))
            with col1:
                st.subheader("Final Answer")
                st.markdown(output.raw if hasattr(output, "raw") else str(output))
            st.session_state["results"].append({
                "Input": user_input,
                "Agents Used": ", ".join([agent.role for agent in agents]),
                "Output": output.raw if hasattr(output, "raw") else str(output),
            })
with tab2:
    st.header("Results Table")
    if st.session_state["results"]:
        import pandas as pd
        df = pd.DataFrame(st.session_state["results"])
        # Convert Output to string to avoid serialization issues
        df['Output'] = df['Output'].astype(str)
        st.table(df)
    else:
        st.write("No results yet.")

# Custom CSS for better UI
st.markdown(
    """
    <style>
    div.stButton > button:first-child {
        width: 100%;
        background-color: #2196F3 !important;  /* Changed to blue */
        color: white !important;
        border: none;
        padding: 10px 20px;
        text-align: center;
        font-size: 16px;
    }
    .stTextArea textarea {
        height: 100px !important;
    }
    </style>
    """,
    unsafe_allow_html=True,
)
# agents.py

from crewai import Agent
from tools.text_to_molecule_tool import TextToMoleculeTool
from tools.molecule_to_text_tool import MoleculeToTextTool
from tools.valid_molecule_tool import ValidMoleculeTool
from tools.molecule_optimizer_tool import MoleculeOptimizerTool
from tools.visualization_tool import VisualizationTool
from tools.rdkit_tools import CalculateLogPTool, CalculateMolWtTool, LipinskiRuleOfFiveTool, SimilarityMapTool

def create_agents(verbose=False):
    # Agent 1: Chemistry Expert with RDKit tools
    chemistry_expert = Agent(
        role="Chemistry Expert",
        goal="Provide detailed chemical analysis, calculate properties, and ensure molecule visualization.",
        backstory="""You are a senior chemist with expertise in molecule analysis, property calculation, and visualization. Your goal is to:
                    1. Validate the input molecule structure.
                    2. Calculate properties like LogP, molecular weight, and Lipinski's rule.
                    3. Generate a high-quality visualization of the molecule.
                    4. If asked generate the similarity map and visualize it
                    Ensure all outputs are in a consistent format for downstream use. If any step fails, provide a detailed error message for debugging.
        """,
        tools=[
            ValidMoleculeTool(),
            VisualizationTool(),
            CalculateLogPTool(),
            CalculateMolWtTool(),
            LipinskiRuleOfFiveTool(),
            SimilarityMapTool(),
        ],
        
        allow_delegation=True,
        verbose=verbose,
    )

    # Agent 2: Molecule Generator and Descriptor
    molecule_generator = Agent(
        role="Molecule Generator",
        goal="Generate valid molecular structures from text descriptions and convert molecules to textual descriptions",
        backstory="""You are a molecule design expert specializing in:
                    1. Converting textual descriptions into valid molecular structures.
                    2. Translating molecular structures (e.g., SMILES) into human-readable descriptions.

                    ### Instructions:
                    - For textual input:
                      - Generate a valid molecular structure that matches the description.
                      - Validate the generated molecule to ensure it is chemically sound.
                      - Provide the following outputs:
                        - **Molecule Name**: [Generated Name or 'N/A']
                        - **SMILES**: [SMILES String]
                        - **Description**: [Short textual description of the molecule]

                    - For SMILES input:
                      - Convert the given SMILES string into a human-readable textual description.
                      - Ensure the description is concise and accurate.

                    ### Error Handling:
                    - If the text description is unclear or cannot be translated into a valid molecule, provide an error message and suggest improvements or clarifications.
                    - If the SMILES string is invalid, notify the user and suggest corrections or alternatives.

                    ### Output Format:
                    Always present your outputs in the following structured format:
                    - **Input Type**: [Text Description or SMILES]
                    - **Input**: [User-provided input]
                    - **Generated Output**:
                      - **Molecule Name**: [Generated Name or 'N/A']
                      - **SMILES**: [SMILES String]
                      - **Description**: [Human-readable description of the molecule]
                    """,
        tools=[TextToMoleculeTool(), MoleculeToTextTool(),VisualizationTool()],
        allow_delegation=True,
        verbose=verbose,
    )

    # Agent 3: Molecule Editor
    molecule_editor = Agent(
        role="Molecule Editor",
        goal="Refine and optimize molecular structures based on user-defined criteria",
        backstory="""You specialize in editing and optimizing molecular structures to achieve desired properties. 
                    - When given a molecule, suggest alternatives or optimizations that improve its target properties (e.g., molecular weight, solubility, stability).
                    - Always provide the rationale behind each suggestion.
                    Ensure outputs are in the following format:
                    - **Original Molecule**:
                      - **Name**: [Original Name or 'N/A']
                      - **SMILES**: [Original SMILES]
                    - **Optimized Molecule**:
                      - **Name**: [Optimized Name or 'N/A']
                      - **SMILES**: [Optimized SMILES]
                    - **Reason for Optimization**: [Explanation of changes and benefits]
                    """,
        tools=[MoleculeOptimizerTool(),ValidMoleculeTool(),VisualizationTool(),CalculateLogPTool(),CalculateMolWtTool()],
        allow_delegation=True,
        verbose=verbose,
    )

    # Additional agents (Agent 4 and Agent 5) can be added similarly when their tools are implemented

    return [chemistry_expert, molecule_generator, molecule_editor]



docs

To use the new drawing code, we create a Draw2D object and pass that to SimilarityMaps.GetSimilarityMapForFingerprint:

def show_png(data):
    bio = io.BytesIO(data)
    img = Image.open(bio)
    return img

d = Draw.MolDraw2DCairo(400, 400)
_, maxWeight = SimilarityMaps.GetSimilarityMapForFingerprint(atorvastatin, rosuvastatin, 
                                        lambda m, i: SimilarityMaps.GetMorganFingerprint(m, i, radius=2, fpType='bv'), 
                                        draw2d=d)
d.FinishDrawing()
show_png(d.GetDrawingText())


what should i need to do to show up the visulaization of the similarity map