Enable continuous dialogue, ready for @{gerritUserName}'s queries
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
index 28725cb..cfc0a1f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Module.java
@@ -1,13 +1,17 @@
 package com.googlesource.gerrit.plugins.chatgpt;
 
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.events.EventListener;
 import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import com.googlesource.gerrit.plugins.chatgpt.listener.GptMentionedCommentListener;
+import com.googlesource.gerrit.plugins.chatgpt.listener.PatchSetCreatedListener;
 
 public class Module extends AbstractModule {
 
     @Override
     protected void configure() {
-        DynamicSet.bind(binder(), EventListener.class).to(PatchSetCreated.class);
+        Multibinder<EventListener> eventListenerBinder = Multibinder.newSetBinder(binder(), EventListener.class);
+        eventListenerBinder.addBinding().to(PatchSetCreatedListener.class);
+        eventListenerBinder.addBinding().to(GptMentionedCommentListener.class);
     }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetCreated.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetCreated.java
deleted file mode 100644
index e9330c5..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetCreated.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package com.googlesource.gerrit.plugins.chatgpt;
-
-import com.google.common.base.Splitter;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.events.EventListener;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.inject.Inject;
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.List;
-import java.util.concurrent.*;
-
-@Slf4j
-public class PatchSetCreated implements EventListener {
-    private final ConfigCreator configCreator;
-    private final PatchSetReviewer reviewer;
-    private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
-    private final RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
-    private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
-            .setNameFormat("PatchSetReviewExecutorThread-%d")
-            .build();
-    private final ExecutorService executorService = new ThreadPoolExecutor(
-            1, 1, 0L, TimeUnit.MILLISECONDS, queue, threadFactory, handler);
-    private CompletableFuture<Void> latestFuture;
-
-    @Inject
-    PatchSetCreated(ConfigCreator configCreator, PatchSetReviewer reviewer) {
-        this.configCreator = configCreator;
-        this.reviewer = reviewer;
-
-        addShutdownHoot();
-
-    }
-
-    public static String buildFullChangeId(String projectName, String branchName, String changeKey) {
-        return String.join("~", projectName, branchName, changeKey);
-    }
-
-    private void addShutdownHoot() {
-        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
-            executorService.shutdown();
-            try {
-                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
-                    executorService.shutdownNow();
-                }
-            } catch (InterruptedException ex) {
-                executorService.shutdownNow();
-                Thread.currentThread().interrupt();
-            }
-        }));
-    }
-
-    @Override
-    public void onEvent(Event event) {
-        // Execute the potentially time-consuming operation asynchronously
-        latestFuture = CompletableFuture.runAsync(() -> {
-            if (!(event instanceof PatchSetCreatedEvent)) {
-                log.debug("The event is not a PatchSetCreatedEvent, it is: {}", event);
-                return;
-            }
-
-            log.info("Processing event: {}", event);
-            PatchSetCreatedEvent createdPatchSetEvent = (PatchSetCreatedEvent) event;
-            Project.NameKey projectNameKey = createdPatchSetEvent.getProjectNameKey();
-            String projectName = projectNameKey.get();
-            String branchName = createdPatchSetEvent.getBranchNameKey().shortName();
-            String changeKey = createdPatchSetEvent.getChangeKey().get();
-
-            log.info("Processing patch set for project: {}, branch: {}, change key: {}", projectName, branchName, changeKey);
-
-            try {
-                Configuration config = configCreator.createConfig(projectNameKey);
-                List<String> enabledProjects = Splitter.on(",").omitEmptyStrings()
-                        .splitToList(config.getEnabledProjects());
-                if (!config.isGlobalEnable() &&
-                        !enabledProjects.contains(projectName) &&
-                        !config.isProjectEnable()) {
-                    log.info("The project {} is not enabled for review", projectName);
-                    return;
-                }
-
-                String fullChangeId = buildFullChangeId(projectName, branchName, changeKey);
-
-                reviewer.review(config, fullChangeId);
-                log.info("Review completed for project: {}, branch: {}, change key: {}", projectName, branchName, changeKey);
-            } catch (Exception e) {
-                log.error("Failed to submit review for project: {}, branch: {}, change key: {}", projectName, branchName, changeKey, e);
-                if (e instanceof InterruptedException) {
-                    Thread.currentThread().interrupt();
-                }
-            }
-        }, executorService);
-    }
-
-    public CompletableFuture<Void> getLatestFuture() {
-        return latestFuture;
-    }
-
-}
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
index c640b7e..508f77b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewer.java
@@ -4,6 +4,7 @@
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
 import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
 
 import java.util.*;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
index 5bfc5b8..376468a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/GerritClient.java
@@ -3,7 +3,7 @@
 import com.google.common.net.HttpHeaders;
 import com.google.gson.Gson;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.chatgpt.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.entity.ContentType;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
index f595d89..1e2e519 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/OpenAiClient.java
@@ -3,7 +3,7 @@
 import com.google.common.net.HttpHeaders;
 import com.google.gson.Gson;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.chatgpt.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.entity.ContentType;
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/ConfigCreator.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
similarity index 78%
rename from src/main/java/com/googlesource/gerrit/plugins/chatgpt/ConfigCreator.java
rename to src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
index e3877b5..861e7ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/ConfigCreator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/ConfigCreator.java
@@ -1,4 +1,4 @@
-package com.googlesource.gerrit.plugins.chatgpt;
+package com.googlesource.gerrit.plugins.chatgpt.config;
 
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -26,9 +26,10 @@
     public Configuration createConfig(Project.NameKey projectName)
             throws NoSuchProjectException {
         PluginConfig globalConfig = configFactory.getFromGerritConfig(pluginName);
-        log.info("The names in global config: {}", globalConfig.getNames());
+        log.info("These configuration items have been set in the global configuration: {}", globalConfig.getNames());
         PluginConfig projectConfig = configFactory.getFromProjectConfig(projectName, pluginName);
-        log.info("The names in project config: {}", projectConfig.getNames());
+        log.info("These configuration items have been set in the project configuration: {}", projectConfig.getNames());
         return new Configuration(globalConfig, projectConfig);
     }
+
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
similarity index 84%
rename from src/main/java/com/googlesource/gerrit/plugins/chatgpt/Configuration.java
rename to src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
index f99f743..2f30c57 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/config/Configuration.java
@@ -1,41 +1,49 @@
-package com.googlesource.gerrit.plugins.chatgpt;
+package com.googlesource.gerrit.plugins.chatgpt.config;
 
+import com.google.common.collect.Maps;
 import com.google.gerrit.server.config.PluginConfig;
-import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 
+import java.util.Map;
+
 @Slf4j
-@AllArgsConstructor
 public class Configuration {
 
     public static final String OPENAI_DOMAIN = "https://api.openai.com";
     public static final String DEFAULT_GPT_MODEL = "gpt-3.5-turbo";
     public static final String DEFAULT_GPT_PROMPT = "Act as a Code Review Helper, please review this patch set: ";
     public static final String NOT_CONFIGURED_ERROR_MSG = "%s is not configured";
+    public static final String KEY_GPT_PROMPT = "gptPrompt";
     private static final String DEFAULT_GPT_TEMPERATURE = "1";
     private static final boolean DEFAULT_GLOBAL_ENABLE = false;
     private static final String DEFAULT_ENABLED_PROJECTS = "";
     private static final boolean DEFAULT_PATCH_SET_REDUCTION = false;
     private static final boolean DEFAULT_PROJECT_ENABLE = false;
     private static final int DEFAULT_MAX_REVIEW_LINES = 1000;
-
     private static final String KEY_GPT_TOKEN = "gptToken";
     private static final String KEY_GERRIT_AUTH_BASE_URL = "gerritAuthBaseUrl";
     private static final String KEY_GERRIT_USERNAME = "gerritUserName";
     private static final String KEY_GERRIT_PASSWORD = "gerritPassword";
     private static final String KEY_GPT_DOMAIN = "gptDomain";
     private static final String KEY_GPT_MODEL = "gptModel";
-    private static final String KEY_GPT_PROMPT = "gptPrompt";
     private static final String KEY_GPT_TEMPERATURE = "gptTemperature";
     private static final String KEY_PROJECT_ENABLE = "isEnabled";
     private static final String KEY_GLOBAL_ENABLE = "globalEnable";
     private static final String KEY_ENABLED_PROJECTS = "enabledProjects";
     private static final String KEY_PATCH_SET_REDUCTION = "patchSetReduction";
     private static final String KEY_MAX_REVIEW_LINES = "maxReviewLines";
+    private final Map<String, Object> configsDynamically = Maps.newHashMap();
+    private final PluginConfig globalConfig;
+    private final PluginConfig projectConfig;
 
-    private PluginConfig globalConfig;
+    public Configuration(PluginConfig globalConfig, PluginConfig projectConfig) {
+        this.globalConfig = globalConfig;
+        this.projectConfig = projectConfig;
+    }
 
-    private PluginConfig projectConfig;
+    public <T> void configureDynamically(String key, T value) {
+        configsDynamically.put(key, value);
+    }
 
     public String getGptToken() {
         return getValidatedOrThrow(KEY_GPT_TOKEN);
@@ -62,6 +70,9 @@
     }
 
     public String getGptPrompt() {
+        if (configsDynamically.get(KEY_GPT_PROMPT) != null) {
+            return configsDynamically.get(KEY_GPT_PROMPT).toString();
+        }
         return getString(KEY_GPT_PROMPT, DEFAULT_GPT_PROMPT);
     }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
new file mode 100644
index 0000000..0f7f6f6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
@@ -0,0 +1,90 @@
+package com.googlesource.gerrit.plugins.chatgpt.listener;
+
+import com.google.common.base.Splitter;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.chatgpt.PatchSetReviewer;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+import java.util.concurrent.*;
+
+@Slf4j
+public class EventListenerHandler {
+
+    private final PatchSetReviewer reviewer;
+    private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
+    private final RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
+    private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
+            .setNameFormat("EventListenerHandler-%d")
+            .build();
+    private final ExecutorService executorService = new ThreadPoolExecutor(
+            1, 1, 0L, TimeUnit.MILLISECONDS, queue, threadFactory, handler);
+    private CompletableFuture<Void> latestFuture;
+
+    @Inject
+    public EventListenerHandler(PatchSetReviewer reviewer) {
+        this.reviewer = reviewer;
+
+        addShutdownHoot();
+    }
+
+    public static String buildFullChangeId(Project.NameKey projectName, BranchNameKey branchName, Change.Key changeKey) {
+        return String.join("~", projectName.get(), branchName.shortName(), changeKey.get());
+    }
+
+    private void addShutdownHoot() {
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            executorService.shutdown();
+            try {
+                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
+                    executorService.shutdownNow();
+                }
+            } catch (InterruptedException ex) {
+                executorService.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }));
+    }
+
+    public void handleEvent(Configuration config, ChangeEvent changeEvent) {
+        Project.NameKey projectNameKey = changeEvent.getProjectNameKey();
+        BranchNameKey branchNameKey = changeEvent.getBranchNameKey();
+        Change.Key changeKey = changeEvent.getChangeKey();
+
+        String fullChangeId = buildFullChangeId(projectNameKey, branchNameKey, changeKey);
+
+        List<String> enabledProjects = Splitter.on(",").omitEmptyStrings()
+                .splitToList(config.getEnabledProjects());
+        if (!config.isGlobalEnable() &&
+                !enabledProjects.contains(projectNameKey.get()) &&
+                !config.isProjectEnable()) {
+            log.info("The project {} is not enabled for review", projectNameKey);
+            return;
+        }
+
+        // Execute the potentially time-consuming operation asynchronously
+        latestFuture = CompletableFuture.runAsync(() -> {
+            try {
+                log.info("Processing change: {}", fullChangeId);
+                reviewer.review(config, fullChangeId);
+                log.info("Finished processing change: {}", fullChangeId);
+            } catch (Exception e) {
+                log.error("Error while processing change: {}", fullChangeId, e);
+                if (e instanceof InterruptedException) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }, executorService);
+    }
+
+    public CompletableFuture<Void> getLatestFuture() {
+        return latestFuture;
+    }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GptMentionedCommentListener.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GptMentionedCommentListener.java
new file mode 100644
index 0000000..d1a71e0
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/GptMentionedCommentListener.java
@@ -0,0 +1,56 @@
+package com.googlesource.gerrit.plugins.chatgpt.listener;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class GptMentionedCommentListener implements EventListener {
+
+    private final ConfigCreator configCreator;
+
+    private final EventListenerHandler eventListenerHandler;
+
+    @Inject
+    public GptMentionedCommentListener(ConfigCreator configCreator, EventListenerHandler eventListenerHandler) {
+        this.configCreator = configCreator;
+        this.eventListenerHandler = eventListenerHandler;
+    }
+
+    @Override
+    public void onEvent(Event event) {
+        if (!(event instanceof CommentAddedEvent)) {
+            log.debug("The event is not a CommentAddedEvent, it is: {}", event);
+            return;
+        }
+
+        log.info("Processing event: {}", event);
+        ChangeEvent changeEvent = (ChangeEvent) event;
+        Project.NameKey projectNameKey = changeEvent.getProjectNameKey();
+
+        try {
+            Configuration config = configCreator.createConfig(projectNameKey);
+            String commentText = ((CommentAddedEvent) event).comment;
+            if (commentText == null || !commentText.contains("@" + config.getGerritUserName())) {
+                log.info("The comment does not mention the bot: {}", commentText);
+                return;
+            }
+
+            String questionToGpt = commentText.substring(commentText.indexOf("@" + config.getGerritUserName())
+                    + config.getGerritUserName().length() + 1);
+            config.configureDynamically(Configuration.KEY_GPT_PROMPT, questionToGpt);
+
+            eventListenerHandler.handleEvent(config, changeEvent);
+        } catch (NoSuchProjectException e) {
+            log.error("Project not found: {}", projectNameKey, e);
+        }
+
+    }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/PatchSetCreatedListener.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/PatchSetCreatedListener.java
new file mode 100644
index 0000000..225ac88
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/PatchSetCreatedListener.java
@@ -0,0 +1,46 @@
+package com.googlesource.gerrit.plugins.chatgpt.listener;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class PatchSetCreatedListener implements EventListener {
+    private final ConfigCreator configCreator;
+
+    private final EventListenerHandler eventListenerHandler;
+
+    @Inject
+    public PatchSetCreatedListener(ConfigCreator configCreator, EventListenerHandler eventListenerHandler) {
+        this.configCreator = configCreator;
+        this.eventListenerHandler = eventListenerHandler;
+    }
+
+    @Override
+    public void onEvent(Event event) {
+        if (!(event instanceof PatchSetCreatedEvent)) {
+            log.debug("The event is not a PatchSetCreatedEvent, it is: {}", event);
+            return;
+        }
+
+        log.info("Processing event: {}", event);
+        ChangeEvent changeEvent = (ChangeEvent) event;
+        Project.NameKey projectNameKey = changeEvent.getProjectNameKey();
+
+        try {
+            Configuration config = configCreator.createConfig(projectNameKey);
+            eventListenerHandler.handleEvent(config, changeEvent);
+        } catch (NoSuchProjectException e) {
+            log.error("Project not found: {}", projectNameKey, e);
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTests.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTests.java
new file mode 100644
index 0000000..b656eb8
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewTests.java
@@ -0,0 +1,163 @@
+package com.googlesource.gerrit.plugins.chatgpt;
+
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
+import com.github.tomakehurst.wiremock.verification.LoggedRequest;
+import com.google.common.net.HttpHeaders;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.events.CommentAddedEvent;
+import com.google.gerrit.server.events.PatchSetCreatedEvent;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
+import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
+import com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator;
+import com.googlesource.gerrit.plugins.chatgpt.config.ConfigCreator;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
+import com.googlesource.gerrit.plugins.chatgpt.listener.EventListenerHandler;
+import com.googlesource.gerrit.plugins.chatgpt.listener.GptMentionedCommentListener;
+import com.googlesource.gerrit.plugins.chatgpt.listener.PatchSetCreatedListener;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.entity.ContentType;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.net.URI;
+import java.util.Base64;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritCommentUri;
+import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritPatchSetUri;
+import static com.googlesource.gerrit.plugins.chatgpt.listener.EventListenerHandler.buildFullChangeId;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@Slf4j
+@RunWith(MockitoJUnitRunner.class)
+public class ChatGptReviewTests {
+    private static final Project.NameKey PROJECT_NAME = Project.NameKey.parse("myProject");
+    private static final Change.Key CHANGE_ID = Change.Key.parse("myChangeId");
+    private static final BranchNameKey BRANCH_NAME = BranchNameKey.create(PROJECT_NAME, "myBranchName");
+
+    @Rule
+    public WireMockRule wireMockRule = new WireMockRule(9527);
+
+    private Configuration config;
+
+    @Before
+    public void before() {
+        initConfig();
+        setupMockRequests();
+    }
+
+    private void initConfig() {
+        config = mock(Configuration.class);
+        when(config.getGerritAuthBaseUrl()).thenReturn("http://localhost:9527");
+        when(config.getGptDomain()).thenReturn("http://localhost:9527");
+        when(config.getGptTemperature()).thenReturn(1.0);
+        when(config.getMaxReviewLines()).thenReturn(500);
+        when(config.getEnabledProjects()).thenReturn("");
+        when(config.isProjectEnable()).thenReturn(true);
+    }
+
+    private void setupMockRequests() {
+        // Mocks the behavior of the getPatchSet request
+        WireMock.stubFor(WireMock.get(gerritPatchSetUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
+                .willReturn(WireMock.aResponse()
+                        .withStatus(HTTP_OK)
+                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+                        .withBody(Base64.getEncoder().encodeToString("myPatch".getBytes()))));
+
+        // Mocks the behavior of the askGpt request
+        byte[] gptAnswer = Base64.getDecoder().decode("ZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV2" +
+                "9hM3Q2RG83RkkzIiwib2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWw" +
+                "iOiJncHQtMy41LXR1cmJvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJyb2xlIjoiYXNzaXN0YW50In0sImluZGV4Ijow" +
+                "LCJmaW5pc2hfcmVhc29uIjpudWxsfV19CgpkYXRhOiB7ImlkIjoiY2hhdGNtcGwtN1JkNU5hWkQ4Yk1VNGR2cFVXb2EzdDZEb" +
+                "zdGSTMiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24uY2h1bmsiLCJjcmVhdGVkIjoxNjg2ODE5NDU3LCJtb2RlbCI6ImdwdC0" +
+                "zLjUtdHVyYm8tMDMwMSIsImNob2ljZXMiOlt7ImRlbHRhIjp7ImNvbnRlbnQiOiJIZWxsbyJ9LCJpbmRleCI6MCwiZmluaXNo" +
+                "X3JlYXNvbiI6bnVsbH1dfQoKZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV29hM3Q2RG83RkkzIiwib" +
+                "2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWwiOiJncHQtMy41LXR1cm" +
+                "JvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJjb250ZW50IjoiISJ9LCJpbmRleCI6MCwiZmluaXNoX3JlYXNvbiI" +
+                "6bnVsbH1dfQ==");
+        WireMock.stubFor(WireMock.post(WireMock.urlEqualTo(URI.create(config.getGptDomain()
+                        + UriResourceLocator.chatCompletionsUri()).getPath()))
+                .willReturn(WireMock.aResponse()
+                        .withStatus(HTTP_OK)
+                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
+                        .withBody(new String(gptAnswer))));
+
+        // Mocks the behavior of the postReview request
+        WireMock.stubFor(WireMock.post(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
+                .willReturn(WireMock.aResponse()
+                        .withStatus(HTTP_OK)));
+    }
+
+    @Test
+    public void patchSetCreatedOrUpdated() throws InterruptedException, NoSuchProjectException, ExecutionException {
+        GerritClient gerritClient = new GerritClient();
+        OpenAiClient openAiClient = new OpenAiClient();
+        PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient, openAiClient);
+        ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
+        when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
+
+        PatchSetCreatedEvent event = mock(PatchSetCreatedEvent.class);
+        when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
+        when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
+        when(event.getChangeKey()).thenReturn(CHANGE_ID);
+        EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer);
+
+        PatchSetCreatedListener patchSetCreatedListener = new PatchSetCreatedListener(mockConfigCreator, eventListenerHandler);
+        patchSetCreatedListener.onEvent(event);
+        CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+        future.get();
+
+        RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
+                WireMock.urlEqualTo(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID))));
+        List<LoggedRequest> loggedRequests = WireMock.findAll(requestPatternBuilder);
+        Assert.assertEquals(1, loggedRequests.size());
+        String requestBody = loggedRequests.get(0).getBodyAsString();
+        Assert.assertEquals("{\"message\":\"Hello!\\n\"}", requestBody);
+
+    }
+
+    @Test
+    public void gptMentionedInComment() throws InterruptedException, NoSuchProjectException, ExecutionException {
+        GerritClient gerritClient = new GerritClient();
+        OpenAiClient openAiClient = new OpenAiClient();
+        PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient, openAiClient);
+        ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
+        when(config.getGerritUserName()).thenReturn("gpt");
+        when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
+
+        CommentAddedEvent event = mock(CommentAddedEvent.class);
+        when(event.getProjectNameKey()).thenReturn(PROJECT_NAME);
+        when(event.getBranchNameKey()).thenReturn(BRANCH_NAME);
+        when(event.getChangeKey()).thenReturn(CHANGE_ID);
+        EventListenerHandler eventListenerHandler = new EventListenerHandler(patchSetReviewer);
+
+        GptMentionedCommentListener gptMentionedCommentListener = new GptMentionedCommentListener(mockConfigCreator, eventListenerHandler);
+        event.comment = "@gpt Hello!";
+        gptMentionedCommentListener.onEvent(event);
+        CompletableFuture<Void> future = eventListenerHandler.getLatestFuture();
+        future.get();
+
+        RequestPatternBuilder requestPatternBuilder = WireMock.postRequestedFor(
+                WireMock.urlEqualTo(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID))));
+        List<LoggedRequest> loggedRequests = WireMock.findAll(requestPatternBuilder);
+        Assert.assertEquals(1, loggedRequests.size());
+        String requestBody = loggedRequests.get(0).getBodyAsString();
+        Assert.assertEquals("{\"message\":\"Hello!\\n\"}", requestBody);
+
+    }
+
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewerTests.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewerTests.java
deleted file mode 100644
index 3f34a1e..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/PatchSetReviewerTests.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.googlesource.gerrit.plugins.chatgpt;
-
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
-import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
-import com.github.tomakehurst.wiremock.verification.LoggedRequest;
-import com.google.common.net.HttpHeaders;
-import com.google.gerrit.entities.BranchNameKey;
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.server.events.PatchSetCreatedEvent;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
-import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
-import com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.http.entity.ContentType;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.net.URI;
-import java.util.Base64;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.*;
-import static com.googlesource.gerrit.plugins.chatgpt.PatchSetCreated.buildFullChangeId;
-import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritCommentUri;
-import static com.googlesource.gerrit.plugins.chatgpt.client.UriResourceLocator.gerritPatchSetUri;
-import static java.net.HttpURLConnection.HTTP_OK;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@Slf4j
-@RunWith(MockitoJUnitRunner.class)
-public class PatchSetReviewerTests {
-    private static final String PROJECT_NAME = "myProject";
-    private static final String CHANGE_ID = "myChangeId";
-    private static final String BRANCH_NAME = "myBranchName";
-
-    @Rule
-    public WireMockRule wireMockRule = new WireMockRule(9527);
-
-    private Configuration config;
-
-    @Before
-    public void before() {
-        initConfig();
-        setupMockRequests();
-    }
-
-    private void initConfig() {
-        config = Mockito.mock(Configuration.class);
-        when(config.getGerritAuthBaseUrl()).thenReturn("http://localhost:9527");
-        when(config.getGptDomain()).thenReturn("http://localhost:9527");
-        when(config.getGptTemperature()).thenReturn(1.0);
-        when(config.getMaxReviewLines()).thenReturn(500);
-        when(config.getEnabledProjects()).thenReturn("");
-        when(config.isProjectEnable()).thenReturn(true);
-
-    }
-
-    private void setupMockRequests() {
-        // Mocks the behavior of the getPatchSet request
-        stubFor(get(gerritPatchSetUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
-                .willReturn(aResponse()
-                        .withStatus(HTTP_OK)
-                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
-                        .withBody(Base64.getEncoder().encodeToString("myPatch".getBytes()))));
-
-        // Mocks the behavior of the askGpt request
-        byte[] gptAnswer = Base64.getDecoder().decode("ZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV2" +
-                "9hM3Q2RG83RkkzIiwib2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWw" +
-                "iOiJncHQtMy41LXR1cmJvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJyb2xlIjoiYXNzaXN0YW50In0sImluZGV4Ijow" +
-                "LCJmaW5pc2hfcmVhc29uIjpudWxsfV19CgpkYXRhOiB7ImlkIjoiY2hhdGNtcGwtN1JkNU5hWkQ4Yk1VNGR2cFVXb2EzdDZEb" +
-                "zdGSTMiLCJvYmplY3QiOiJjaGF0LmNvbXBsZXRpb24uY2h1bmsiLCJjcmVhdGVkIjoxNjg2ODE5NDU3LCJtb2RlbCI6ImdwdC0" +
-                "zLjUtdHVyYm8tMDMwMSIsImNob2ljZXMiOlt7ImRlbHRhIjp7ImNvbnRlbnQiOiJIZWxsbyJ9LCJpbmRleCI6MCwiZmluaXNo" +
-                "X3JlYXNvbiI6bnVsbH1dfQoKZGF0YTogeyJpZCI6ImNoYXRjbXBsLTdSZDVOYVpEOGJNVTRkdnBVV29hM3Q2RG83RkkzIiwib" +
-                "2JqZWN0IjoiY2hhdC5jb21wbGV0aW9uLmNodW5rIiwiY3JlYXRlZCI6MTY4NjgxOTQ1NywibW9kZWwiOiJncHQtMy41LXR1cm" +
-                "JvLTAzMDEiLCJjaG9pY2VzIjpbeyJkZWx0YSI6eyJjb250ZW50IjoiISJ9LCJpbmRleCI6MCwiZmluaXNoX3JlYXNvbiI" +
-                "6bnVsbH1dfQ==");
-        stubFor(post(urlEqualTo(URI.create(config.getGptDomain()
-                + UriResourceLocator.chatCompletionsUri()).getPath()))
-                .willReturn(aResponse()
-                        .withStatus(HTTP_OK)
-                        .withHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())
-                        .withBody(new String(gptAnswer))));
-
-        // Mocks the behavior of the postReview request
-        stubFor(post(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID)))
-                .willReturn(aResponse()
-                        .withStatus(HTTP_OK)));
-    }
-
-    @Test
-    public void review() throws InterruptedException, NoSuchProjectException, ExecutionException {
-        GerritClient gerritClient = new GerritClient();
-        OpenAiClient openAiClient = new OpenAiClient();
-        PatchSetReviewer patchSetReviewer = new PatchSetReviewer(gerritClient, openAiClient);
-        ConfigCreator mockConfigCreator = mock(ConfigCreator.class);
-        when(mockConfigCreator.createConfig(ArgumentMatchers.any())).thenReturn(config);
-
-        PatchSetCreatedEvent event = mock(PatchSetCreatedEvent.class);
-        when(event.getProjectNameKey()).thenReturn(Project.NameKey.parse(PROJECT_NAME));
-        when(event.getBranchNameKey()).thenReturn(BranchNameKey.create(Project.NameKey.parse(PROJECT_NAME), BRANCH_NAME));
-        when(event.getChangeKey()).thenReturn(Change.Key.parse(CHANGE_ID));
-
-        PatchSetCreated patchSetCreated = new PatchSetCreated(mockConfigCreator, patchSetReviewer);
-        patchSetCreated.onEvent(event);
-        CompletableFuture<Void> future = patchSetCreated.getLatestFuture();
-        future.get();
-
-        RequestPatternBuilder requestPatternBuilder = postRequestedFor(
-                urlEqualTo(gerritCommentUri(buildFullChangeId(PROJECT_NAME, BRANCH_NAME, CHANGE_ID))));
-        List<LoggedRequest> loggedRequests = findAll(requestPatternBuilder);
-        assertEquals(1, loggedRequests.size());
-        String requestBody = loggedRequests.get(0).getBodyAsString();
-        assertEquals("{\"message\":\"Hello!\\n\"}", requestBody);
-
-    }
-
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
index c6a1d63..fef832f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/integration/CodeReviewPluginIT.java
@@ -1,8 +1,8 @@
 package com.googlesource.gerrit.plugins.chatgpt.integration;
 
-import com.googlesource.gerrit.plugins.chatgpt.Configuration;
 import com.googlesource.gerrit.plugins.chatgpt.client.GerritClient;
 import com.googlesource.gerrit.plugins.chatgpt.client.OpenAiClient;
+import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.Ignore;
 import org.junit.Test;