import gradio as gr import zipfile import os import shutil import subprocess from chat_with_project import query_project from get_prompts import get_prompt_for_mode from dotenv import load_dotenv, set_key from milvus import initialize_milvus, DEFAULT_MILVUS_HOST, DEFAULT_MILVUS_PORT, DEFAULT_COLLECTION_NAME, DEFAULT_DIMENSION, DEFAULT_MAX_RETRIES, DEFAULT_RETRY_DELAY from pymilvus import connections, MilvusException, utility import markdown # --- Configuration and Setup --- # Define paths for workspace and extraction directories WORKSPACE_DIR = "workspace" EXTRACTION_DIR = "extraction" # Milvus connection status - Assume connected initially milvus_connected = True def clear_directories(): """Clears the workspace and extraction directories.""" for directory in [WORKSPACE_DIR, EXTRACTION_DIR]: if os.path.exists(directory): shutil.rmtree(directory) os.makedirs(directory, exist_ok=True) # Clear directories at startup clear_directories() # --- API Key Management --- def ensure_env_file_exists(): """Ensures that a .env file exists in the project root.""" if not os.path.exists(".env"): with open(".env", "w") as f: f.write("") # Create an empty .env file def load_api_key(): """Loads the API key from the .env file or the environment.""" ensure_env_file_exists() load_dotenv() return os.environ.get("OPENAI_API_KEY") def update_api_key(api_key): """Updates the API key in the .env file.""" if api_key: set_key(".env", "OPENAI_API_KEY", api_key) load_dotenv() # Reload environment variables return "API key updated successfully." else: return "API key cannot be empty." def is_api_key_set(): """Checks if the API key is set.""" return bool(load_api_key()) # --- Core Functionalities --- def process_zip(zip_file_path): """Extracts a zip file, analyzes content, and stores information.""" try: # Clear existing workspace and extraction directories before processing clear_directories() # Extract the zip file with zipfile.ZipFile(zip_file_path, 'r') as zip_ref: zip_ref.extractall(WORKSPACE_DIR) # Run extract.py subprocess.run(["python", "./utils/extract.py", WORKSPACE_DIR], check=True) return "Processing complete! Results saved in the 'extraction' directory." except Exception as e: return f"An error occurred: {e}" def init_milvus( milvus_host, milvus_port, collection_name, dimension, max_retries, retry_delay ): """Initializes or loads the Milvus vector database.""" global milvus_connected try: # Convert string inputs to appropriate types milvus_port = int(milvus_port) dimension = int(dimension) max_retries = int(max_retries) retry_delay = int(retry_delay) # Call the modified function success = initialize_milvus( milvus_host, milvus_port, collection_name, dimension, max_retries, retry_delay, ) if success: milvus_connected = True return "Milvus database initialized or loaded successfully." else: milvus_connected = False return "Error initializing Milvus: Unable to establish connection or initialize." except Exception as e: milvus_connected = False return f"Error initializing Milvus: {e}" # --- Chatbot Verification --- def is_project_loaded(): """Checks if a project has been loaded (i.e., if the extraction directory contains .pkl files).""" extraction_dir = "extraction" pkl_files = [f for f in os.listdir(extraction_dir) if f.endswith('.pkl')] return bool(pkl_files) # --- Helper Function for Developer Mode --- def extract_files_from_response(response): """ Parses the LLM response to extract file paths and their corresponding code content. Args: response (str): The raw response string from the LLM. Returns: dict: A dictionary where keys are file paths and values are the code content of each file. """ files = {} current_file = None current_content = [] for line in response.splitlines(): if line.startswith("--- BEGIN FILE:"): if current_file is not None: # Save previous file content files[current_file] = "\n".join(current_content) # Start a new file current_file = line.replace("--- BEGIN FILE:", "").strip() current_content = [] elif line.startswith("--- END FILE:"): if current_file is not None: # Save current file content files[current_file] = "\n".join(current_content) current_file = None current_content = [] elif current_file is not None: # Append line to current file content current_content.append(line) return files # --- Gradio UI Components --- # Chat Interface def chat_ui(query, history, mode): """Handles the chat interaction for Analyzer, Debugger, and Developer modes.""" api_key = load_api_key() if not api_key: return [ ( "Error", "OpenAI API key not set. Please set the API key in the Settings tab.", ) ], [] if not is_project_loaded(): return [ ( "Error", "No project loaded. Please upload and process a ZIP file first.", ) ], [] if not milvus_connected: return [ ("Error", "Milvus is not connected. Please connect to Milvus first.") ], [] # Initialize history if None if history is None: history = [] print(f"Chat Mode: {mode}") system_prompt = get_prompt_for_mode(mode) print(f"System Prompt: {system_prompt}") # Pass the query and system prompt to the LLM response = query_project(query, system_prompt) print(f"Type response {type(response)}") print(f"Response from query_project: {response}") if response is None or not response.strip(): response = "An error occurred during processing. Please check the logs." formatted_response = "" if mode == "developer": extracted_files = extract_files_from_response(response) for filepath, content in extracted_files.items(): formatted_response += f"## {filepath}\n`\n{content}\n`\n\n" else: formatted_response = response # Use HTML directly for code blocks to preserve formatting if mode == "developer": formatted_response = formatted_response.replace("`\n", "
").replace("\n`", "
")
else:
# Convert the entire response to HTML at once
md = markdown.Markdown(extensions=['fenced_code'])
formatted_response = md.convert(formatted_response)
history.append((query, formatted_response))
return history, history
# ZIP Processing Interface
zip_iface = gr.Interface(
fn=process_zip,
inputs=gr.File(label="Upload ZIP File"),
outputs="text",
title="Zip File Analyzer",
description="Upload a zip file to analyze and store its contents.",
)
# Milvus Initialization Interface
milvus_iface = gr.Interface(
fn=init_milvus,
inputs=[
gr.Textbox(
label="Milvus Host",
placeholder=DEFAULT_MILVUS_HOST,
value=DEFAULT_MILVUS_HOST,
),
gr.Textbox(
label="Milvus Port",
placeholder=DEFAULT_MILVUS_PORT,
value=DEFAULT_MILVUS_PORT,
),
gr.Textbox(
label="Collection Name",
placeholder=DEFAULT_COLLECTION_NAME,
value=DEFAULT_COLLECTION_NAME,
),
gr.Textbox(
label="Dimension",
placeholder=str(DEFAULT_DIMENSION),
value=str(DEFAULT_DIMENSION),
),
gr.Textbox(
label="Max Retries",
placeholder=str(DEFAULT_MAX_RETRIES),
value=str(DEFAULT_MAX_RETRIES),
),
gr.Textbox(
label="Retry Delay (seconds)",
placeholder=str(DEFAULT_RETRY_DELAY),
value=str(DEFAULT_RETRY_DELAY),
),
],
outputs="text",
title="Milvus Database Initialization",
description="Initialize or load the Milvus vector database.",
)
# Gradio Chatbot UI Interface
chat_iface = gr.Interface(
fn=chat_ui,
inputs=[
gr.Textbox(label="Ask a question", placeholder="Type your question here"),
gr.State(), # Maintains chat history
gr.Radio(
["analyzer", "debugger", "developer"],
label="Chat Mode",
value="analyzer",
),
],
outputs=[
gr.Chatbot(label="Chat with Project", height=500),
"state", # This is to store the state,
],
title="Chat with your Project",
description="Ask questions about the data extracted from the zip file.",
# Example usage - Corrected to only include instruction and mode
examples=[
["What is this project about?", "analyzer"],
["Are there any potential bugs?", "debugger"],
["How does the data flow through the application?", "analyzer"],
["Explain the main components of the architecture.", "analyzer"],
["What are the dependencies of this project?", "analyzer"],
["Are there any potential memory leaks?", "debugger"],
[
"Identify any areas where the code could be optimized.",
"debugger",
],
[
"Please implement basic logging for the main application and save logs to a file.",
"developer",
],
[
"Can you add a try/except blocks in main functions to handle exceptions",
"developer",
],
],
)
# Settings Interface
settings_iface = gr.Interface(
fn=update_api_key,
inputs=gr.Textbox(label="OpenAI API Key", type="password"),
outputs="text",
title="Settings",
description="Set your OpenAI API key.",
)
# Status Interface
def get_api_key_status():
if is_api_key_set():
return "API key status: Set"
else:
return "API key status: Not set"
def get_milvus_status():
if milvus_connected:
return "Milvus: Connected"
else:
return "Milvus: Disconnected"
status_iface = gr.Interface(
fn=lambda: [get_api_key_status(), get_milvus_status()],
inputs=None,
outputs=["text", "text"],
live=True,
title="Status",
)
# Add credits to the UI
credits = gr.Markdown(
"## Credits\n\nCreated by [Ruslan Magana Vsevolodovna](https://ruslanmv.com/)"
)
# --- Main Application Launch ---
# Combine the interfaces using Tabs
demo = gr.TabbedInterface(
[zip_iface, milvus_iface, chat_iface, settings_iface, status_iface],
["Process ZIP", "Init Milvus", "Chat with Project", "Settings", "Status"],
)
# Launch the app with credits
demo.queue().launch()