diff --git a/clients/eclipse/feature/feature.xml b/clients/eclipse/feature/feature.xml
index 5d0505a199b8..6555cd4f7543 100644
--- a/clients/eclipse/feature/feature.xml
+++ b/clients/eclipse/feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="com.tabbyml.features.tabby4eclipse"
       label="Tabby"
-      version="0.0.2.30"
+      version="0.0.2.31"
       provider-name="com.tabbyml">
 
    <description url="http://www.example.com/description">
@@ -19,6 +19,6 @@
 
    <plugin
          id="com.tabbyml.tabby4eclipse"
-         version="0.0.2.30"/>
+         version="0.0.2.31"/>
 
 </feature>
diff --git a/clients/eclipse/plugin/META-INF/MANIFEST.MF b/clients/eclipse/plugin/META-INF/MANIFEST.MF
index 91fa845d4d87..9c9e3de5e32c 100644
--- a/clients/eclipse/plugin/META-INF/MANIFEST.MF
+++ b/clients/eclipse/plugin/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: Tabby Plugin for Eclipse
 Bundle-SymbolicName: com.tabbyml.tabby4eclipse;singleton:=true
-Bundle-Version: 0.0.2.30
+Bundle-Version: 0.0.2.31
 Bundle-Activator: com.tabbyml.tabby4eclipse.Activator
 Bundle-Vendor: com.tabbyml
 Require-Bundle: org.eclipse.ui,
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatCommand.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatCommand.java
new file mode 100644
index 000000000000..6c4c4e0560ae
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatCommand.java
@@ -0,0 +1,8 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public abstract class ChatCommand {
+	public static final String EXPLAIN = "explain";
+	public static final String FIX = "fix";
+	public static final String GENERATE_DOCS = "generate-docs";
+	public static final String GENERATE_TESTS = "generate-tests";
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatMessage.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatMessage.java
deleted file mode 100644
index d79d715ec245..000000000000
--- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatMessage.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package com.tabbyml.tabby4eclipse.chat;
-
-import java.util.List;
-
-import com.google.gson.annotations.SerializedName;
-
-public class ChatMessage {
-	private String message;
-	private FileContext selectContext;
-	private List<FileContext> relevantContext;
-	private FileContext activeContext;
-
-	public ChatMessage() {
-	}
-
-	public ChatMessage(String message) {
-		this.message = message;
-	}
-
-	public String getMessage() {
-		return message;
-	}
-
-	public void setMessage(String message) {
-		this.message = message;
-	}
-
-	public FileContext getSelectContext() {
-		return selectContext;
-	}
-
-	public void setSelectContext(FileContext selectContext) {
-		this.selectContext = selectContext;
-	}
-
-	public List<FileContext> getRelevantContext() {
-		return relevantContext;
-	}
-
-	public void setRelevantContext(List<FileContext> relevantContext) {
-		this.relevantContext = relevantContext;
-	}
-
-	public FileContext getActiveContext() {
-		return activeContext;
-	}
-
-	public void setActiveContext(FileContext activeContext) {
-		this.activeContext = activeContext;
-	}
-
-	public static class FileContext {
-		@SuppressWarnings("unused")
-		private String kind = "file";
-		private LineRange range;
-		private String filepath;
-		private String content;
-		@SerializedName("git_url")
-		private String gitUrl;
-
-		public FileContext() {
-		}
-
-		public LineRange getRange() {
-			return range;
-		}
-
-		public void setRange(LineRange range) {
-			this.range = range;
-		}
-
-		public String getFilePath() {
-			return filepath;
-		}
-
-		public void setFilePath(String filepath) {
-			this.filepath = filepath;
-		}
-
-		public String getContent() {
-			return content;
-		}
-
-		public void setContent(String content) {
-			this.content = content;
-		}
-
-		public String getGitUrl() {
-			return gitUrl;
-		}
-
-		public void setGitUrl(String gitUrl) {
-			this.gitUrl = gitUrl;
-		}
-
-		public static class LineRange {
-			private int start;
-			private int end;
-
-			public LineRange(int start, int end) {
-				this.start = start;
-				this.end = end;
-			}
-
-			public int getStart() {
-				return start;
-			}
-
-			public int getEnd() {
-				return end;
-			}
-		}
-	}
-
-}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java
index 1e61864d10d0..2a4595baa9b0 100644
--- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatView.java
@@ -13,6 +13,8 @@
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.jface.resource.ColorRegistry;
 import org.eclipse.jface.resource.FontRegistry;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.browser.Browser;
 import org.eclipse.swt.browser.BrowserFunction;
@@ -23,6 +25,8 @@
 import org.eclipse.swt.graphics.RGB;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.ISelectionListener;
+import org.eclipse.ui.IWorkbenchPart;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.part.ViewPart;
 import org.eclipse.ui.themes.ITheme;
@@ -31,10 +35,11 @@
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import com.tabbyml.tabby4eclipse.Activator;
+import com.tabbyml.tabby4eclipse.DebouncedRunnable;
 import com.tabbyml.tabby4eclipse.Logger;
 import com.tabbyml.tabby4eclipse.StringUtils;
 import com.tabbyml.tabby4eclipse.Utils;
-import com.tabbyml.tabby4eclipse.chat.ChatMessage.FileContext;
+import com.tabbyml.tabby4eclipse.editor.EditorUtils;
 import com.tabbyml.tabby4eclipse.lsp.LanguageServerService;
 import com.tabbyml.tabby4eclipse.lsp.ServerConfigHolder;
 import com.tabbyml.tabby4eclipse.lsp.StatusInfoHolder;
@@ -66,7 +71,13 @@ public class ChatView extends ViewPart {
 	private RGB bgActiveColor;
 	private RGB fgColor;
 	private RGB borderColor;
+	private RGB inputBorderColor;
 	private RGB primaryColor;
+	private RGB primaryFgColor;
+	private RGB popoverColor;
+	private RGB popoverFgColor;
+	private RGB accentColor;
+	private RGB accentFgColor;
 	private String font;
 	private int fontSize = 13;
 
@@ -85,7 +96,7 @@ public void completed(ProgressEvent event) {
 				handleLoaded();
 			}
 		});
-		
+
 		injectFunctions();
 		load();
 		serverConfigHolder.addConfigDidChangeListener(() -> {
@@ -94,8 +105,34 @@ public void completed(ProgressEvent event) {
 		statusInfoHolder.addStatusDidChangeListener(() -> {
 			reloadContent(false);
 		});
+		
+		PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().addSelectionListener(new ISelectionListener() {
+			@Override
+            public void selectionChanged(IWorkbenchPart part, ISelection selection) {
+                if (selection instanceof ITextSelection) {
+                	syncActiveSelectionRunnable.call();
+                }
+            }
+		});
 	}
 
+	private DebouncedRunnable syncActiveSelectionRunnable = new DebouncedRunnable(() -> {
+		if (!isChatPanelLoaded) {
+			return;
+		}
+		EditorUtils.asyncExec(() -> {
+			try {
+				chatPanelClientInvoke("updateActiveSelection", new ArrayList<>() {
+					{
+						add(ChatViewUtils.getSelectedTextAsEditorFileContext());
+					}
+				});
+			} catch (Exception e) {
+				// ignore
+			}
+		});
+	}, 100);
+
 	@Override
 	public void setFocus() {
 		browser.forceFocus();
@@ -114,46 +151,34 @@ public void dispose() {
 	}
 
 	public void explainSelectedText() {
-		chatPanelClientInvoke("sendMessage", new ArrayList<>() {
+		chatPanelClientInvoke("executeCommand", new ArrayList<>() {
 			{
-				ChatMessage chatMessage = new ChatMessage();
-				chatMessage.setMessage(ChatViewUtils.PROMPT_EXPLAIN);
-				chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext());
-				add(chatMessage);
+				add(ChatCommand.EXPLAIN);
 			}
 		});
 	}
 
 	public void fixSelectedText() {
 		// FIXME(@icycodes): collect the diagnostic message provided by IDE or LSP
-		chatPanelClientInvoke("sendMessage", new ArrayList<>() {
+		chatPanelClientInvoke("executeCommand", new ArrayList<>() {
 			{
-				ChatMessage chatMessage = new ChatMessage();
-				chatMessage.setMessage(ChatViewUtils.PROMPT_FIX);
-				chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext());
-				add(chatMessage);
+				add(ChatCommand.FIX);
 			}
 		});
 	}
 
 	public void generateDocsForSelectedText() {
-		chatPanelClientInvoke("sendMessage", new ArrayList<>() {
+		chatPanelClientInvoke("executeCommand", new ArrayList<>() {
 			{
-				ChatMessage chatMessage = new ChatMessage();
-				chatMessage.setMessage(ChatViewUtils.PROMPT_GENERATE_DOCS);
-				chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext());
-				add(chatMessage);
+				add(ChatCommand.GENERATE_DOCS);
 			}
 		});
 	}
 
 	public void generateTestsForSelectedText() {
-		chatPanelClientInvoke("sendMessage", new ArrayList<>() {
+		chatPanelClientInvoke("executeCommand", new ArrayList<>() {
 			{
-				ChatMessage chatMessage = new ChatMessage();
-				chatMessage.setMessage(ChatViewUtils.PROMPT_GENERATE_TESTS);
-				chatMessage.setSelectContext(ChatViewUtils.getSelectedTextAsFileContext());
-				add(chatMessage);
+				add(ChatCommand.GENERATE_TESTS);
 			}
 		});
 	}
@@ -161,7 +186,7 @@ public void generateTestsForSelectedText() {
 	public void addSelectedTextAsContext() {
 		chatPanelClientInvoke("addRelevantContext", new ArrayList<>() {
 			{
-				add(ChatViewUtils.getSelectedTextAsFileContext());
+				add(ChatViewUtils.getSelectedTextAsEditorFileContext());
 			}
 		});
 	}
@@ -169,7 +194,7 @@ public void addSelectedTextAsContext() {
 	public void addActiveEditorAsContext() {
 		chatPanelClientInvoke("addRelevantContext", new ArrayList<>() {
 			{
-				add(ChatViewUtils.getActiveEditorAsFileContext());
+				add(ChatViewUtils.getActiveEditorAsEditorFileContext());
 			}
 		});
 	}
@@ -178,11 +203,24 @@ private void setupThemeStyle() {
 		ITheme currentTheme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
 		ColorRegistry colorRegistry = currentTheme.getColorRegistry();
 		bgColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_BG_START");
+		isDark = (bgColor.red + bgColor.green + bgColor.blue) / 3 < 128;
+
 		bgActiveColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_BG_END");
 		fgColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_TEXT_COLOR");
-		borderColor = colorRegistry.getRGB("org.eclipse.ui.workbench.ACTIVE_TAB_INNER_KEYLINE_COLOR");
+		borderColor = isDark ? new RGB(64, 64, 64) : new RGB(192, 192, 192);
+		inputBorderColor = borderColor;
+
 		primaryColor = colorRegistry.getRGB("org.eclipse.ui.workbench.LINK_COLOR");
-		isDark = (bgColor.red + bgColor.green + bgColor.blue) / 3 < 128;
+		if (primaryColor == null) {
+			primaryColor = isDark ? new RGB(55, 148, 255) : new RGB(26, 133, 255);
+		}
+		primaryFgColor = new RGB(255, 255, 255);
+		popoverColor = bgActiveColor;
+		popoverFgColor = fgColor;
+		accentColor = isDark ? new RGB(4, 57, 94)
+				: new RGB((int) (bgActiveColor.red * 0.8), (int) (bgActiveColor.green * 0.8),
+						(int) (bgActiveColor.blue * 0.8));
+		accentFgColor = fgColor;
 
 		FontRegistry fontRegistry = currentTheme.getFontRegistry();
 		FontData[] fontData = fontRegistry.getFontData("org.eclipse.jface.textfont");
@@ -206,9 +244,27 @@ private String buildCss() {
 		if (borderColor != null) {
 			css += String.format("--border: %s;", StringUtils.toHsl(borderColor));
 		}
+		if (inputBorderColor != null) {
+			css += String.format("--input: %s;", StringUtils.toHsl(inputBorderColor));
+		}
 		if (primaryColor != null) {
 			css += String.format("--primary: %s;", StringUtils.toHsl(primaryColor));
 		}
+		if (primaryFgColor != null) {
+			css += String.format("--primary-foreground: %s;", StringUtils.toHsl(primaryFgColor));
+		}
+		if (popoverColor != null) {
+			css += String.format("--popover: %s;", StringUtils.toHsl(popoverColor));
+		}
+		if (popoverFgColor != null) {
+			css += String.format("--popover-foreground: %s;", StringUtils.toHsl(popoverFgColor));
+		}
+		if (accentColor != null) {
+			css += String.format("--accent: %s;", StringUtils.toHsl(accentColor));
+		}
+		if (accentFgColor != null) {
+			css += String.format("--accent-foreground: %s;", StringUtils.toHsl(accentFgColor));
+		}
 		if (font != null) {
 			css += String.format("font: %s;", font);
 		}
@@ -220,13 +276,14 @@ private List<Object> parseArguments(final Object[] arguments) {
 		if (arguments.length < 1) {
 			return List.of();
 		}
-		return gson.fromJson(arguments[0].toString(), new TypeToken<List<Object>>(){});
+		return gson.fromJson(arguments[0].toString(), new TypeToken<List<Object>>() {
+		});
 	}
-	
+
 	private Object serializeResult(final Object result) {
 		return gson.toJson(result);
 	}
-	
+
 	private void injectFunctions() {
 		browserFunctions.add(new BrowserFunction(browser, "handleTabbyChatPanelResponse") {
 			@Override
@@ -239,12 +296,12 @@ public Object function(Object[] arguments) {
 				String uuid = (String) params.get(0);
 				String errorMessage = (String) params.get(1);
 				Object result = params.get(2);
-				
+
 				CompletableFuture<Object> future = pendingChatPanelRequest.remove(uuid);
 				if (future == null) {
 					return null;
 				}
-				
+
 				if (errorMessage != null && !errorMessage.isEmpty()) {
 					future.completeExceptionally(new Exception(errorMessage));
 				} else {
@@ -253,7 +310,7 @@ public Object function(Object[] arguments) {
 				return null;
 			}
 		});
-		
+
 		browserFunctions.add(new BrowserFunction(browser, "handleReload") {
 			@Override
 			public Object function(Object[] arguments) {
@@ -262,20 +319,6 @@ public Object function(Object[] arguments) {
 				return null;
 			}
 		});
-		
-		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelNavigate") {
-			@Override
-			public Object function(Object[] arguments) {
-				List<Object> params = parseArguments(arguments);
-				logger.debug("tabbyChatPanelNavigate: " + params);
-				if (params.size() < 1) {
-					return null;
-				}
-				FileContext context = gson.fromJson(gson.toJson(params.get(0)), FileContext.class);
-				ChatViewUtils.navigateToFileContext(context);
-				return null;
-			}
-		});
 
 		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelRefresh") {
 			@Override
@@ -285,36 +328,12 @@ public Object function(Object[] arguments) {
 				return null;
 			}
 		});
-		
-		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOnSubmitMessage") {
-			@Override
-			public Object function(Object[] arguments) {
-				List<Object> params = parseArguments(arguments);
-				if (params.size() < 1) {
-					return null;
-				}
-				String message = (String) params.get(0);
-				List<FileContext> relevantContexts = params.size() > 1
-						? relevantContexts = gson.fromJson(gson.toJson(params.get(1)), new TypeToken<List<FileContext>>() {
-						}.getType())
-						: null;
-				chatPanelClientInvoke("sendMessage", new ArrayList<>() {
-					{
-						ChatMessage chatMessage = new ChatMessage();
-						chatMessage.setMessage(message);
-						chatMessage.setRelevantContext(relevantContexts);
-						chatMessage.setActiveContext(ChatViewUtils.getSelectedTextAsFileContext());
-						add(chatMessage);
-					}
-				});
-				return null;
-			}
-		});
 
 		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOnApplyInEditor") {
 			@Override
 			public Object function(Object[] arguments) {
 				List<Object> params = parseArguments(arguments);
+				logger.debug("tabbyChatPanelOnApplyInEditor: " + params);
 				if (params.size() < 1) {
 					return null;
 				}
@@ -328,6 +347,7 @@ public Object function(Object[] arguments) {
 			@Override
 			public Object function(Object[] arguments) {
 				List<Object> params = parseArguments(arguments);
+				logger.debug("tabbyChatPanelOnLoaded: " + params);
 				if (params.size() < 1) {
 					return null;
 				}
@@ -349,6 +369,7 @@ public Object function(Object[] arguments) {
 			@Override
 			public Object function(Object[] arguments) {
 				List<Object> params = parseArguments(arguments);
+				logger.debug("tabbyChatPanelOnCopy: " + params);
 				if (params.size() < 1) {
 					return null;
 				}
@@ -371,12 +392,56 @@ public Object function(Object[] arguments) {
 		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOpenInEditor") {
 			@Override
 			public Object function(Object[] arguments) {
-				// FIXME: Not implemented
-				return serializeResult(false);
+				List<Object> params = parseArguments(arguments);
+				logger.debug("tabbyChatPanelOpenInEditor: " + params);
+				if (params.size() < 1) {
+					return null;
+				}
+				FileLocation fileLocation = ChatViewUtils.asFileLocation(params.get(0));
+				boolean success = ChatViewUtils.openInEditor(fileLocation);
+				Object result = serializeResult(success);
+				logger.debug("tabbyChatPanelOpenInEditor result: " + result);
+				return result;
+			}
+		});
+
+		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelOpenExternal") {
+			@Override
+			public Object function(Object[] arguments) {
+				List<Object> params = parseArguments(arguments);
+				logger.debug("tabbyChatPanelOpenExternal: " + params);
+				if (params.size() < 1) {
+					return null;
+				}
+				String url = (String) params.get(0);
+				ChatViewUtils.openExternal(url);
+				return null;
+			}
+		});
+		
+		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelReadWorkspaceGitRepositories") {
+			@Override
+			public Object function(Object[] arguments) {
+				logger.debug("tabbyChatPanelReadWorkspaceGitRepositories");
+				List<GitRepository> repositories = ChatViewUtils.readGitRepositoriesInWorkspace();
+				Object result = serializeResult(repositories);
+				logger.debug("tabbyChatPanelReadWorkspaceGitRepositories result: " + result);
+				return result;
+			}
+		});
+
+		browserFunctions.add(new BrowserFunction(browser, "tabbyChatPanelGetActiveEditorSelection") {
+			@Override
+			public Object function(Object[] arguments) {
+				logger.debug("tabbyChatPanelGetActiveEditorSelection");
+				EditorFileContext context = ChatViewUtils.getSelectedTextAsEditorFileContext();
+				Object result = serializeResult(context);
+				logger.debug("tabbyChatPanelGetActiveEditorSelection result: " + result);
+				return result;
 			}
 		});
 	}
-	
+
 	private void load() {
 		try {
 			// Find chat panel html file
@@ -462,7 +527,6 @@ private void updateContentToChatPanel() {
 		showChatPanel(true);
 	}
 
-	
 	// execute js functions
 
 	private void executeScript(String script) {
@@ -470,7 +534,7 @@ private void executeScript(String script) {
 			browser.execute(script);
 		});
 	}
-	
+
 	private void showMessage(String message) {
 		if (message != null) {
 			executeScript(String.format("showMessage('%s')", message));
@@ -503,6 +567,13 @@ private void applyStyle() {
 
 	private void initChatPanel() {
 		isChatPanelLoaded = true;
+		browser.getDisplay().timerExec(100, () -> {
+			updateContentToChatPanel();
+			pendingScripts.forEach((script) -> {
+				executeScript(script);
+			});
+			pendingScripts.clear();
+		});
 		chatPanelClientInvoke("init", new ArrayList<>() {
 			{
 				add(new HashMap<>() {
@@ -522,15 +593,8 @@ private void initChatPanel() {
 				add(isDark ? "dark" : "light");
 			}
 		});
-		browser.getDisplay().timerExec(100, () -> {
-			updateContentToChatPanel();
-			pendingScripts.forEach((script) -> {
-				executeScript(script);
-			});
-			pendingScripts.clear();
-		});
 	}
-	
+
 	private String wrapJsFunction(String name) {
 		return String.format(
 			String.join("\n",
@@ -538,44 +602,46 @@ private String wrapJsFunction(String name) {
 				"  return new Promise((resolve, reject) => {",
 				"    const paramsJson = JSON.stringify(args)",
 				"    const result = %s(paramsJson)",
-				"    resolve(result)",
+				"    resolve(JSON.parse(result))",
 				"  });",
 				"}"
 			),
 			name
 		);
 	}
-	
+
 	private void createChatPanelClient() {
 		String script = String.format(
 			String.join("\n",
 				"if (!window.tabbyChatPanelClient) {",
 				"  window.tabbyChatPanelClient = TabbyThreads.createThreadFromIframe(getChatPanel(), {",
 				"    expose: {",
-				"      navigate: %s,",
 				"      refresh: %s,",
-				"      onSubmitMessage: %s,",
 				"      onApplyInEditor: %s,",
 				"      onLoaded: %s,",
 				"      onCopy: %s,",
 				"      onKeyboardEvent: %s,",
 				"      openInEditor: %s,",
+				"      openExternal: %s,",
+				"      readWorkspaceGitRepositories: %s,",
+				"      getActiveEditorSelection: %s,",
 				"    }",
 				"  })",
 				"}"
 			),
-			wrapJsFunction("tabbyChatPanelNavigate"),
 			wrapJsFunction("tabbyChatPanelRefresh"),
-			wrapJsFunction("tabbyChatPanelOnSubmitMessage"),
 			wrapJsFunction("tabbyChatPanelOnApplyInEditor"),
 			wrapJsFunction("tabbyChatPanelOnLoaded"),
 			wrapJsFunction("tabbyChatPanelOnCopy"),
 			wrapJsFunction("tabbyChatPanelOnKeyboardEvent"),
-			wrapJsFunction("tabbyChatPanelOpenInEditor")
+			wrapJsFunction("tabbyChatPanelOpenInEditor"),
+			wrapJsFunction("tabbyChatPanelOpenExternal"),
+			wrapJsFunction("tabbyChatPanelReadWorkspaceGitRepositories"),
+			wrapJsFunction("tabbyChatPanelGetActiveEditorSelection")
 		);
 		executeScript(script);
 	}
-	
+
 	private CompletableFuture<Object> chatPanelClientInvoke(String method, List<Object> params) {
 		CompletableFuture<Object> future = new CompletableFuture<>();
 		String uuid = UUID.randomUUID().toString();
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java
index 8b9197a92f60..f3992776b9ec 100644
--- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/ChatViewUtils.java
@@ -1,10 +1,15 @@
 package com.tabbyml.tabby4eclipse.chat;
 
 import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.jface.text.IDocument;
@@ -13,6 +18,7 @@
 import org.eclipse.swt.dnd.Clipboard;
 import org.eclipse.swt.dnd.TextTransfer;
 import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.program.Program;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IWorkbenchPage;
@@ -21,25 +27,22 @@
 import org.eclipse.ui.ide.ResourceUtil;
 import org.eclipse.ui.texteditor.ITextEditor;
 
+import com.google.gson.Gson;
 import com.tabbyml.tabby4eclipse.Logger;
 import com.tabbyml.tabby4eclipse.Version;
-import com.tabbyml.tabby4eclipse.chat.ChatMessage.FileContext;
 import com.tabbyml.tabby4eclipse.editor.EditorUtils;
 import com.tabbyml.tabby4eclipse.git.GitProvider;
-import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository;
 import com.tabbyml.tabby4eclipse.lsp.protocol.GitRepositoryParams;
 
 public class ChatViewUtils {
 	private static final String ID = "com.tabbyml.tabby4eclipse.views.chat";
 
 	private static final String MIN_SERVER_VERSION = "0.18.0";
-	private static final String CHAT_PANEL_API_VERSION = "0.4.0";
+	private static final String CHAT_PANEL_API_VERSION = "0.5.0";
 	private static Logger logger = new Logger("ChatView");
 
-	public static final String PROMPT_EXPLAIN = "Explain the selected code:";
-	public static final String PROMPT_FIX = "Identify and fix potential bugs in the selected code:";
-	public static final String PROMPT_GENERATE_DOCS = "Generate documentation for the selected code:";
-	public static final String PROMPT_GENERATE_TESTS = "Generate a unit test for the selected code:";
+	private static final Gson gson = new Gson();
+	private static final Map<String, String> gitRemoteUrlToLocalRoot = new HashMap<>();
 
 	public static ChatView openChatView() {
 		IWorkbenchPage page = EditorUtils.getActiveWorkbenchPage();
@@ -104,119 +107,131 @@ public static String checkChatPanelApiVersion(String version) {
 		return null;
 	}
 
-	public static FileContext getSelectedTextAsFileContext() {
+	public static EditorFileContext getSelectedTextAsEditorFileContext() {
 		ITextEditor activeTextEditor = EditorUtils.getActiveTextEditor();
 		if (activeTextEditor == null) {
 			return null;
 		}
-		FileContext context = new FileContext();
+		IFile file = ResourceUtil.getFile(activeTextEditor.getEditorInput());
+		URI fileUri = file.getLocationURI();
 		ISelection selection = activeTextEditor.getSelectionProvider().getSelection();
 		if (selection instanceof ITextSelection textSelection) {
 			if (!textSelection.isEmpty()) {
 				String content = textSelection.getText();
 				if (!content.isBlank()) {
-					context.setContent(content);
-					context.setRange(new FileContext.LineRange(textSelection.getStartLine() + 1,
-							textSelection.getEndLine() + 1));
-				}
-			}
-		}
-		if (context.getContent() == null) {
-			return null;
-		}
-
-		IFile file = ResourceUtil.getFile(activeTextEditor.getEditorInput());
-		URI fileUri = file.getLocationURI();
-		if (file != null) {
-			GitRepository gitInfo = GitProvider.getInstance()
-					.getRepository(new GitRepositoryParams(fileUri.toString()));
-			IProject project = file.getProject();
-			if (gitInfo != null) {
-				try {
-					context.setGitUrl(gitInfo.getRemoteUrl());
-					String relativePath = new URI(gitInfo.getRoot()).relativize(fileUri).getPath();
-					context.setFilePath(relativePath);
-				} catch (Exception e) {
-					logger.error("Failed to get git info.", e);
+					return new EditorFileContext(fileUriToChatPanelFilepath(fileUri),
+							new LineRange(textSelection.getStartLine() + 1, textSelection.getEndLine() + 1), content);
 				}
-			} else if (project != null) {
-				URI projectRoot = project.getLocationURI();
-				String relativePath = projectRoot.relativize(fileUri).getPath();
-				context.setFilePath(relativePath);
-			} else {
-				context.setFilePath(fileUri.toString());
 			}
 		}
-		return context;
+		return null;
 	}
 
-	public static FileContext getActiveEditorAsFileContext() {
+	public static EditorFileContext getActiveEditorAsEditorFileContext() {
 		ITextEditor activeTextEditor = EditorUtils.getActiveTextEditor();
 		if (activeTextEditor == null) {
 			return null;
 		}
-		FileContext context = new FileContext();
-
-		IDocument document = EditorUtils.getDocument(activeTextEditor);
-		context.setRange(new FileContext.LineRange(1, document.getNumberOfLines()));
-		context.setContent(document.get());
-
 		IFile file = ResourceUtil.getFile(activeTextEditor.getEditorInput());
 		URI fileUri = file.getLocationURI();
-		if (file != null) {
-			GitRepository gitInfo = GitProvider.getInstance()
-					.getRepository(new GitRepositoryParams(fileUri.toString()));
-			IProject project = file.getProject();
-			if (gitInfo != null) {
-				try {
-					context.setGitUrl(gitInfo.getRemoteUrl());
-					String relativePath = new URI(gitInfo.getRoot()).relativize(fileUri).getPath();
-					context.setFilePath(relativePath);
-				} catch (Exception e) {
-					logger.error("Failed to get git info.", e);
-				}
-			} else if (project != null) {
-				URI projectRoot = project.getLocationURI();
-				String relativePath = projectRoot.relativize(fileUri).getPath();
-				context.setFilePath(relativePath);
-			} else {
-				context.setFilePath(fileUri.toString());
-			}
+		IDocument document = EditorUtils.getDocument(activeTextEditor);
+		String content = document.get();
+		if (!content.isBlank()) {
+			return new EditorFileContext(fileUriToChatPanelFilepath(fileUri), null, content);
 		}
-
-		return context;
+		return null;
 	}
 
-	public static void navigateToFileContext(FileContext context) {
-		logger.info("Navigate to file: " + context.getFilePath() + ", line: " + context.getRange().getStart());
-		// FIXME(@icycode): the base path could be a git repository root, but it cannot
-		// be determined here
-		IFile file = null;
-		ITextEditor activeTextEditor = EditorUtils.getActiveTextEditor();
-		if (activeTextEditor != null) {
-			// try find file in the project of the active editor
-			IFile activeFile = ResourceUtil.getFile(activeTextEditor.getEditorInput());
-			if (activeFile != null) {
-				file = activeFile.getProject().getFile(new Path(context.getFilePath()));
-			}
-		} else {
-			// try find file in the workspace
-			file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path(context.getFilePath()));
+	public static boolean openInEditor(FileLocation fileLocation) {
+		if (fileLocation == null) {
+			return false;
 		}
+		Filepath filepath = fileLocation.getFilepath();
+		URI fileUri = null;
 		try {
+			switch (filepath.getKind()) {
+			case Filepath.Kind.URI:
+				FilepathUri filepathUri = (FilepathUri) filepath;
+				fileUri = new URI(filepathUri.getUri());
+				break;
+
+			case Filepath.Kind.GIT:
+				FilepathInGitRepository filepathInGit = (FilepathInGitRepository) filepath;
+				String gitLocalRoot = gitRemoteUrlToLocalRoot.get(filepathInGit.getGitUrl());
+				if (gitLocalRoot != null) {
+					fileUri = new URI(gitLocalRoot + "/" + filepathInGit.getFilepath());
+				}
+				break;
+
+			default:
+				fileUri = null;
+				break;
+			}
+
+			if (fileUri == null) {
+				throw new Exception("Cannot parse as file uri.");
+			}
+
+			IFile file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path(fileUri.getPath()));
 			if (file != null && file.exists()) {
 				IEditorPart editorPart = IDE.openEditor(EditorUtils.getActiveWorkbenchPage(), file);
+
 				if (editorPart instanceof ITextEditor textEditor) {
 					IDocument document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
-					int offset = document.getLineOffset(context.getRange().getStart() - 1);
-					textEditor.selectAndReveal(offset, 0);
+					Object location = fileLocation.getLocation();
+					Position position;
+
+					if (location instanceof Number lineNumberValue) {
+						position = new Position(lineNumberValue.intValue() - 1, 0);
+					} else if (location instanceof Position positionValue) {
+						position = new Position(positionValue.getLine() - 1, positionValue.getCharacter() - 1);
+					} else if (location instanceof LineRange lineRangeValue) {
+						position = new Position(lineRangeValue.getStart() - 1, 0);
+					} else if (location instanceof PositionRange positionRangeValue) {
+						position = new Position(positionRangeValue.getStart().getLine() - 1,
+								positionRangeValue.getStart().getCharacter() - 1);
+					} else {
+						position = null;
+					}
+
+					if (position != null) {
+						int offset = document.getLineOffset(position.getLine()) + position.getCharacter();
+						textEditor.selectAndReveal(offset, 0);
+					}
 				}
+				return true;
+			} else {
+				return false;
 			}
 		} catch (Exception e) {
-			logger.error("Failed to navigate to file: " + context.getFilePath(), e);
+			logger.error("Failed to open in editor.", e);
+			return false;
 		}
 	}
 
+	public static void openExternal(String url) {
+		Program.launch(url);
+	}
+	
+	public static List<GitRepository> readGitRepositoriesInWorkspace() {
+		List<GitRepository> repositories = new ArrayList<>();
+        IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+        IProject[] projects = workspaceRoot.getProjects();
+        
+        for (IProject project : projects) {
+        	try {
+        		URI projectRootUri = project.getLocation().toFile().toURI();
+                com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository repo = GitProvider.getInstance().getRepository(new GitRepositoryParams(projectRootUri.toString()));
+                if (repo != null) {
+                	repositories.add(new GitRepository(repo.getRemoteUrl()));
+                }
+            } catch (Exception e) {
+                logger.warn("Error when read git repository.", e);
+            }
+        }
+        return repositories;
+	}
+
 	public static void setClipboardContent(String content) {
 		Display display = Display.getCurrent();
 		if (display == null) {
@@ -243,4 +258,76 @@ public static void applyContentInEditor(String content) {
 			}
 		}
 	}
+
+	public static Filepath fileUriToChatPanelFilepath(URI fileUri) {
+		String fileUriString = fileUri.toString();
+        com.tabbyml.tabby4eclipse.lsp.protocol.GitRepository gitRepo = GitProvider.getInstance().getRepository(new GitRepositoryParams(fileUriString));
+		String gitUrl = (gitRepo != null) ? gitRepo.getRemoteUrl() : null;
+		if (gitUrl != null) {
+			gitRemoteUrlToLocalRoot.put(gitUrl, gitRepo.getRoot());
+		}
+
+		if (gitUrl != null && fileUriString.startsWith(gitRepo.getRoot())) {
+			try {
+				String relativePath = new URI(gitRepo.getRoot()).relativize(fileUri).getPath();
+				return new FilepathInGitRepository(relativePath, gitUrl);
+			} catch (URISyntaxException e) {
+				return new FilepathUri(fileUriString);
+			}
+		} else {
+			return new FilepathUri(fileUriString);
+		}
+	}
+
+	public static FileLocation asFileLocation(Object obj) {
+		if (!(obj instanceof Map)) {
+			return null;
+		}
+
+		Map<?, ?> map = (Map<?, ?>) obj;
+
+		if (!map.containsKey("filepath")) {
+			return null;
+		}
+
+		Object filepathValue = map.get("filepath");
+		Filepath filepath = null;
+
+		if (filepathValue instanceof Map) {
+			Map<?, ?> filepathMap = (Map<?, ?>) filepathValue;
+			if (filepathMap.containsKey("kind")) {
+				String kind = (String) filepathMap.get("kind");
+				if (Filepath.Kind.GIT.equals(kind)) {
+					filepath = gson.fromJson(gson.toJson(filepathValue), FilepathInGitRepository.class);
+				} else if (Filepath.Kind.URI.equals(kind)) {
+					filepath = gson.fromJson(gson.toJson(filepathValue), FilepathUri.class);
+				}
+			}
+		}
+
+		if (filepath == null) {
+			return null;
+		}
+
+		Object locationValue = map.get("location");
+		Object location = null;
+
+		if (locationValue instanceof Number) {
+			location = locationValue;
+		} else if (locationValue instanceof Map) {
+			Map<?, ?> locationMap = (Map<?, ?>) locationValue;
+			if (locationMap.containsKey("line")) {
+				location = gson.fromJson(gson.toJson(locationValue), Position.class);
+			} else if (locationMap.containsKey("start")) {
+				Object startValue = locationMap.get("start");
+				if (startValue instanceof Number) {
+					location = gson.fromJson(gson.toJson(locationValue), LineRange.class);
+				} else if (startValue instanceof Map) {
+					location = gson.fromJson(gson.toJson(locationValue), PositionRange.class);
+				}
+			}
+		}
+
+		return new FileLocation(filepath, location);
+	}
 }
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/EditorFileContext.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/EditorFileContext.java
new file mode 100644
index 000000000000..a09fecc192a8
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/EditorFileContext.java
@@ -0,0 +1,31 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class EditorFileContext {
+	private final String kind;
+	private final Filepath filepath;
+	private final Range range;
+	private final String content;
+
+	public EditorFileContext(Filepath filepath, Range range, String content) {
+		this.kind = "file";
+		this.filepath = filepath;
+		this.range = range;
+		this.content = content;
+	}
+
+	public String getKind() {
+		return kind;
+	}
+
+	public Filepath getFilepath() {
+		return filepath;
+	}
+
+	public Range getRange() {
+		return range;
+	}
+
+	public String getContent() {
+		return content;
+	}
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FileLocation.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FileLocation.java
new file mode 100644
index 000000000000..215595116add
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FileLocation.java
@@ -0,0 +1,19 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class FileLocation {
+	private final Filepath filepath;
+	private final Object location;
+
+	public FileLocation(Filepath filepath, Object location) {
+		this.filepath = filepath;
+		this.location = location;
+	}
+
+	public Filepath getFilepath() {
+		return filepath;
+	}
+
+	public Object getLocation() {
+		return location;
+	}
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Filepath.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Filepath.java
new file mode 100644
index 000000000000..8af078ecd86e
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Filepath.java
@@ -0,0 +1,18 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public abstract class Filepath {
+	private final String kind;
+
+	protected Filepath(String kind) {
+		this.kind = kind;
+	}
+
+	public String getKind() {
+		return kind;
+	}
+
+	public static class Kind {
+		public static final String GIT = "git";
+		public static final String URI = "uri";
+	}
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathInGitRepository.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathInGitRepository.java
new file mode 100644
index 000000000000..8c59edbbd12f
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathInGitRepository.java
@@ -0,0 +1,30 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class FilepathInGitRepository extends Filepath {
+	private final String filepath;
+	private final String gitUrl;
+	private final String revision;
+
+	public FilepathInGitRepository(String filepath, String gitUrl) {
+		this(filepath, gitUrl, null);
+	}
+	
+	public FilepathInGitRepository(String filepath, String gitUrl, String revision) {
+		super(Kind.GIT);
+		this.filepath = filepath;
+		this.gitUrl = gitUrl;
+		this.revision = revision;
+	}
+
+	public String getFilepath() {
+		return filepath;
+	}
+
+	public String getGitUrl() {
+		return gitUrl;
+	}
+
+	public String getRevision() {
+		return revision;
+	}
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathUri.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathUri.java
new file mode 100644
index 000000000000..c47cfe6c6b9f
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/FilepathUri.java
@@ -0,0 +1,14 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class FilepathUri extends Filepath {
+	private final String uri;
+
+	public FilepathUri(String uri) {
+		super(Kind.URI);
+		this.uri = uri;
+	}
+
+	public String getUri() {
+		return uri;
+	}
+}
\ No newline at end of file
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/GitRepository.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/GitRepository.java
new file mode 100644
index 000000000000..32f5c2d31ce5
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/GitRepository.java
@@ -0,0 +1,13 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class GitRepository {
+	private final String url;
+
+	public GitRepository(String url) {
+		this.url = url;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+}
\ No newline at end of file
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/LineRange.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/LineRange.java
new file mode 100644
index 000000000000..9fc0958077c4
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/LineRange.java
@@ -0,0 +1,19 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class LineRange extends Range {
+	private final int start;
+	private final int end;
+
+	public LineRange(int start, int end) {
+		this.start = start;
+		this.end = end;
+	}
+
+	public int getStart() {
+		return start;
+	}
+
+	public int getEnd() {
+		return end;
+	}
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Position.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Position.java
new file mode 100644
index 000000000000..cdfc4c2ff86c
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Position.java
@@ -0,0 +1,19 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class Position {
+	private final int line;
+	private final int character;
+
+	public Position(int line, int character) {
+		this.line = line;
+		this.character = character;
+	}
+
+	public int getLine() {
+		return line;
+	}
+
+	public int getCharacter() {
+		return character;
+	}
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/PositionRange.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/PositionRange.java
new file mode 100644
index 000000000000..8d216a636e16
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/PositionRange.java
@@ -0,0 +1,19 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public class PositionRange extends Range {
+	private final Position start;
+	private final Position end;
+
+	public PositionRange(Position start, Position end) {
+		this.start = start;
+		this.end = end;
+	}
+
+	public Position getStart() {
+		return start;
+	}
+
+	public Position getEnd() {
+		return end;
+	}
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Range.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Range.java
new file mode 100644
index 000000000000..5abc6534fd45
--- /dev/null
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/chat/Range.java
@@ -0,0 +1,4 @@
+package com.tabbyml.tabby4eclipse.chat;
+
+public abstract class Range {
+}
diff --git a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java
index bfb345699057..611fbe78790f 100644
--- a/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java
+++ b/clients/eclipse/plugin/src/com/tabbyml/tabby4eclipse/git/EclipseJGitProvider.java
@@ -66,7 +66,7 @@ public GitRepository getRepository(GitRepositoryParams params) {
 				return null;
 			}
 		} catch (Exception e) {
-			logger.warn("Failed to get repository for: " + params.getUri(), e);
+			logger.debug("Failed to get repository for: " + params.getUri());
 			return null;
 		}
 	}
@@ -92,7 +92,7 @@ public GitDiffResult getDiff(GitDiffParams params) {
 				return null;
 			}
 		} catch (Exception e) {
-			logger.warn("Failed to get diff for: " + params.getRepository(), e);
+			logger.debug("Failed to get diff for: " + params.getRepository());
 			return null;
 		}
 	}