> = {
"chat.workspaceUnpin": "Unpin",
"chat.workspaceRename": "Rename",
"chat.workspaceRemove": "Remove workspace",
+ "chat.workspaceBrowseInFileTree": "Browse in File Tree",
"chat.workspaceRemoveConfirm": "Remove \"{name}\"?",
"chat.workspaceRemoveRunning": "A background task is running, so this workspace cannot be removed yet.",
"chat.workspaceRemoveDescription":
diff --git a/crates/agent-gui/src/components/chat/ChatHistorySidebar.tsx b/crates/agent-gui/src/components/chat/ChatHistorySidebar.tsx
index 07be5ff6..24619b05 100644
--- a/crates/agent-gui/src/components/chat/ChatHistorySidebar.tsx
+++ b/crates/agent-gui/src/components/chat/ChatHistorySidebar.tsx
@@ -14,6 +14,8 @@ import {
ChevronRight,
Edit3,
Folder,
+ FolderOpen,
+ FolderTree,
Link2,
MessageSquareText,
MoreHorizontal,
@@ -66,6 +68,8 @@ type ChatHistorySidebarProps = {
onCreateProject?: () => void;
onSelectProject?: (project: WorkspaceProject) => void;
onNewConversationForProject?: (project: WorkspaceProject) => void;
+ onBrowseProjectInFileTree?: (project: WorkspaceProject) => void;
+ onBrowseProjectInSystemFileManager?: (project: WorkspaceProject) => void;
onStartRenamingProject?: (project: WorkspaceProject) => void;
onProjectRenameDraftChange?: (value: string) => void;
onCommitProjectRename?: () => void;
@@ -407,6 +411,8 @@ const ProjectRow = memo(function ProjectRow(props: {
renameDraft: string;
onSelectProject: (project: WorkspaceProject) => void;
onNewConversationForProject: (project: WorkspaceProject) => void;
+ onBrowseProjectInFileTree?: (project: WorkspaceProject) => void;
+ onBrowseProjectInSystemFileManager?: (project: WorkspaceProject) => void;
onStartRenamingProject: (project: WorkspaceProject) => void;
onProjectRenameDraftChange: (value: string) => void;
onCommitProjectRename: () => void;
@@ -425,6 +431,8 @@ const ProjectRow = memo(function ProjectRow(props: {
renameDraft,
onSelectProject,
onNewConversationForProject,
+ onBrowseProjectInFileTree,
+ onBrowseProjectInSystemFileManager,
onStartRenamingProject,
onProjectRenameDraftChange,
onCommitProjectRename,
@@ -463,6 +471,14 @@ const ProjectRow = memo(function ProjectRow(props: {
onSetProjectPinned(project, !isPinned);
}, [isPinned, onSetProjectPinned, project]);
+ const handleBrowseInFileTree = useCallback(() => {
+ onBrowseProjectInFileTree?.(project);
+ }, [onBrowseProjectInFileTree, project]);
+
+ const handleBrowseInSystemFileManager = useCallback(() => {
+ onBrowseProjectInSystemFileManager?.(project);
+ }, [onBrowseProjectInSystemFileManager, project]);
+
if (isPendingRemove) {
return (
@@ -703,6 +719,21 @@ const ProjectRow = memo(function ProjectRow(props: {
>
) : null}
+ {onBrowseProjectInFileTree ? (
+
+
+ {t("chat.workspaceBrowseInFileTree")}
+
+ ) : null}
+ {onBrowseProjectInSystemFileManager ? (
+
+
+ {t("chat.workspaceBrowseInSystemFileManager")}
+
+ ) : null}
>
@@ -801,6 +832,8 @@ export const ChatHistorySidebar = memo(function ChatHistorySidebar(props: ChatHi
onCreateProject,
onSelectProject,
onNewConversationForProject,
+ onBrowseProjectInFileTree,
+ onBrowseProjectInSystemFileManager,
onStartRenamingProject,
onProjectRenameDraftChange,
onCommitProjectRename,
@@ -853,6 +886,12 @@ export const ChatHistorySidebar = memo(function ChatHistorySidebar(props: ChatHi
const handleNewConversationForProject = useStableEvent((project: WorkspaceProject) => {
onNewConversationForProject?.(project);
});
+ const handleBrowseProjectInFileTree = useStableEvent((project: WorkspaceProject) => {
+ onBrowseProjectInFileTree?.(project);
+ });
+ const handleBrowseProjectInSystemFileManager = useStableEvent((project: WorkspaceProject) => {
+ onBrowseProjectInSystemFileManager?.(project);
+ });
const handleStartRenamingProject = useStableEvent((project: WorkspaceProject) => {
onStartRenamingProject?.(project);
});
@@ -1355,6 +1394,14 @@ export const ChatHistorySidebar = memo(function ChatHistorySidebar(props: ChatHi
renameDraft={projectRenameDraft}
onSelectProject={handleSelectProject}
onNewConversationForProject={handleNewConversationForProject}
+ onBrowseProjectInFileTree={
+ onBrowseProjectInFileTree ? handleBrowseProjectInFileTree : undefined
+ }
+ onBrowseProjectInSystemFileManager={
+ onBrowseProjectInSystemFileManager
+ ? handleBrowseProjectInSystemFileManager
+ : undefined
+ }
onStartRenamingProject={handleStartRenamingProject}
onProjectRenameDraftChange={handleProjectRenameDraftChange}
onCommitProjectRename={handleCommitProjectRename}
diff --git a/crates/agent-gui/src/i18n/config.ts b/crates/agent-gui/src/i18n/config.ts
index c16178cf..8b1a4819 100644
--- a/crates/agent-gui/src/i18n/config.ts
+++ b/crates/agent-gui/src/i18n/config.ts
@@ -39,9 +39,12 @@ export const translations: Record> = {
"chat.workspaceUnpin": "取消置顶",
"chat.workspaceRename": "修改标题",
"chat.workspaceRemove": "移除工作空间",
+ "chat.workspaceBrowseInFileTree": "在文件树浏览",
+ "chat.workspaceBrowseInSystemFileManager": "在资源管理器浏览",
"chat.workspaceRemoveConfirm": "移除「{name}」?",
"chat.workspaceRemoveRunning": "后台任务运行中,暂时不能移除。",
"chat.workspaceRemoveDescription": "会删除此工作空间下的历史对话,不会删除文件夹。",
+ "chat.workspaceOpenSystemFileManagerFailed": "打开资源管理器失败",
"chat.exitConfirmTitle": "退出 LiveAgent?",
"chat.exitConfirmSubtitle": "当前仍有终端任务在运行。",
"chat.exitConfirmRunningLabel": "正在运行的 Terminal",
@@ -1186,10 +1189,13 @@ export const translations: Record> = {
"chat.workspaceUnpin": "Unpin",
"chat.workspaceRename": "Rename",
"chat.workspaceRemove": "Remove workspace",
+ "chat.workspaceBrowseInFileTree": "Browse in File Tree",
+ "chat.workspaceBrowseInSystemFileManager": "Browse in System File Manager",
"chat.workspaceRemoveConfirm": "Remove \"{name}\"?",
"chat.workspaceRemoveRunning": "A background task is running, so this workspace cannot be removed yet.",
"chat.workspaceRemoveDescription":
"This deletes conversations under the workspace, but it does not delete the folder.",
+ "chat.workspaceOpenSystemFileManagerFailed": "Failed to open the file manager",
"chat.exitConfirmTitle": "Exit LiveAgent?",
"chat.exitConfirmSubtitle": "Terminal tasks are still running.",
"chat.exitConfirmRunningLabel": "Running Terminal sessions",
diff --git a/crates/agent-gui/src/pages/ChatPage.tsx b/crates/agent-gui/src/pages/ChatPage.tsx
index 16473f7f..6973998c 100644
--- a/crates/agent-gui/src/pages/ChatPage.tsx
+++ b/crates/agent-gui/src/pages/ChatPage.tsx
@@ -2,6 +2,7 @@ import type { Context, UserMessage } from "@earendil-works/pi-ai";
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { getCurrentWebview } from "@tauri-apps/api/webview";
+import { revealItemInDir } from "@tauri-apps/plugin-opener";
import { type SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { MacOsTitleBarSpacer, MacOsTitleBarToggle } from "../components/MacOsTitleBarSpacer";
import { ChatHistorySidebar } from "../components/chat/ChatHistorySidebar";
@@ -1009,6 +1010,52 @@ export function ChatPage(props: ChatPageProps) {
[activateWorkspaceProject, checkWorkspaceProjectDirectory],
);
+ const handleBrowseWorkspaceProjectInFileTree = useCallback(
+ async (project: WorkspaceProject) => {
+ if (!(await checkWorkspaceProjectDirectory(project))) {
+ return;
+ }
+ const pathKey = workspaceProjectPathKey(project.path);
+ if (!pathKey) {
+ return;
+ }
+
+ setActiveView("chat");
+ setProjectToolsPanelOpen(true);
+ activateWorkspaceProject(project);
+ setSettings((prev) =>
+ updateProjectToolsFileTreeOpen(
+ updateCustomSettings(prev, {
+ projectToolsPanel: {
+ ...prev.customSettings.projectToolsPanel,
+ activeTab: "fileTree",
+ },
+ }),
+ pathKey,
+ true,
+ ),
+ );
+ },
+ [activateWorkspaceProject, checkWorkspaceProjectDirectory, setSettings],
+ );
+
+ const handleBrowseWorkspaceProjectInSystemFileManager = useCallback(
+ async (project: WorkspaceProject) => {
+ if (!(await checkWorkspaceProjectDirectory(project))) {
+ return;
+ }
+
+ try {
+ await revealItemInDir(project.path.trim());
+ } catch (error) {
+ const message = asErrorMessage(error, t("chat.workspaceOpenSystemFileManagerFailed"));
+ setHistoryError(message);
+ setErrorMessage(message);
+ }
+ },
+ [checkWorkspaceProjectDirectory, setErrorMessage, setHistoryError, t],
+ );
+
const handleOpenCreateWorkspaceProject = useCallback(async () => {
try {
const picked = await invoke("system_pick_folder", {
@@ -4094,6 +4141,8 @@ export function ChatPage(props: ChatPageProps) {
onCreateProject={handleOpenCreateWorkspaceProject}
onSelectProject={handleSelectWorkspaceProject}
onNewConversationForProject={handleNewConversationForProject}
+ onBrowseProjectInFileTree={handleBrowseWorkspaceProjectInFileTree}
+ onBrowseProjectInSystemFileManager={handleBrowseWorkspaceProjectInSystemFileManager}
onStartRenamingProject={handleStartRenamingWorkspaceProject}
onProjectRenameDraftChange={setProjectRenameDraft}
onCommitProjectRename={handleCommitWorkspaceProjectRename}