Skip to content

Commit

Permalink
Progress toward session continuation
Browse files Browse the repository at this point in the history
  • Loading branch information
dougollerenshaw committed Oct 17, 2024
1 parent da8b9dc commit a0aa3fb
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 82 deletions.
142 changes: 81 additions & 61 deletions codeaide/logic/chat_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
from codeaide.utils.cost_tracker import CostTracker
from codeaide.utils.file_handler import FileHandler
from codeaide.utils.terminal_manager import TerminalManager
from codeaide.utils.general_utils import generate_session_id
from codeaide.utils.logging_config import get_logger, setup_logger
from codeaide.utils.general_utils import get_project_root
from codeaide.utils.logging_config import get_logger
from PyQt5.QtCore import QObject, pyqtSignal
from codeaide.utils.environment_manager import EnvironmentManager
from codeaide.utils.session_manager import SessionManager


class ChatHandler(QObject):
Expand All @@ -31,31 +32,21 @@ class ChatHandler(QObject):
) # Signal to update chat with (role, message)
show_code_signal = pyqtSignal(str, str) # Signal to show code with (code, version)
traceback_occurred = pyqtSignal(str)
session_updated = pyqtSignal()

def __init__(self):
super().__init__()
"""
Initialize the ChatHandler class.
base_dir = get_project_root()
self.file_handler = FileHandler(base_dir=base_dir)
self.session_manager = SessionManager(base_dir, self.file_handler)

Args:
None
self.session_id = None
self.session_dir = None

Returns:
None
"""
self.session_id = generate_session_id()
self.cost_tracker = CostTracker()
self.file_handler = FileHandler(session_id=self.session_id)
self.session_dir = (
self.file_handler.session_dir
) # Store the specific session directory
self.logger = get_logger()
self.conversation_history = self.file_handler.load_chat_history()
self.environment_manager = EnvironmentManager(self.session_id)
self.terminal_manager = TerminalManager(
environment_manager=self.environment_manager,
traceback_callback=self.emit_traceback_signal,
)
self.environment_manager = None
self.terminal_manager = None
self.latest_version = "0.0"
self.api_client = None
self.api_key_set = False
Expand All @@ -66,17 +57,26 @@ def __init__(self):
self.max_tokens = AI_PROVIDERS[self.current_provider]["models"][
self.current_model
]["max_tokens"]
self.env_manager = EnvironmentManager(self.session_id)

self.api_key_valid, self.api_key_message = self.check_api_key()
self.logger.info(f"New session started with ID: {self.session_id}")
self.logger.info(f"Session directory: {self.session_dir}")
self.chat_window = None

def start_application(self):
from codeaide.ui.chat_window import (
ChatWindow,
) # Import here to avoid circular imports
from codeaide.ui.chat_window import ChatWindow

# Create initial session
self.session_id = self.session_manager.create_new_session()
self.session_dir = self.file_handler.session_dir

self.conversation_history = self.file_handler.load_chat_history()
self.environment_manager = EnvironmentManager(self.session_id)
self.terminal_manager = TerminalManager(
environment_manager=self.environment_manager,
traceback_callback=self.emit_traceback_signal,
)

self.logger.info(f"New session started with ID: {self.session_id}")
self.logger.info(f"Session directory: {self.session_dir}")

self.chat_window = ChatWindow(self)
self.connect_signals()
Expand Down Expand Up @@ -333,8 +333,13 @@ def process_ai_response(self, response):
code_version,
version_description,
requirements,
session_summary,
) = parsed_response

# Update the session summary
if session_summary:
self.update_session_summary(session_summary)

if code and self.compare_versions(code_version, self.latest_version) <= 0:
raise ValueError(
f"New version {code_version} is not higher than the latest version {self.latest_version}"
Expand Down Expand Up @@ -567,55 +572,70 @@ def get_latest_version(self):
def set_latest_version(self, version):
self.latest_version = version

def start_new_session(self, chat_window):
self.logger.info("Starting new session")

# Log the previous session path correctly
self.logger.info(f"Previous session path: {self.session_dir}")

# Generate new session ID
new_session_id = generate_session_id()

# Create new FileHandler with new session ID
new_file_handler = FileHandler(session_id=new_session_id)

# Copy existing log to new session and set up new logger
self.file_handler.copy_log_to_new_session(new_session_id)
setup_logger(new_file_handler.session_dir)

# Update instance variables
def start_new_session(self, chat_window, based_on=None, initial_session=False):
new_session_id = self.session_manager.create_new_session(based_on=based_on)
self.session_id = new_session_id
self.file_handler = new_file_handler
self.session_dir = new_file_handler.session_dir # Update the session directory
self.file_handler.set_session_id(new_session_id)
self.session_dir = self.file_handler.session_dir

# Clear conversation history
self.conversation_history = []

# Clear chat display in UI
chat_window.clear_chat_display()

# Close code pop-up if it exists
chat_window.close_code_popup()

# Add system message about previous session
system_message = f"A new session has been started. The previous chat will not be visible to the agent. Previous session data saved in: {self.session_dir}"
chat_window.add_to_chat("System", system_message)
chat_window.add_to_chat("AI", INITIAL_MESSAGE)
if not initial_session:
# Inform the user that a new session is starting if this is done after an existing session was underway
system_message = f"A new session has been started. The previous chat will not be visible to the agent. Previous session data saved in: {self.session_dir}"
chat_window.add_to_chat("System", system_message)
chat_window.add_to_chat("AI", INITIAL_MESSAGE)

self.logger.info(f"New session started with ID: {self.session_id}")
self.logger.info(f"New session directory: {self.session_dir}")

# New method to load a previous session
return new_session_id

def load_previous_session(self, session_id, chat_window):
if session_id is None:
self.logger.error("Attempted to load a session with None id")
raise ValueError("Invalid session id: None")

self.logger.info(f"Loading previous session: {session_id}")
self.session_id = session_id
self.file_handler = FileHandler(session_id=session_id)
new_session_id = self.session_manager.load_previous_session(session_id)
self.session_id = new_session_id
self.file_handler.set_session_id(new_session_id)
self.session_dir = self.file_handler.session_dir

# Load chat contents
chat_window.load_chat_contents()
# Load chat history from the original session
self.conversation_history = self.file_handler.load_chat_history(session_id)

chat_window.clear_chat_display()
# Load chat contents from the original session
chat_contents = self.file_handler.load_chat_contents(session_id)
for content in chat_contents:
if isinstance(content, dict) and "role" in content and "content" in content:
chat_window.add_to_chat(content["role"], content["content"])
elif (
isinstance(content, dict)
and "sender" in content
and "message" in content
):
chat_window.add_to_chat(content["sender"], content["message"])
else:
self.logger.warning(f"Unexpected chat content format: {content}")

system_message = f"A new session has been created based on session {session_id}. Previous session data copied to: {self.session_dir}"
chat_window.add_to_chat("System", system_message)

self.logger.info(f"Loaded previous session with ID: {session_id}")
self.logger.info(f"Created new session with ID: {new_session_id}")

return new_session_id

def update_session_summary(self, summary):
self.session_manager.update_session_summary(summary)
self.session_updated.emit()

self.logger.info(f"Loaded previous session with ID: {self.session_id}")
def get_all_sessions(self):
return self.session_manager.get_all_sessions()

def emit_traceback_signal(self, traceback_text):
self.logger.info(
Expand All @@ -638,4 +658,4 @@ def send_traceback_to_agent(self, traceback_text):
self.chat_window.on_submit()

def cleanup(self):
self.env_manager.cleanup()
self.environment_manager.cleanup()
113 changes: 107 additions & 6 deletions codeaide/ui/chat_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ def __init__(self, chat_handler):
self.timer.timeout.connect(lambda: None)

self.logger.info("Chat window initialized")
self.chat_handler.session_updated.connect(self.update_session_dropdown)

def setup_ui(self):
central_widget = QWidget(self)
Expand All @@ -175,33 +176,51 @@ def setup_ui(self):
main_layout.setSpacing(5)
main_layout.setContentsMargins(8, 8, 8, 8)

# Create a widget for the dropdowns
# Create a widget for the dropdowns (keep this as is)
dropdown_widget = QWidget()
dropdown_layout = QHBoxLayout(dropdown_widget)
dropdown_layout.setContentsMargins(0, 0, 0, 0)
dropdown_layout.setSpacing(5)

# Provider dropdown
# Provider dropdown (keep this as is)
self.provider_dropdown = QComboBox()
self.provider_dropdown.addItems(AI_PROVIDERS.keys())
self.provider_dropdown.setCurrentText(DEFAULT_PROVIDER)
self.provider_dropdown.currentTextChanged.connect(self.update_model_dropdown)
dropdown_layout.addWidget(QLabel("Provider:"))
dropdown_layout.addWidget(self.provider_dropdown)

# Model dropdown
# Model dropdown (keep this as is)
self.model_dropdown = QComboBox()
self.update_model_dropdown(DEFAULT_PROVIDER, add_message_to_chat=False)
self.model_dropdown.currentTextChanged.connect(self.update_chat_handler)
dropdown_layout.addWidget(QLabel("Model:"))
dropdown_layout.addWidget(self.model_dropdown)

# Add stretch to push everything to the left
# Add stretch to push everything to the left (keep this as is)
dropdown_layout.addStretch(1)

# Add the dropdown widget to the main layout
# Add the dropdown widget to the main layout (keep this as is)
main_layout.addWidget(dropdown_widget)

# Create a new widget for the session selector
session_widget = QWidget()
session_layout = QHBoxLayout(session_widget)
session_layout.setContentsMargins(0, 0, 0, 0)
session_layout.setSpacing(5)

# Session selector dropdown
self.session_dropdown = QComboBox()
session_layout.addWidget(QLabel("Session Selector:"))
session_layout.addWidget(self.session_dropdown)
session_layout.addStretch(1) # Add stretch to keep alignment consistent

# Add the session widget to the main layout
main_layout.addWidget(session_widget)

# Now that self.session_dropdown is initialized, we can update it
self.update_session_dropdown()

# Chat display
self.chat_display = QTextEdit(self)
self.chat_display.setReadOnly(True)
Expand Down Expand Up @@ -532,7 +551,7 @@ def on_new_session_clicked(self):

if reply == QMessageBox.Yes:
self.logger.info("User confirmed starting a new session")
self.chat_handler.start_new_session(self)
self.chat_handler.start_new_session(self, initial_session=False)
else:
self.logger.info("User cancelled starting a new session")

Expand Down Expand Up @@ -750,3 +769,85 @@ def scroll_to_bottom(self):
# Scroll to the bottom
scrollbar = self.input_text.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())

def update_session_dropdown(self):
self.session_dropdown.blockSignals(True)
self.session_dropdown.clear()
sessions = self.chat_handler.get_all_sessions()
current_session_id = self.chat_handler.session_id

# Always add the current session at the top
current_session = next(
(s for s in sessions if s["id"] == current_session_id), None
)
if current_session:
self.session_dropdown.addItem(
f"{current_session_id} (Current) - {current_session['summary']}",
current_session_id,
)
else:
# If the current session is not in the list (e.g., it's new and empty), add it manually
current_summary = (
self.chat_handler.session_manager.get_session_summary(
current_session_id
)
or "New session"
)
self.session_dropdown.addItem(
f"{current_session_id} (Current) - {current_summary}",
current_session_id,
)

# Add other non-empty sessions
for session in sessions:
if session["id"] != current_session_id:
self.session_dropdown.addItem(
f"{session['id']} - {session['summary']}", session["id"]
)

self.session_dropdown.setCurrentIndex(0)
self.session_dropdown.blockSignals(False)

# Reconnect the signal
self.session_dropdown.currentIndexChanged.connect(self.on_session_selected)

def on_session_selected(self, index):
if index == 0: # Current session
return

selected_session_id = self.session_dropdown.itemData(index)
if selected_session_id is None:
self.logger.error(f"Invalid session selected at index {index}")
self.session_dropdown.setCurrentIndex(0)
return

reply = QMessageBox.question(
self,
"Load Previous Session",
f"This will create a new session based on session {selected_session_id}. Are you sure you'd like to proceed?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)

if reply == QMessageBox.Yes:
self.logger.info(
f"User confirmed loading previous session: {selected_session_id}"
)
try:
self.update_session_dropdown()
# Disconnect and reconnect the signal to prevent multiple calls
self.session_dropdown.currentIndexChanged.disconnect()
self.session_dropdown.setCurrentIndex(0)
self.session_dropdown.currentIndexChanged.connect(
self.on_session_selected
)
except Exception as e:
self.logger.error(f"Error loading previous session: {str(e)}")
QMessageBox.critical(
self, "Error", f"Failed to load previous session: {str(e)}"
)
self.session_dropdown.setCurrentIndex(0)
else:
self.logger.info("User cancelled loading previous session")
# Reset the dropdown to the current session
self.session_dropdown.setCurrentIndex(0)
11 changes: 10 additions & 1 deletion codeaide/utils/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,21 @@ def parse_response(response, provider):
version_description = outer_json.get("version_description")
requirements = outer_json.get("requirements", [])
questions = outer_json.get("questions", [])
session_summary = outer_json.get("session_summary", "")

# Clean the code if it exists
if code:
code = clean_code(code)

return text, questions, code, code_version, version_description, requirements
return (
text,
questions,
code,
code_version,
version_description,
requirements,
session_summary,
)


def clean_code(code):
Expand Down
Loading

0 comments on commit a0aa3fb

Please sign in to comment.