diff --git a/ChatManager.py b/ChatManager.py index 31f8823..892e85d 100644 --- a/ChatManager.py +++ b/ChatManager.py @@ -19,8 +19,7 @@ def get_client() -> OpenAI: client = get_client() def send_message(message: str) -> str: - # hier gehts darum, die message vorzubereiten - # -> also nachricht aus den Chatfenster mit systemprompts und file_conntext verknüpfen + # Nachricht aus dem Chatfenster wird für qwen vorbereitet und mit Kontext über SystemPrompter ergänzt prompt = generate_prompt(message) response = send_prompt(prompt) return response diff --git a/DebugLogger.py b/DebugLogger.py index 01d5887..409f8be 100644 --- a/DebugLogger.py +++ b/DebugLogger.py @@ -2,7 +2,7 @@ import streamlit as st import subprocess def log_error(error_message): - #speichern wieder über session_state + #speichern des strukturierten outputs von qwen über session_state st.session_state["bugs"] = error_message @@ -20,7 +20,6 @@ def format_debug_output(output: dict) -> str: return "\n".join(lines) -#qwen def format_output(text: bytes) -> str: return text.decode('utf-8', errors='replace').strip() diff --git a/ExecutionEngine.py b/ExecutionEngine.py index 70adbdf..a19cffc 100644 --- a/ExecutionEngine.py +++ b/ExecutionEngine.py @@ -6,6 +6,8 @@ from DebugLogger import log_error, format_output, format_error def run_code(code: Path) -> None | str: + #führt Code aus angeklickter Datei im Editor aus. Sicherheitscheck über verbotene Imports und Funktionen geregelt + if "code_output" in st.session_state: del st.session_state['code_output'] if "code_error" in st.session_state: @@ -29,20 +31,11 @@ def run_code(code: Path) -> None | str: st.session_state['code_error'] = format_error(exc) log_error(st.session_state['code_error']) - -#brauchts diese Funktion noch? macht eigentlich schon run_code -def capture_output(): - errors = st.session_state['code_error'] - #if errors == 0: - # errors = "There are no errors" - return st.session_state['code_output'], errors - - #aus der uebungsloesung def check_code_safety(code: str) -> str | None: - #Analysiert den Code statisch nach verbotenen imports und builtins. + #analysiert den Code statisch nach verbotenen imports und builtins. try: tree = ast.parse(code) except SyntaxError as e: diff --git a/FileManager.py b/FileManager.py index 805a03f..13fbf4e 100644 --- a/FileManager.py +++ b/FileManager.py @@ -15,7 +15,7 @@ def read_file(file_path: Path) -> str: return file.read() def save_file(file_path: Path, content: str): - #wenn eine Datei im Code Editor abgeändert wird, soll die Funktion die Änderungen abspeichern + #wenn eine Datei im Code Editor abgeändert wird und der Save Button gedrückt wurde, speichert Funktion die Änderungen ab with file_path.open('w') as file: file.write(content) diff --git a/SearchManager.py b/SearchManager.py index a257faf..844bbe7 100644 --- a/SearchManager.py +++ b/SearchManager.py @@ -1,7 +1,7 @@ from ddgs import DDGS -import re def perform_search(query: str) -> dict: + #sucht Zusatzinfos im Internet try: results = DDGS().text(query, max_results=3) return results @@ -12,7 +12,8 @@ def perform_search(query: str) -> dict: def parse_results(results: list[dict]) -> list[dict]: - return [ + #Limitiert Inhalt der Suchergebnisse auf 500 Zeichen, damit qwen nicht zu langen Prompt erhält + return [ { "url": r.get("href", ""), # "snippet": r.get("body", "")[:500] diff --git a/SystemPrompter.py b/SystemPrompter.py index eaf5a61..ed9995d 100644 --- a/SystemPrompter.py +++ b/SystemPrompter.py @@ -3,7 +3,7 @@ from SearchManager import perform_search, parse_results def generate_prompt(user_message: str) -> str: - + # der key "debug" im session_state als toggle: wenn an, dann strukturierter output mit feedbackschleife über "bugs" key if "debug" in st.session_state and st.session_state["debug"]: system_content = """\ You are a code-review assistant. You ALWAYS respond with valid JSON and @@ -37,10 +37,11 @@ commentary. Your entire response must be parseable by json.loads(). context += (f" {st.session_state['bugs']} ") prompt = system_content + task + context - #st.session_state["last_prompt"] = (f"{prompt}") + st.session_state["last_prompt"] = (f"{prompt}") return prompt - + else: + # ohne "debug" key im session_state: unstrukturierter output system_content = """\ You are an experienced Python coding assistant. @@ -68,7 +69,7 @@ commentary. Your entire response must be parseable by json.loads(). prompt = system_content + task + context - #st.session_state["last_prompt"] = (f"{prompt}") + st.session_state["last_prompt"] = (f"{prompt}") return prompt diff --git a/app.py b/app.py index d6c6698..5757d1e 100644 --- a/app.py +++ b/app.py @@ -3,28 +3,22 @@ import json import streamlit as st from pathlib import Path from ChatManager import send_message -from FileManager import list_files, read_file, save_file, open_file +from FileManager import list_files, save_file, open_file from ExecutionEngine import run_code from DebugLogger import format_debug_output, log_error -#"Last sent prompt:", -#if "last_prompt" in st.session_state: -# st.session_state["last_prompt"] -#if "file_content" in st.session_state: -# st.session_state["file_content"] - #type(st.session_state["file_content"]) - #print(type(st.session_state["file_content"])) st.set_page_config(layout="wide") +# Initialisierung wichtiger keys im session_state if not "file_content" in st.session_state: st.session_state["file_content"] = "" if not "selected_file" in st.session_state: st.session_state["selected_file"] = "" with st.sidebar: - +# baut den Sidebar aus den vorhanden .py Dateien im Arbeitsverzeichnis for file in list_files(): st.button(file.name, on_click=open_file, args=[file]) @@ -35,7 +29,7 @@ with col_editor_output: if "file_content" in st.session_state: text = st.text_area("Code Editor", height="stretch", key="file_content") - # Preferred pattern for editor + # Oberfläche erhält zusätzliche Buttons, wenn eine Datei geöffnet wird if st.session_state["selected_file"] != "": st.button("Save", on_click=save_file, args=[st.session_state["selected_file"], st.session_state["file_content"]]) st.button("Execute Code", on_click=run_code, args=[st.session_state["selected_file"]]) @@ -55,13 +49,13 @@ with col_chat: if "messages" not in st.session_state: st.session_state.messages = [] - # Display History + # zeigt Chatverlauf als Abschnitt mit eigenem Scrollbalken with st.container(border=True, height=500): for msg in st.session_state.messages: with st.chat_message(msg["role"]): st.write(msg["content"]) - # Input & Response + # regelt Verhalten bei Chat mit qwen. Nach Eingabe wird einer der Promptopitionen gewählt und fertiger Prompt sowie qwen Antwort in die messages gespeichert message = st.chat_input("How can qwen help you?", key="chat") internet = st.toggle("enable internet search", key="internet") if "code_output" in st.session_state or "code_error" in st.session_state: @@ -72,7 +66,6 @@ with col_chat: if "debug" in st.session_state and st.session_state["debug"]: response = format_debug_output(json.loads(response)) log_error(response) - # st.session_state["bugs"] = response st.session_state.messages.append({"role": "assistant", "content": response}) st.rerun() diff --git a/project_description.md b/project_description.md new file mode 100644 index 0000000..d2b7fad --- /dev/null +++ b/project_description.md @@ -0,0 +1,135 @@ +**Lightweight Code Editor with AI integration** + +##1. Introduction + +This code editor was made as a project for the course Artificial Intelligence in Software Engineering I at FH Graubuenden. +Its purpose is to help with coding questions and debugging. + + +##2. Requirements + +streamlit +openai +ddgs +dotenv + +##3. Installation + +- install requirements through pip +- aquire the environment variables thourgh FHGR +- connect to the FHGR VPN to get access to the AI model. +- run the app via terminal with "streamlit run app.py". A browser window will open. + +##4. Architecture + +The code is split up into frontend (app.py) and backend modules (FileManager, ChatManager, SystemPrompter, ExecutionEngine, DebugLogger and SearchManager). +A lot of functionality is realized through the use of the session_state from streamlit. +Since the streamlit app is rerun after each action the session_state is used to keep track of relevant information. + +# 4.1 app.py + +This module is the entrypoint for the streamlit functionality. It contains all elements that are visible when the program is run. + + + - sidebar: + The sidebar shows all python files within the working directory through fuctions found in the FileManager. Each file is represented by a clickable button. + There is no functionality to add or delete files from within the program. To change the available files the user has to manually add them to the directory. + Interacts with FileManger + + - code editor: + The code editor shows the contents of the opened file. The contents can be changed, saved and also executed. + Interacts with FileManager, ExecutionEngine + + - chat: + This element consists of an input area to send messages to the AI, two toggles to enable different modes as well as a scrollable history. + The first toggle is "enable internet search". This appends the results of an internet search to the prompt that is sent to the AI. + This means the results from the search are not primarily for the user, but for the AI to use recent information before responing to the user. + The second toggle "debug with qwen" appears only when a file was executed. This mode uses more structured output which is also sent to the next prompt for the AI. + With this feedback the AI can help with a more focused approach to find and fix bugs in the code. The user has to implement the fixes themself since the AI cannot change the contents of the file. + Interacts with ChatManager, Debuglogger + +# 4.2 FileManager.py + +This module manages the access to the working directory. It uses Path objects. +The open_file function is only used as a helper function to save the file path and contents to the session_state. + +# 4.3 ChatManager.py + +The environment variables are located in a .env file which is excluded from the gitea upload. +The qwen AI is accessed through the openai API client. +While this module sends the messages to the AI it is heavily dependend on the SystemPrompter to supply the relevant context for the finished prompt. +Interacts with SystemPrompter + +# 4.4 SystemPrompter.py + +This module handles all the context to add to the user message sent via the ChatManager. The instructions for the AI are structured with xml tags. +There are two modes which are chosen with a toggle. Both modes differ in the system component and sent context. +When the toggle is off the generated prompt includes system instructions which make the AI act as a coding assistant who gives short responses. +With the toggle on the output is structured with json and focussed on finding bugs in the provided code. The AI's response is also loaded into the session_state. +This means that subsequent messages to the AI keep its own responses therefore having the ability to iterate on them. +Interacts with SearchManager + +# 4.5 ExecutionEngine.py + +With this module the program can execute the opened file after clicking the corresponding button in the streamlit app. +There is a safety check before the subprocess is called to make sure no malicious actions can be made from within the program. +Interacts with Debuglogger + +# 4.6 DebugLogger.py + +This module handles making all outputs from various other module more readable and useable. +It saves the AI's response in the session_state to be able to send it with the next prompt when using the debug toggle. + +# 4.7 SearchManager.py + +This modules uses the duckduckgosearch API to make searching the internet possible. +The user does not directly see the search, but the result is sent with the prompt to the AI. + + +##5. Usage + +The following examples illustrate how to use this program. + +*I want to ask the AI about something in my file* + **user actions**: - click on button with name of the file + - write message to AI + - wait for response + - implement suggestions from AI + + **interaction flow**: - st.sidebar uses list_files() to generate Buttons + - on_click uses open_file() to load content and file Path into session_state + - send_message() uses generate_prompt() to add system_prompt and context = file_content to the user_message + - response is displayed by st.session_state.messages + + +*I want to ask the AI about somehting in my file, but the AI has outdated info about the topic* + **user actions**: - click on button with name of the file + - activate "enable internet search" toggle + - write message to AI + - wait for response + - implement suggestions from AI + + **interaction flow**: - st.sidebar uses list_files() to generate Buttons + - on_click uses open_file() to load content and file Path into session_state + - send_message() uses generate_prompt() to add system_prompt and context = file_content and results from perform_search() to the user_message + - response is displayed by st.session_state.messages + + +*I want to debug my code* + **user actions**: - click on button with name of the file + - click the "execute code" button + - write message to AI + - wait for response + - implement suggestions from AI + - write message to AI + - implement suggestions from AI + + **interaction flow**: - st.sidebar uses list_files() to generate Buttons + - on_click uses open_file() to load content and file Path into session_state + - on_click uses run_code() to execute the file, results are formatted by format_output() or format_error() + - code_output or code_error are loaded into seassion_state + - send_message() uses generate_prompt() to add system_prompt which detects the "debug" toggle and adds context = file_content and code_output / code_error to the user_message + - response is displayed by st.session_state.messages + - send_message() uses generate_prompt() to add system_prompt which detects the "debug" toggle and adds context = file_content and code_output / code_error and debug response to the user_message + - response is displayed by st.session_state.messages + diff --git a/~$praesentation_ai_se.pptx b/~$praesentation_ai_se.pptx new file mode 100644 index 0000000..77e5f90 Binary files /dev/null and b/~$praesentation_ai_se.pptx differ