add another tab called Arxiv Retriver for that I wanna use this Arxiv Retriver:
ArxivRetriever

    arXiv is an open-access archive for 2 million scholarly articles in the fields of physics, mathematics, computer science, quantitative biology, quantitative finance, statistics, electrical engineering and systems science, and economics.

This notebook shows how to retrieve scientific articles from Arxiv.org into the Document format that is used downstream.

For detailed documentation of all ArxivRetriever features and configurations head to the API reference.
Integration details
Retriever	Source	Package
ArxivRetriever	Scholarly articles on arxiv.org	langchain_community
Setup

If you want to get automated tracing from individual queries, you can also set your LangSmith API key by uncommenting below:

# os.environ["LANGSMITH_API_KEY"] = getpass.getpass("Enter your LangSmith API key: ")
# os.environ["LANGSMITH_TRACING"] = "true"

Installation

This retriever lives in the langchain-community package. We will also need the arxiv dependency:

%pip install -qU langchain-community arxiv

Instantiation

ArxivRetriever parameters include:

    optional load_max_docs: default=100. Use it to limit number of downloaded documents. It takes time to download all 100 documents, so use a small number for experiments. There is a hard limit of 300 for now.
    optional load_all_available_meta: default=False. By default only the most important fields downloaded: Published (date when document was published/last updated), Title, Authors, Summary. If True, other fields also downloaded.
    get_full_documents: boolean, default False. Determines whether to fetch full text of documents.

See API reference for more detail.

from langchain_community.retrievers import ArxivRetriever

retriever = ArxivRetriever(
    load_max_docs=2,
    get_ful_documents=True,
)

API Reference:ArxivRetriever
Usage

ArxivRetriever supports retrieval by article identifier:

docs = retriever.invoke("1605.08386")

docs[0].metadata  # meta-information of the Document

{'Entry ID': 'http://arxiv.org/abs/1605.08386v1',
 'Published': datetime.date(2016, 5, 26),
 'Title': 'Heat-bath random walks with Markov bases',
 'Authors': 'Caprice Stanley, Tobias Windisch'}

docs[0].page_content[:400]  # a content of the Document

'Graphs on lattice points are studied whose edges come from a finite set of\nallowed moves of arbitrary length. We show that the diameter of these graphs on\nfibers of a fixed integer matrix can be bounded from above by a constant. We\nthen study the mixing behaviour of heat-bath random walks on these graphs. We\nalso state explicit conditions on the set of moves so that the heat-bath random\nwalk, a ge'

ArxivRetriever also supports retrieval based on natural language text:

docs = retriever.invoke("What is the ImageBind model?")

docs[0].metadata

{'Entry ID': 'http://arxiv.org/abs/2305.05665v2',
 'Published': datetime.date(2023, 5, 31),
 'Title': 'ImageBind: One Embedding Space To Bind Them All',
 'Authors': 'Rohit Girdhar, Alaaeldin El-Nouby, Zhuang Liu, Mannat Singh, Kalyan Vasudev Alwala, Armand Joulin, Ishan Misra'}

Use within a chain

Like other retrievers, ArxivRetriever can be incorporated into LLM applications via chains.

We will need a LLM or chat model:

    OpenAI
    Anthropic
    Azure
    Google
    Cohere
    NVIDIA
    FireworksAI
    Groq
    MistralAI
    TogetherAI

pip install -qU langchain-openai

import getpass
import os

os.environ["OPENAI_API_KEY"] = getpass.getpass()

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough

prompt = ChatPromptTemplate.from_template(
    """Answer the question based only on the context provided.

Context: {context}

Question: {question}"""
)


def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)


chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

API Reference:StrOutputParser | ChatPromptTemplate | RunnablePassthrough

chain.invoke("What is the ImageBind model?")

'The ImageBind model is an approach to learn a joint embedding across six different modalities - images, text, audio, depth, thermal, and IMU data. It shows that only image-paired data is sufficient to bind the modalities together and can leverage large scale vision-language models for zero-shot capabilities and emergent applications such as cross-modal retrieval, composing modalities with arithmetic, cross-modal detection and generation.'




here is the code you need to implement that into. make sure to always show what papers where used.
now output the full updated code. this is my code so far:
import os
import json
import shutil
import gradio as gr
from itertools import chain

# Import necessary modules
import arxiv  # New: Import the arxiv library
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_community.embeddings.sentence_transformer import SentenceTransformerEmbeddings
from langchain_unstructured import UnstructuredLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain.document_loaders import PyPDFLoader  # New: To load PDFs
from langchain.document_loaders import DirectoryLoader, UnstructuredURLLoader
from langchain.tools import DuckDuckGoSearchRun
from langchain_ollama import ChatOllama

# For LangSmith tracing
from langsmith import traceable
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "<TRUFFLEHOG_REDACTED_LANGSMITH>"  # Replace with your actual API key
os.environ["LANGCHAIN_PROJECT"] = "l"  # Optional: "default" is used if not set

# Initialize embeddings
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")

# Knowledge Base Vector Store
persist_directory = "./chroma_db"
vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)

# arXiv Vector Store (Separate)
arxiv_persist_directory = "./arxiv_chroma_db"
arxiv_vectorstore = Chroma(persist_directory=arxiv_persist_directory, embedding_function=embeddings)

# Ensure the 'uploads' directories exist
if not os.path.exists("uploads"):
    os.makedirs("uploads")
if not os.path.exists("arxiv_uploads"):
    os.makedirs("arxiv_uploads")

# Function to load or initialize knowledge base JSON
def load_knowledge_base_json():
    if os.path.exists("knowledge_base.json"):
        with open("knowledge_base.json", "r") as f:
            knowledge_base_data = json.load(f)
    else:
        knowledge_base_data = {"files": [], "urls": []}
    return knowledge_base_data

knowledge_base_data = load_knowledge_base_json()

# Function to save knowledge base JSON
def save_knowledge_base_json():
    with open("knowledge_base.json", "w") as f:
        json.dump(knowledge_base_data, f)

# Function to get processed files
def get_processed_files():
    files = os.listdir("uploads")
    return "### Processed Files:\n" + "\n".join(files) if files else "No files processed yet."

# Function to display processed files and URLs
def display_processed_items():
    global knowledge_base_data
    files_text = get_processed_files()
    urls = knowledge_base_data.get("urls", [])
    display_text = files_text + "\n\n### Processed URLs:\n"
    if urls:
        display_text += "\n".join(urls)
    else:
        display_text += "No URLs processed yet."
    return display_text

# Function to update the knowledge base
@traceable
def update_knowledge_base(uploaded_files, website_urls):
    global knowledge_base_data
    if not uploaded_files and not website_urls:
        return "No new documents or URLs to process.", display_processed_items()

    processed_files = []
    skipped_files = []
    docs = []
    new_urls = []

    # Process uploaded files
    if uploaded_files:
        for file in uploaded_files:
            file_path = file.name
            destination_path = os.path.join("uploads", os.path.basename(file_path))
            file_name = os.path.basename(file_path)

            if os.path.exists(destination_path) or file_name in knowledge_base_data["files"]:
                skipped_files.append(file_name)
            else:
                try:
                    shutil.move(file_path, destination_path)
                    processed_files.append(file_name)
                except Exception as e:
                    print(f"Error moving file {file_name}: {str(e)}")

    # Load documents from uploaded files
    if processed_files:
        loader = DirectoryLoader("uploads", glob="**/*")
        documents = loader.load()
        docs.extend(documents)

    # Load documents from URLs
    if website_urls:
        urls = website_urls.split(',')
        for url in urls:
            url = url.strip()
            if url and url not in knowledge_base_data["urls"]:
                loader = UnstructuredURLLoader(urls=[url])
                try:
                    loaded_docs = loader.load()
                    docs.extend(loaded_docs)
                    new_urls.append(url)
                except Exception as e:
                    print(f"Error loading URL {url}: {str(e)}")
            else:
                skipped_files.append(url)

    # Split documents and add to vector store
    if docs:
        text_splitter = CharacterTextSplitter(chunk_size=2500, chunk_overlap=200, length_function=len)
        split_docs = text_splitter.split_documents(docs)
        vectorstore.add_documents(split_docs)
        vectorstore.persist()
        # Update knowledge base data and save to JSON
        knowledge_base_data["files"].extend(processed_files)
        knowledge_base_data["urls"].extend(new_urls)
        save_knowledge_base_json()
        status_message = f"Processed {len(processed_files)} new files and {len(new_urls)} new URLs."
        if skipped_files:
            status_message += f"\nSkipped existing files/URLs: {', '.join(skipped_files)}"
    else:
        status_message = "No new documents or URLs were added."
    return status_message, display_processed_items()

# Function to clear database and uploaded files
def clear_database():
    global vectorstore, knowledge_base_data

    try:
        vectorstore._client._client.close()
    except AttributeError:
        pass

    shutil.rmtree("uploads", ignore_errors=True)
    os.makedirs("uploads", exist_ok=True)

    shutil.rmtree(persist_directory, ignore_errors=True)

    vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)

    # Clear the knowledge base data
    knowledge_base_data = {"files": [], "urls": []}
    # Remove the JSON file
    if os.path.exists("knowledge_base.json"):
        os.remove("knowledge_base.json")

    return "Knowledge base and uploaded files cleared.", display_processed_items()

# Function to process uploaded files in the chat and include their content
def process_uploaded_files(files):
    content = ""
    if files:
        for file in files:
            if file.name.endswith((".pdf", ".txt", ".docx")):
                loader = UnstructuredLoader(file_path=file.name)
                try:
                    docs = loader.load()
                    content += "\n".join([doc.page_content for doc in docs])
                except Exception as e:
                    print(f"Error processing file {file.name}: {str(e)}")
    return content

# Function to count tokens
def count_tokens(text):
    return len(text.split())  # Simple token count; adjust with tokenizer if needed

# Initialize Llama model
llm = ChatOllama(
    model="llama3.1",
    temperature=0,
    base_url="http://localhost:11434",
    streaming=True
)

# Maximum token limit
MAX_TOKENS = 128000  # Example maximum token limit

# Chat with Llama (normal chat) function
@traceable
def chat_with_llama(message, history, uploaded_files):
    # Process uploaded files and include their content
    file_content = process_uploaded_files(uploaded_files)
    # Convert history to list of messages
    messages = [SystemMessage(content="You are a helpful assistant.")]
    if history:
        for user_msg, assistant_msg in history:
            if user_msg:
                messages.append(HumanMessage(content=user_msg))
                if assistant_msg is not None:
                    messages.append(AIMessage(content=assistant_msg))
    # Append file content to user message if any
    if file_content:
        message += f"\n\nContent files Uploaded:\n{file_content}"
        message += "\n\nWith that knowledge answer to the question as best as possible: "
    messages.append(HumanMessage(content=message))
    # Check if the total tokens exceed the maximum allowed tokens
    total_tokens = sum(count_tokens(msg.content) for msg in messages)
    if total_tokens > MAX_TOKENS:
        # Truncate the history to fit within the token limit
        while total_tokens > MAX_TOKENS and len(messages) > 2:
            # Remove the oldest user-assistant message pair
            messages.pop(1)
            messages.pop(1)
            total_tokens = sum(count_tokens(msg.content) for msg in messages)
        if total_tokens > MAX_TOKENS:
            return "Error: The conversation exceeds the maximum token limit. Please start a new chat."
    # Stream the response from the model
    try:
        response_stream = llm.stream(messages)
        partial_response = ""
        for chunk in response_stream:
            partial_response += chunk.content
            yield partial_response
    except Exception as e:
        yield f"Error: {str(e)}"

# Function to generate search queries
def generate_search_queries(message):
    query_generation_prompt = PromptTemplate(
        input_variables=["user_message"],
        template="""
Given the user's message:
"{user_message}"

Generate two distinct search queries that would help retrieve the most relevant documents from the knowledge base to answer the user's question. Be very simple. For a question like "What is the capital of France?" just do a query with "capital" and "France capital".

Provide the search queries as a JSON list, for example:
["search query 1", "search query 2"]
"""
    )
    llm_chain = LLMChain(llm=llm, prompt=query_generation_prompt)
    search_queries_json = llm_chain.run(user_message=message)
    # Parse the JSON output
    try:
        search_queries = json.loads(search_queries_json)
    except json.JSONDecodeError:
        # If parsing fails, default to using the original message
        search_queries = [message]
    if not isinstance(search_queries, list):
        search_queries = [search_queries]
    return search_queries

# Updated Function to handle knowledge base Q&A
@traceable
def knowledge_base_chat(message, history):
    # Generate search queries
    search_queries = generate_search_queries(message)
    # Perform retrieval for each generated query
    all_results = []
    for query in search_queries:
        results = vectorstore.similarity_search(query, k=4)
        all_results.extend(results)
    if not all_results:
        yield "No relevant information found in the knowledge base."
        return
    # Remove duplicate documents
    unique_results = list({doc.page_content: doc for doc in all_results}.values())
    # Use the LLM to extract important information
    extraction_prompt = PromptTemplate(
        input_variables=["user_message", "document"],
        template="""
Given the user's message:
"{user_message}"

And the following document:

{document}

Extract important information that could help to answer the question. Respond with the relevant parts of the document in original text, omitting irrelevant sections.
"""
    )
    extracted_info_list = []
    extraction_chain = LLMChain(llm=llm, prompt=extraction_prompt)
    for doc in unique_results:
        extracted_info = extraction_chain.run(user_message=message, document=doc.page_content)
        extracted_info_list.append(extracted_info)
    # Combine the extracted information
    context = "\n\n".join(extracted_info_list)
    # Build the conversation history
    messages = [SystemMessage(content="You are a helpful assistant that answers questions based on the provided context.")]
    if history:
        for user_msg, assistant_msg in history:
            if user_msg:
                messages.append(HumanMessage(content=user_msg))
                if assistant_msg is not None:
                    messages.append(AIMessage(content=assistant_msg))
    # Add the latest message with context
    prompt = f"Context:\n{context}\n\nQuestion:\n{message}\n\nAnswer:"
    messages.append(HumanMessage(content=prompt))
    # Stream the response from the model
    try:
        response_stream = llm.stream(messages)
        partial_response = ""
        for chunk in response_stream:
            partial_response += chunk.content
            yield partial_response
    except Exception as e:
        yield f"Error: {str(e)}"

# Function for the Internet-Enabled LLM
@traceable
def internet_access_llm(message, history, use_wikipedia):
    # Generate a query from the user input
    query = message
    # Search the internet
    search_tool = DuckDuckGoSearchRun()
    search_results = search_tool.run(query)
    # Optionally search Wikipedia
    if use_wikipedia:
        wikipedia_tool = DuckDuckGoSearchRun(backend="wikipedia")
        wiki_results = wikipedia_tool.run(query)
        search_results += "\n\n" + wiki_results
    # Build the conversation history
    messages = [SystemMessage(content="You are an assistant that uses internet search results to provide accurate answers.")]
    if history:
        for user_msg, assistant_msg in history:
            if user_msg:
                messages.append(HumanMessage(content=user_msg))
                if assistant_msg is not None:
                    messages.append(AIMessage(content=assistant_msg))
    # Add the latest message with search results
    prompt = f"Search Results:\n{search_results}\n\nQuestion:\n{message}\n\nAnswer:"
    messages.append(HumanMessage(content=prompt))
    # Stream the response from the model
    try:
        response_stream = llm.stream(messages)
        partial_response = ""
        for chunk in response_stream:
            partial_response += chunk.content
            yield partial_response
    except Exception as e:
        yield f"Error: {str(e)}"

# Function to retrieve arXiv papers and update arXiv knowledge base
@traceable
def arxiv_research(arxiv_query, max_papers, uploaded_files, arxiv_ids_or_links):
    if not arxiv_query and not uploaded_files and not arxiv_ids_or_links:
        return "No query, files, or IDs provided.", display_arxiv_items()

    processed_papers = []
    skipped_papers = []
    docs = []

    # Search arXiv using the query
    if arxiv_query:
        search = arxiv.Search(query=arxiv_query, max_results=int(max_papers))
        for result in search.results():
            paper_id = result.get_short_id()
            paper_title = result.title
            pdf_url = result.pdf_url
            # Check if paper is already processed
            if paper_id in knowledge_base_data.get("arxiv_papers", []):
                skipped_papers.append(paper_title)
                continue
            # Download PDF
            try:
                paper = next(arxiv.Client().results(arxiv.Search(id_list=[paper_id])))
                pdf_path = paper.download_pdf(dirpath="arxiv_uploads")
                # Extract text
                loader = PyPDFLoader(pdf_path)
                paper_doc = loader.load()
                docs.extend(paper_doc)
                processed_papers.append(paper_title)
                # Save paper info
                knowledge_base_data.setdefault("arxiv_papers", []).append(paper_id)
            except Exception as e:
                print(f"Error downloading or processing paper {paper_title}: {str(e)}")

    # Process uploaded files
    if uploaded_files:
        for file in uploaded_files:
            file_path = file.name
            destination_path = os.path.join("arxiv_uploads", os.path.basename(file_path))
            file_name = os.path.basename(file_path)
            if os.path.exists(destination_path) or file_name in knowledge_base_data.get("arxiv_files", []):
                skipped_papers.append(file_name)
            else:
                try:
                    shutil.move(file_path, destination_path)
                    loader = PyPDFLoader(destination_path)
                    paper_doc = loader.load()
                    docs.extend(paper_doc)
                    processed_papers.append(file_name)
                    knowledge_base_data.setdefault("arxiv_files", []).append(file_name)
                except Exception as e:
                    print(f"Error processing file {file_name}: {str(e)}")

    # Process arXiv IDs or URLs
    if arxiv_ids_or_links:
        ids_or_links = arxiv_ids_or_links.split(',')
        for item in ids_or_links:
            item = item.strip()
            if not item:
                continue
            # Extract arXiv ID
            if 'arxiv.org' in item:
                paper_id = item.split('/')[-1]
            else:
                paper_id = item
            if paper_id in knowledge_base_data.get("arxiv_papers", []):
                skipped_papers.append(paper_id)
                continue
            # Download and process paper
            try:
                paper = next(arxiv.Client().results(arxiv.Search(id_list=[paper_id])))
                pdf_path = paper.download_pdf(dirpath="arxiv_uploads")
                loader = PyPDFLoader(pdf_path)
                paper_doc = loader.load()
                docs.extend(paper_doc)
                processed_papers.append(paper.title)
                knowledge_base_data.setdefault("arxiv_papers", []).append(paper_id)
            except Exception as e:
                print(f"Error processing paper {paper_id}: {str(e)}")

    # Split documents and add to arXiv vector store
    if docs:
        text_splitter = CharacterTextSplitter(chunk_size=2500, chunk_overlap=200, length_function=len)
        split_docs = text_splitter.split_documents(docs)
        arxiv_vectorstore.add_documents(split_docs)
        arxiv_vectorstore.persist()
        # Save knowledge base data
        save_knowledge_base_json()
        status_message = f"Processed {len(processed_papers)} new papers."
        if skipped_papers:
            status_message += f"\nSkipped existing papers: {', '.join(skipped_papers)}"
    else:
        status_message = "No new papers were added."
    return status_message, display_arxiv_items()

# Function to display processed arXiv items
def display_arxiv_items():
    arxiv_files = os.listdir("arxiv_uploads")
    arxiv_papers = knowledge_base_data.get("arxiv_papers", [])
    display_text = "### Processed arXiv Papers:\n"
    if arxiv_papers:
        display_text += "\n".join(arxiv_papers)
    else:
        display_text += "No arXiv papers processed yet."
    display_text += "\n\n### Uploaded Files:\n"
    if arxiv_files:
        display_text += "\n".join(arxiv_files)
    else:
        display_text += "No files uploaded yet."
    return display_text

# Function to allow users to download papers
def download_papers():
    files = os.listdir("arxiv_uploads")
    file_paths = [os.path.join("arxiv_uploads", f) for f in files]
    return file_paths

# Function to handle arXiv Knowledge Base Q&A
@traceable
def ask_arxiv_knowledge_base(message, history):
    # Generate search queries
    search_queries = generate_search_queries(message)
    # Perform retrieval
    all_results = []
    for query in search_queries:
        results = arxiv_vectorstore.similarity_search(query, k=4)
        all_results.extend(results)
    if not all_results:
        yield "No relevant information found in the arXiv knowledge base."
        return
    # Remove duplicates
    unique_results = list({doc.page_content: doc for doc in all_results}.values())
    # Use LLM to extract information
    extraction_prompt = PromptTemplate(
        input_variables=["user_message", "document"],
        template="""
Given the user's message:
"{user_message}"

And the following document:

{document}

Extract important information that could help to answer the question. Respond with the relevant parts of the document in original text, omitting irrelevant sections.
"""
    )
    extracted_info_list = []
    extraction_chain = LLMChain(llm=llm, prompt=extraction_prompt)
    for doc in unique_results:
        extracted_info = extraction_chain.run(user_message=message, document=doc.page_content)
        extracted_info_list.append(extracted_info)
    # Combine the extracted information
    context = "\n\n".join(extracted_info_list)
    # Build the conversation history
    messages = [SystemMessage(content="You are a helpful assistant that answers questions based on arXiv research papers.")]
    if history:
        for user_msg, assistant_msg in history:
            if user_msg:
                messages.append(HumanMessage(content=user_msg))
                if assistant_msg is not None:
                    messages.append(AIMessage(content=assistant_msg))
    # Add the latest message with context
    prompt = f"Context:\n{context}\n\nQuestion:\n{message}\n\nAnswer:"
    messages.append(HumanMessage(content=prompt))
    # Stream the response from the model
    try:
        response_stream = llm.stream(messages)
        partial_response = ""
        for chunk in response_stream:
            partial_response += chunk.content
            yield partial_response
    except Exception as e:
        yield f"Error: {str(e)}"

# Gradio interface
with gr.Blocks() as demo:
    gr.Markdown("# Multi-Tab Gradio App with Llama and Similarity Search")

    # Chat with Llama Tab
    with gr.Tab("Chat with Llama"):
        gr.Markdown("## Chat Interface")
        with gr.Row():
            upload_files = gr.File(
                label="Upload PDFs, DOCX or TXT files",
                file_types=[".pdf", ".txt", ".docx"],
                file_count="multiple",
                visible=True
            )
        gr_chat = gr.ChatInterface(
            fn=chat_with_llama,
            title="Chat with Llama",
            additional_inputs=[upload_files]
        )

    # Knowledge Base Tab
    with gr.Tab("Knowledge Base"):
        gr.Markdown("## Knowledge Base Management and Chat")
        with gr.Row():
            with gr.Column(scale=1):
                gr.Markdown("### Update Knowledge Base")
                kb_upload_files = gr.File(
                    label="Upload Files",
                    file_types=[".pdf", ".txt", ".docx"],
                    file_count="multiple"
                )
                kb_website_urls = gr.Textbox(
                    label="Website URLs",
                    placeholder="Enter comma-separated URLs"
                )
                kb_update_button = gr.Button("Update Knowledge Base")
                kb_clear_button = gr.Button("Clear Database")
                kb_update_status = gr.Markdown()
                kb_update_button.click(
                    update_knowledge_base,
                    inputs=[kb_upload_files, kb_website_urls],
                    outputs=[kb_update_status, kb_update_status]
                )
                kb_clear_button.click(
                    clear_database,
                    outputs=[kb_update_status, kb_update_status]
                )
                gr.Markdown("### Processed Files and URLs")
                processed_items_display = gr.Markdown(display_processed_items())
                # Update the display when the knowledge base is updated or cleared
                kb_update_button.click(
                    update_knowledge_base,
                    inputs=[kb_upload_files, kb_website_urls],
                    outputs=[kb_update_status, processed_items_display]
                )
                kb_clear_button.click(
                    clear_database,
                    outputs=[kb_update_status, processed_items_display]
                )
            with gr.Column(scale=2):
                gr.Markdown("### Chat with Knowledge Base")
                gr_chat_kb = gr.ChatInterface(
                    fn=knowledge_base_chat,
                    title="Knowledge Base Chat"
                )

    # LLM with Internet Access Tab
    with gr.Tab("LLM with Internet Access"):
        gr.Markdown("## Internet-Enabled LLM")
        use_wikipedia = gr.Checkbox(label="Use Wikipedia")
        gr_chat_internet = gr.ChatInterface(
            fn=internet_access_llm,
            title="LLM with Internet Access",
            additional_inputs=[use_wikipedia]
        )

    # arXiv Research Papers Tab
    with gr.Tab("arXiv Research Papers"):
        gr.Markdown("## Retrieve arXiv Papers and Chat")
        with gr.Row():
            with gr.Column(scale=1):
                gr.Markdown("### Retrieve/Add arXiv Papers")
                arxiv_query = gr.Textbox(
                    label="Search Query",
                    placeholder="Enter your arXiv search query"
                )
                max_papers = gr.Slider(
                    label="Number of Papers",
                    minimum=1,
                    maximum=10,
                    step=1,
                    value=5
                )
                arxiv_upload_files = gr.File(
                    label="Upload PDF Files",
                    file_types=[".pdf"],
                    file_count="multiple"
                )
                arxiv_ids_or_links = gr.Textbox(
                    label="arXiv IDs or URLs",
                    placeholder="Enter comma-separated arXiv IDs or URLs"
                )
                arxiv_button = gr.Button("Retrieve/Add Papers")
                arxiv_result = gr.Markdown()
                arxiv_button.click(
                    arxiv_research,
                    inputs=[arxiv_query, max_papers, arxiv_upload_files, arxiv_ids_or_links],
                    outputs=[arxiv_result, arxiv_result]
                )
                gr.Markdown("### Processed arXiv Papers")
                processed_items_display_arxiv = gr.Markdown(display_arxiv_items())
                # Update the display when new papers are added
                arxiv_button.click(
                    arxiv_research,
                    inputs=[arxiv_query, max_papers, arxiv_upload_files, arxiv_ids_or_links],
                    outputs=[arxiv_result, processed_items_display_arxiv]
                )
                gr.Markdown("### Download Papers")
                download_button = gr.Button("Download Papers")
                download_files = gr.File()
                download_button.click(
                    download_papers,
                    outputs=[download_files]
                )
            with gr.Column(scale=2):
                gr.Markdown("### Chat with arXiv Knowledge Base")
                gr_chat_arxiv = gr.ChatInterface(
                    fn=ask_arxiv_knowledge_base,
                    title="arXiv Knowledge Base Chat"
                )

    # Launch the app
    demo.launch()