From bc3d0f6b9a5f41a9759d17490ba057b37277b09b Mon Sep 17 00:00:00 2001 From: rabenojha Date: Mon, 29 Dec 2025 00:36:07 +0100 Subject: [PATCH 1/3] add method for creating and deleting project --- __init__.py | 3 +- geo_agent.py | 26 +++++------ tools/io.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 126 insertions(+), 24 deletions(-) diff --git a/__init__.py b/__init__.py index 2912169..f8c7e9e 100644 --- a/__init__.py +++ b/__init__.py @@ -7,7 +7,7 @@ Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ ------------------- begin : 2025-12-15 - copyright : (C) 2025 by Tek Kshetri, Rabin Ojha + copyright : (C) 2025 by Tek Kshetri email : iamtekson@gmail.com git sha : $Format:%H$ ***************************************************************************/ @@ -33,5 +33,4 @@ def classFactory(iface): # pylint: disable=invalid-name """ # from .geo_agent import GeoAgent - return GeoAgent(iface) diff --git a/geo_agent.py b/geo_agent.py index 3c98aac..d359e1b 100644 --- a/geo_agent.py +++ b/geo_agent.py @@ -816,13 +816,13 @@ def _initialize_agent( def _display_user_message(self, message: str) -> None: """Display user message in the chat area.""" - self.dlg.llm_response.append("\n") - self.dlg.llm_response.append("." * 40) - self.dlg.llm_response.append(f"\nuser: {message}") + self.dlg.chatgpt_ans.append("\n") + self.dlg.chatgpt_ans.append("." * 40) + self.dlg.chatgpt_ans.append(f"\nUser: {message}") # Show a processing indicator immediately - self.dlg.llm_response.append("\nAgent is processing…") + self.dlg.chatgpt_ans.append("\nAgent is processing…") try: - self.dlg.llm_response.repaint() + self.dlg.chatgpt_ans.repaint() except Exception: pass self._scroll_to_bottom() @@ -830,7 +830,7 @@ def _display_user_message(self, message: str) -> None: def _display_ai_response(self, response: str) -> None: """Display AI response in the chat area.""" # Get cursor and remove the processing indicator line - cursor = self.dlg.llm_response.textCursor() + cursor = self.dlg.chatgpt_ans.textCursor() cursor.movePosition(cursor.End) # Move back to select the "Agent is processing..." line cursor.select(cursor.LineUnderCursor) @@ -840,9 +840,9 @@ def _display_ai_response(self, response: str) -> None: # Append agent response formatted_response = response.replace("\n", "
") - self.dlg.llm_response.append("\nAgent: ") - self.dlg.llm_response.insertHtml(formatted_response) - self.dlg.llm_response.append("\n" + "." * 40) + self.dlg.chatgpt_ans.append("\nAgent: ") + self.dlg.chatgpt_ans.insertHtml(formatted_response) + self.dlg.chatgpt_ans.append("\n" + "." * 40) # Re-enable buttons only after response is displayed self.dlg.send_chat.setEnabled(True) @@ -851,7 +851,7 @@ def _display_ai_response(self, response: str) -> None: def _scroll_to_bottom(self) -> None: """Scroll chat area to the bottom.""" - scroll_bar = self.dlg.llm_response.verticalScrollBar() + scroll_bar = self.dlg.chatgpt_ans.verticalScrollBar() scroll_bar.setValue(scroll_bar.maximum()) def _load_api_key(self) -> str: @@ -886,7 +886,7 @@ def export_chat(self) -> None: import datetime # Get chat content - chat_text = self.dlg.llm_response.toPlainText() + chat_text = self.dlg.chatgpt_ans.toPlainText() if not chat_text.strip(): self.iface.messageBar().pushMessage( "GeoAgent", @@ -924,7 +924,7 @@ def export_chat(self) -> None: def clear_chat(self) -> None: """Clear the chat history.""" try: - self.dlg.llm_response.clear() + self.dlg.chatgpt_ans.clear() # Start a fresh thread id (clears memory) import uuid @@ -941,5 +941,5 @@ def clear_chat(self) -> None: "GeoAgent", f"Failed to clear chat: {str(e)}", level=Qgis.Critical, - duration=QGIS_MESSAGE_DURATION, + duration=5, ) diff --git a/tools/io.py b/tools/io.py index 9aa63c5..83b5f33 100644 --- a/tools/io.py +++ b/tools/io.py @@ -10,6 +10,7 @@ QgsVectorLayer, QgsRasterLayer, QgsMapLayer, + QgsCoordinateReferenceSystem, QgsVectorFileWriter, ) from qgis.gui import QgisInterface @@ -60,15 +61,7 @@ def add_layer_to_qgis( # Auto-detect layer type if not provided if not layer_type: raster_extensions = [".tif", ".tiff", ".img", ".asc", ".nc", ".jpg", ".png"] - vector_extensions = [ - ".shp", - ".geojson", - ".json", - ".gpkg", - ".kml", - ".gml", - ".csv", - ] + vector_extensions = [".shp", ".geojson", ".json", ".gpkg", ".kml", ".gml", ".csv"] ext = os.path.splitext(path_or_url.lower())[1] if ext in raster_extensions: @@ -363,6 +356,114 @@ def remove_layer(layer_name: str) -> str: return f"Error removing layer: {str(e)}" +@tool +def new_qgis_project(path: str, project_name: Optional[str] = None) -> str: + """ + Create a new QGIS project and save it to a file. + + Args: + path: File path to save the project (e.g., '/path/to/project.qgs' or '/path/to/project.qgz') + project_name: Optional project name/title. Uses the filename if nothing is provided. + + Returns: + Success message with project path or error message. + + Examples: + - new_qgis_project('/geoagent/my_project.qgs') + - new_qgis_project('/geoagent/test_project.qgz', 'QGIS Test Project') + """ + try: + project = QgsProject.instance() + + # clear current project + project.clear() + + # set project title + if project_name: + project.setTitle(project_name) + else: + project.setTitle(os.path.splitext(os.path.basename(path))[0]) + + # validate file extensions + valid_extensions = ['.qgs', '.qgz'] + file_ext = os.path.splitext(path.lower())[1] + + if file_ext not in valid_extensions: + return f"Error: Invalid file extension '{file_ext}'. Use '.qgs' or '.qgz' for QGIS project files." + + # creat directory if needed + project_dir = os.path.dirname(path) + if project_dir and not os.path.exists(project_dir): + os.makedirs(project_dir) + + # save th project + project.setFileName(path) + success = project.write(path) + + if success: + return f"Success: Created new project '{project.title()}' at '{path}'" + else: + return f"Error: Failed to save project to '{path}'. Check file permissions and path validity." + + except Exception as e: + return f"Error creating project: {str(e)}" + +@tool +def delete_existing_project(path: str) -> str: + """ + Delete an existing QGIS project file from disk. + + Args: + path: File path to the QGIS project file to delete (e.g., '/path/to/project.qgs' or '/path/to/project.qgz') + + Returns: + Success message or error message. + + Examples: + - delete_existing_project('/geoagent/old_project.qgs') + - delete_existing_project('/geoagent/temp_analysis.qgz') + """ + try: + + if not os.path.exists(path): + return f"Error: Project file '{path}' does not exist." + + valid_extensions = ['.qgs', '.qgz'] + file_ext = os.path.splitext(path.lower())[1] + + if file_ext not in valid_extensions: + return f"Error: Invalid file extension '{file_ext}'. Expected '.qgs' or '.qgz' file." + + iface = get_qgis_interface() + if not iface: + return "Error: QGIS interface not initialized. Cannot show confirmation dialog." + + # show confirmation dialog + from qgis.PyQt.QtWidgets import QMessageBox + + msg_box = QMessageBox(iface.mainWindow()) + msg_box.setWindowTitle("Delete Project") + msg_box.setText( + f"Do you really want to delete the project file:\n\n'{path}'\n\nThis action cannot be undone." + ) + msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + msg_box.setDefaultButton(QMessageBox.No) + msg_box.setIcon(QMessageBox.Warning) + + # get user response + response = msg_box.exec_() + + if response == QMessageBox.Yes: + # delete the project file + os.remove(path) + return f"Success: Project file '{path}' has been deleted." + else: + return f"Cancelled: Project file '{path}' was not deleted." + + except Exception as e: + return f"Error deleting project: {str(e)}" + + # Export tools for easy import __all__ = [ "add_layer_to_qgis", @@ -371,4 +472,6 @@ def remove_layer(layer_name: str) -> str: "zoom_to_layer", "remove_layer", "set_qgis_interface", + "new_qgis_project", + "delete_existing_project", ] From 08497c705e04cd7de856849b57fa7a2939ae1c16 Mon Sep 17 00:00:00 2001 From: rabenojha Date: Mon, 29 Dec 2025 00:46:24 +0100 Subject: [PATCH 2/3] change UI name --- __init__.py | 2 +- geo_agent.py | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/__init__.py b/__init__.py index f8c7e9e..1e6e291 100644 --- a/__init__.py +++ b/__init__.py @@ -7,7 +7,7 @@ Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ ------------------- begin : 2025-12-15 - copyright : (C) 2025 by Tek Kshetri + copyright : (C) 2025 by Tek Kshetri, Rabin Ojha email : iamtekson@gmail.com git sha : $Format:%H$ ***************************************************************************/ diff --git a/geo_agent.py b/geo_agent.py index d359e1b..16bd397 100644 --- a/geo_agent.py +++ b/geo_agent.py @@ -816,13 +816,13 @@ def _initialize_agent( def _display_user_message(self, message: str) -> None: """Display user message in the chat area.""" - self.dlg.chatgpt_ans.append("\n") - self.dlg.chatgpt_ans.append("." * 40) - self.dlg.chatgpt_ans.append(f"\nUser: {message}") + self.dlg.llm_response.append("\n") + self.dlg.llm_response.append("." * 40) + self.dlg.llm_response.append(f"\nUser: {message}") # Show a processing indicator immediately - self.dlg.chatgpt_ans.append("\nAgent is processing…") + self.dlg.llm_response.append("\nAgent is processing…") try: - self.dlg.chatgpt_ans.repaint() + self.dlg.llm_response.repaint() except Exception: pass self._scroll_to_bottom() @@ -830,7 +830,7 @@ def _display_user_message(self, message: str) -> None: def _display_ai_response(self, response: str) -> None: """Display AI response in the chat area.""" # Get cursor and remove the processing indicator line - cursor = self.dlg.chatgpt_ans.textCursor() + cursor = self.dlg.llm_response.textCursor() cursor.movePosition(cursor.End) # Move back to select the "Agent is processing..." line cursor.select(cursor.LineUnderCursor) @@ -840,9 +840,9 @@ def _display_ai_response(self, response: str) -> None: # Append agent response formatted_response = response.replace("\n", "
") - self.dlg.chatgpt_ans.append("\nAgent: ") - self.dlg.chatgpt_ans.insertHtml(formatted_response) - self.dlg.chatgpt_ans.append("\n" + "." * 40) + self.dlg.llm_response.append("\nAgent: ") + self.dlg.llm_response.insertHtml(formatted_response) + self.dlg.llm_response.append("\n" + "." * 40) # Re-enable buttons only after response is displayed self.dlg.send_chat.setEnabled(True) @@ -851,7 +851,7 @@ def _display_ai_response(self, response: str) -> None: def _scroll_to_bottom(self) -> None: """Scroll chat area to the bottom.""" - scroll_bar = self.dlg.chatgpt_ans.verticalScrollBar() + scroll_bar = self.dlg.llm_response.verticalScrollBar() scroll_bar.setValue(scroll_bar.maximum()) def _load_api_key(self) -> str: @@ -886,7 +886,7 @@ def export_chat(self) -> None: import datetime # Get chat content - chat_text = self.dlg.chatgpt_ans.toPlainText() + chat_text = self.dlg.llm_response.toPlainText() if not chat_text.strip(): self.iface.messageBar().pushMessage( "GeoAgent", @@ -924,7 +924,7 @@ def export_chat(self) -> None: def clear_chat(self) -> None: """Clear the chat history.""" try: - self.dlg.chatgpt_ans.clear() + self.dlg.llm_response.clear() # Start a fresh thread id (clears memory) import uuid @@ -941,5 +941,5 @@ def clear_chat(self) -> None: "GeoAgent", f"Failed to clear chat: {str(e)}", level=Qgis.Critical, - duration=5, + duration=QGIS_MESSAGE_DURATION, ) From e5079f9bb4f8121137d70f8b9e47d257321ebee5 Mon Sep 17 00:00:00 2001 From: rabenojha Date: Mon, 29 Dec 2025 00:56:02 +0100 Subject: [PATCH 3/3] update configuration file --- tools/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/__init__.py b/tools/__init__.py index 104e5da..41be000 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -4,6 +4,8 @@ get_layer_columns, zoom_to_layer, remove_layer, + new_qgis_project, + delete_existing_project ) from .commons import now_utc from .filters import ( @@ -25,6 +27,8 @@ get_layer_columns, zoom_to_layer, remove_layer, + new_qgis_project, + delete_existing_project, # Filtering & Selection select_by_attribute, select_by_geometry,