this is my app.py
import os
import tempfile
import streamlit as st
import streamlit.components.v1 as components
from git import Repo, GitCommandError

from agents import (
    FilterAgent,
    AnalysisAgent,
    ConsolidationAgent,
    ImprovementAgent,
    DiagramAgent,
)
from file_handling.file_handler import FileHandler
from utils.azure_client import get_azure_client
import json
from diagram_renderer import render_architecture_diagram  # New component

from dotenv import load_dotenv
load_dotenv()

BASE_PROMPT_DIR = os.path.join(os.path.dirname(__file__), "agents", "prompts")

st.set_page_config(page_title="Project Architecture Analyzer", layout="wide")


# Remove render_mermaid function and replace with:
def render_architecture_diagram(diagram_data):
    """Render architecture diagram using custom icons and connections"""
    if not diagram_data:
        st.warning("No diagram data generated")
        return
    
    try:
        # Parse the structured diagram data
        diagram_json = json.loads(diagram_data)
        render_architecture_diagram(diagram_json)
    except json.JSONDecodeError:
        st.error("Invalid diagram format. Expected JSON structure")
        st.text_area("Raw Diagram Data", diagram_data, height=200)

# In run_analysis() function:
# Replace the cloud architecture diagram section with:


def render_mermaid(mermaid_code, height=600):
    # Remove markdown code fences if present
    mermaid_code = mermaid_code.strip()
    if mermaid_code.startswith("```mermaid"):
        lines = mermaid_code.splitlines()
        mermaid_code = "\n".join(lines[1:-1]).strip()
    
    # Debug: show raw code
    st.text_area("Raw Mermaid code for debugging", mermaid_code, height=200)
    
    mermaid_html = f"""
    <div class="mermaid">
    {mermaid_code}
    </div>

    <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
    <script>
      mermaid.initialize({{ startOnLoad: true }});
    </script>
    """

    components.html(mermaid_html, height=height)


def main():
    st.title("🔍 Project Architecture Analyzer")

    repo_url = st.text_input("GitHub Repository URL")

    # Sidebar for credentials & settings
    st.sidebar.header("Configuration")
    pat = st.sidebar.text_input("GitHub Personal Access Token", type="password")
    azure_endpoint = st.sidebar.text_input(
        "Azure OpenAI Endpoint",
        value=os.environ.get("AZURE_OPENAI_ENDPOINT", ""),
    )
    api_key = st.sidebar.text_input(
        "Azure OpenAI API Key",
        type="password",
        value=os.environ.get("AZURE_OPENAI_API_KEY", ""),
    )
    deployment_name = st.sidebar.text_input(
        "Deployment Name",
        value=os.environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", ""),
    )
    analysis_mode = st.sidebar.selectbox("Analysis Mode", ["Technical Blueprint", "Improvement Plan", "Cloud Architecture Diagram"])

    if st.button("Analyze Project"):
        if not all([repo_url, pat, azure_endpoint, api_key, deployment_name]):
            st.error("Please fill in all configuration fields.")
        else:
            run_analysis(repo_url, pat, azure_endpoint, api_key, deployment_name, analysis_mode)


def run_analysis(repo_url, pat, azure_endpoint, api_key, deployment_name, analysis_mode):
    try:
        client = get_azure_client(azure_endpoint, api_key)
    except Exception as e:
        st.error(f"Azure OpenAI client initialization failed: {e}")
        return

    filter_agent = FilterAgent(client, deployment_name, os.path.join(BASE_PROMPT_DIR, "filter_agent_prompt.txt"))
    analysis_agent = AnalysisAgent(client, deployment_name, os.path.join(BASE_PROMPT_DIR, "analysis_agent_prompt.txt"))
    consolidation_agent = ConsolidationAgent(client, deployment_name, os.path.join(BASE_PROMPT_DIR, "consolidation_agent_prompt.txt"))
    improvement_agent = ImprovementAgent(client, deployment_name, os.path.join(BASE_PROMPT_DIR, "improvement_agent_prompt.txt"))
    diagram_agent = DiagramAgent(client, deployment_name, os.path.join(BASE_PROMPT_DIR, "diagram_agent_prompt.txt"))

    with tempfile.TemporaryDirectory() as tmp_dir:
        try:
            with st.spinner("🌀 Cloning repository..."):
                if pat:
                    if not repo_url.startswith("https://"):
                        st.error("PAT can only be used with HTTPS URLs")
                        return
                    auth_repo_url = repo_url.replace("https://", f"https://{pat}@", 1)
                else:
                    auth_repo_url = repo_url

                Repo.clone_from(auth_repo_url, tmp_dir)
            st.success("Repository cloned successfully")

            with st.spinner("🔍 Loading analysis overview..."):
                file_handler = FileHandler(tmp_dir, filter_agent)

                st.subheader("📊 Analysis Overview")
                cols = st.columns(4)
                cols[0].metric("Total Files", file_handler.total_scanned)
                cols[1].metric("Included Files", len(file_handler.files))
                cols[2].metric("Excluded Files", len(file_handler.excluded_files))
                ratio = 0
                if file_handler.total_scanned > 0:
                    ratio = len(file_handler.files) / file_handler.total_scanned * 100
                cols[3].metric("Focus Ratio", f"{ratio:.1f}%")

            consolidated_summary = "## Project Architecture Blueprint\n"
            total_files = len(file_handler.files)

            progress_bar = st.progress(0)
            status_box = st.empty()

            for idx, file_path in enumerate(file_handler.files):
                content = file_handler.read_file_content(file_path)
                rel_path = os.path.relpath(file_path, tmp_dir)

                with st.spinner(f"Analyzing {rel_path}..."):
                    summary = analysis_agent.summarize_file(rel_path, content)
                    consolidated_summary = consolidation_agent.merge_summaries(consolidated_summary, summary)

                progress = (idx + 1) / total_files
                progress_bar.progress(progress)

                status_text = f"""
**Processing:** `{rel_path}`  
**Progress:** {idx + 1}/{total_files} ({progress * 100:.1f}%)  
**Summary Size:** {len(consolidated_summary) // 1024} KB
"""
                status_box.markdown(status_text)

            # Final Report Output
            if analysis_mode == "Technical Blueprint":
                st.subheader("📄 Architectural Analysis Report")
                tab1, tab2 = st.tabs(["Full Report", "Key Components"])
                with tab1:
                    st.markdown(consolidated_summary)
                with tab2:
                    st.markdown("### 🏗️ Architectural Pillars")
                    for file_path in file_handler.files[:10]:
                        st.markdown(f"- `{os.path.basename(file_path)}`")
            elif analysis_mode == "Improvement Plan":
                st.subheader("⚙️ Improvement Plan")
                with st.spinner("Generating improvement plan..."):
                    improvements = improvement_agent.generate_improvements(consolidated_summary)
                st.markdown(improvements)


            elif analysis_mode == "Cloud Architecture Diagram":
                st.subheader("🌥️ Cloud Architecture Diagram")
                with st.spinner("Generating cloud architecture description and diagram..."):
                    architecture_description, diagram_data = diagram_agent.generate_architecture_description(
                        consolidated_summary
                    )

                st.markdown(architecture_description)

                if diagram_data:
                    render_architecture_diagram(diagram_data)
                else:
                    st.warning("No cloud architecture diagram generated.")    
        

            with st.expander("🚫 Excluded Items"):
                st.markdown("#### Directories")
                for d in sorted(file_handler.excluded_dirs):
                    st.markdown(f"- `{d}`")
                st.markdown("#### Files")
                for f in sorted(file_handler.excluded_files):
                    st.markdown(f"- `{f}`")

        except GitCommandError as e:
            st.error(f"Cloning failed: {e}")
        except Exception as e:
            st.error(f"Analysis error: {e}")


if __name__ == "__main__":
    main()

this is daigram_render.py
import streamlit as st

# Icon mapping - add more as needed
ICON_MAP = {
    "user": "👤",
    "azure-bot-services": "🤖",
    "azure-openai": "🧠",
    "workflow": "⚙️",
    "search": "🔍",
    "azure-cognitive-search": "🔎",
    "azure-logic-apps": "📱",
    "ticket": "🎫",
    "mail": "✉️",
    "bar-chart-2": "📊",
    "users": "👥",
    "database": "💾",
    "cloud": "☁️",
    "server": "🖥️",
    "api": "🌐",
    "storage": "📦",
    "default": "⬛"
}

def render_architecture_diagram(diagram_data):
    """Render custom architecture diagram with icons and connections"""
    if not diagram_data:
        st.warning("No diagram data to render")
        return
        
    # Display title if exists
    if "title" in diagram_data:
        st.markdown(f"### {diagram_data['title']}")
    
    # Create columns for layout
    col1, col2 = st.columns([1, 3])
    
    # Create component lookup
    components_by_id = {comp["id"]: comp for comp in diagram_data.get("components", [])}
    
    with col1:
        st.markdown("**Components**")
        for comp in diagram_data.get("components", []):
            icon = ICON_MAP.get(comp.get("icon"), ICON_MAP["default"])
            st.markdown(f"{icon} **{comp['label']}**")
    
    with col2:
        st.markdown("**Connections**")
        for connection in diagram_data.get("connections", []):
            source = components_by_id.get(connection["source"])
            target = components_by_id.get(connection["target"])
            
            if source and target:
                source_icon = ICON_MAP.get(source.get("icon"), ICON_MAP["default"])
                target_icon = ICON_MAP.get(target.get("icon"), ICON_MAP["default"])
                
                conn_text = f"{source_icon} **{source['label']}** → {target_icon} **{target['label']}**"
                if connection.get("label"):
                    conn_text += f": *{connection['label']}*"
                    
                st.markdown(conn_text)
    
    # Visual diagram representation
    st.markdown("---")
    st.markdown("### Visual Representation")
    
    # Generate HTML representation
    diagram_html = "<div style='line-height: 2.5; font-size: 24px; padding: 20px;'>"
    
    # Add components in a grid layout
    diagram_html += "<div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px;'>"
    for comp in diagram_data.get("components", []):
        icon = ICON_MAP.get(comp.get("icon"), ICON_MAP["default"])
        diagram_html += f"""
        <div id="{comp['id']}" style="padding: 15px; border: 1px solid #ddd; 
                  border-radius: 8px; text-align: center; background-color: #f9f9f9;">
            <div style="font-size: 32px;">{icon}</div>
            <div style="font-weight: bold; margin-top: 10px;">{comp['label']}</div>
        </div>
        """
    diagram_html += "</div>"
    
    # Add connections as arrows
    diagram_html += "<div style='position: relative; height: 100px; margin: 40px 0;'>"
    for i, connection in enumerate(diagram_data.get("connections", [])):
        diagram_html += f"""
        <div style="position: absolute; left: 20%; top: {20 + i*60}px; 
                  width: 60%; display: flex; align-items: center;">
            <div style="border-top: 2px dashed #666; width: 100%;"></div>
            <div style="background: white; padding: 0 10px; font-size: 16px;">
                {connection.get('label', '')}
            </div>
        </div>
        """
    diagram_html += "</div></div>"
    
    st.markdown(diagram_html, unsafe_allow_html=True)

this is file handler
import os
import tiktoken
import streamlit as st

class FileHandler:
    def __init__(self, repo_path: str, agent):
        self.repo_path = repo_path
        self.agent = agent
        self.files = []
        self.excluded_dirs = set()
        self.excluded_files = []
        self.total_scanned = 0
        self.encoder = tiktoken.get_encoding("cl100k_base")
        self._load_files()

    def _load_files(self):
        excluded_dir_names = {
            ".git", ".github", "node_modules", "venv", "dist", "build",
            "__pycache__", "bin", "obj", "packages", "lib", "libs",
            "third_party", "dependencies", ".idea", ".vscode", "coverage"
        }

        for root, dirs, files in os.walk(self.repo_path):
            original_dirs = set(dirs)
            dirs[:] = [d for d in dirs if d not in excluded_dir_names]

            for excluded_dir in (original_dirs - set(dirs)):
                excluded_path = os.path.join(root, excluded_dir)
                self.excluded_dirs.add(os.path.relpath(excluded_path, self.repo_path))

            for f in files:
                self.total_scanned += 1
                full_path = os.path.join(root, f)
                content = self.read_file_content(full_path)
                
                if not content:
                    self.excluded_files.append(os.path.relpath(full_path, self.repo_path))
                    continue
                
                try:
                    should_analyze = self.agent.should_analyze_file(
                        os.path.relpath(full_path, self.repo_path),
                        content[:2000]
                    )
                except Exception as e:
                    st.error(f"Error analyzing {full_path}: {e}")
                    should_analyze = False
                
                if should_analyze:
                    self.files.append(full_path)
                else:
                    self.excluded_files.append(os.path.relpath(full_path, self.repo_path))

    def read_file_content(self, file_path):
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                return f.read()
        except UnicodeDecodeError:
            try:
                with open(file_path, "r", encoding="iso-8859-1") as f:
                    return f.read()
            except Exception:
                return None
        except Exception:
            return None

this is daigram agent.py
import streamlit as st
import json
from datetime import datetime
from pathlib import Path

class DiagramAgent:
    def __init__(self, client, deployment, prompt_path):
        self.client = client
        self.deployment = deployment
        self.activity_log = []
        self.prompt = Path(prompt_path).read_text()

    def log_activity(self, action):
        timestamp = datetime.now().strftime("%H:%M:%S")
        log_entry = f"{timestamp} | {'Diagram Agent'.ljust(15)} | {action.ljust(25)}"
        self.activity_log.append(log_entry)

    def generate_architecture_description(self, consolidated_summary):
        self.log_activity("Generating architecture description")
        try:
            response = self.client.chat.completions.create(
                model=self.deployment,
                messages=[
                    {
                        "role": "system", 
                        "content": self.prompt
                    },
                    {
                        "role": "user", 
                        "content": (
                            f"Project Summary:\n{consolidated_summary}\n\n"
                            "Generate a structured JSON representation of the cloud architecture with "
                            "components and connections. Use ONLY the specified JSON format."
                        )
                    }
                ],
                temperature=0.1,
                max_tokens=2000,
                response_format={"type": "json_object"}
            )
        except Exception as e:
            st.error(f"DiagramAgent API call failed: {e}")
            return "", None  # Return None for diagram data

        content = response.choices[0].message.content
        
        try:
            # The response should be pure JSON due to response_format
            diagram_data = json.loads(content)
            
            # Extract description if exists
            description = diagram_data.get("description", "")
            if description:
                description = "## Architecture Description\n" + description
                
            return description, diagram_data
        except json.JSONDecodeError as e:
            st.error(f"Failed to parse diagram data: {e}")
            st.text_area("Raw Response", content, height=300)
            return content, None

this is daigram prompt
You are an expert in cloud architecture and diagramming. Based on the provided project summary, create:

1. A clear, concise description of the cloud architecture flow and major components
2. A structured JSON representation of the architecture with components and connections

Output MUST follow this exact JSON format:

{
  "title": "Architecture Title",
  "description": "Detailed architecture description...",
  "components": [
    {
      "id": "unique_id_1",
      "label": "Component Name",
      "icon": "icon_key"
    },
    // More components...
  ],
  "connections": [
    {
      "source": "source_component_id",
      "target": "target_component_id",
      "label": "Connection description"
    },
    // More connections...
  ]
}

Guidelines:
- Use ONLY valid JSON format
- For icons, use these keys: user, azure-bot-services, azure-openai, workflow, 
  search, azure-cognitive-search, azure-logic-apps, ticket, mail, bar-chart-2, 
  users, database, cloud, server, api, storage
- Components should represent key services/nodes in the architecture
- Connections should show relationships with clear labels
- Include ALL critical components and connections
- Make the title descriptive and concise

getting this error right now
Analysis error: the JSON object must be str, bytes or bytearray, not dict

can you correct my code give me proper one