From 5e532ad83fd3ce08d58b379423822dc584d3c349 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sat, 9 Aug 2025 13:22:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=96=87=E4=BB=B6=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bun.lock | 12 + package.json | 2 + src-tauri/src/commands/mod.rs | 2 + src-tauri/src/main.rs | 21 ++ src/components/ClaudeCodeSession.tsx | 427 ++++++++++++++++----------- src/components/TabContent.tsx | 2 +- src/locales/en/common.json | 33 ++- src/locales/zh/common.json | 33 ++- 8 files changed, 363 insertions(+), 169 deletions(-) diff --git a/bun.lock b/bun.lock index 3f5e70e..25f7135 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,8 @@ "name": "claudia", "dependencies": { "@hookform/resolvers": "^3.9.1", + "@monaco-editor/react": "^4.7.0", + "@radix-ui/react-context-menu": "^2.2.15", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.1", @@ -224,6 +226,10 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + "@monaco-editor/loader": ["@monaco-editor/loader@1.5.0", "https://registry.npmmirror.com/@monaco-editor/loader/-/loader-1.5.0.tgz", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw=="], + + "@monaco-editor/react": ["@monaco-editor/react@4.7.0", "https://registry.npmmirror.com/@monaco-editor/react/-/react-4.7.0.tgz", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="], + "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="], @@ -264,6 +270,8 @@ "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.15", "https://registry.npmmirror.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.15.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw=="], + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw=="], "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], @@ -854,6 +862,8 @@ "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + "monaco-editor": ["monaco-editor@0.52.2", "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.52.2.tgz", {}, "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ=="], + "motion-dom": ["motion-dom@12.18.1", "", { "dependencies": { "motion-utils": "^12.18.1" } }, "sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w=="], "motion-utils": ["motion-utils@12.18.1", "", {}, "sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA=="], @@ -970,6 +980,8 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "state-local": ["state-local@1.0.7", "https://registry.npmmirror.com/state-local/-/state-local-1.0.7.tgz", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], "style-to-js": ["style-to-js@1.1.17", "", { "dependencies": { "style-to-object": "1.0.9" } }, "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA=="], diff --git a/package.json b/package.json index 2331db4..847d7b9 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ }, "dependencies": { "@hookform/resolvers": "^3.9.1", + "@monaco-editor/react": "^4.7.0", + "@radix-ui/react-context-menu": "^2.2.15", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.1", diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 02e70b6..bfda7e8 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -9,3 +9,5 @@ pub mod language; pub mod relay_stations; pub mod relay_adapters; pub mod packycode_nodes; +pub mod filesystem; +pub mod git; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6d0fbd1..47be208 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -60,6 +60,13 @@ use commands::relay_adapters::{ use commands::packycode_nodes::{ test_all_packycode_nodes, auto_select_best_node, get_packycode_nodes, }; +use commands::filesystem::{ + read_directory_tree, search_files_by_name, get_file_info, watch_directory, + read_file, write_file, +}; +use commands::git::{ + get_git_status, get_git_history, get_git_branches, get_git_diff, +}; use process::ProcessRegistryState; use std::sync::Mutex; use tauri::Manager; @@ -296,6 +303,20 @@ fn main() { test_all_packycode_nodes, auto_select_best_node, get_packycode_nodes, + + // File System + read_directory_tree, + search_files_by_name, + get_file_info, + watch_directory, + read_file, + write_file, + + // Git + get_git_status, + get_git_history, + get_git_branches, + get_git_diff, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/components/ClaudeCodeSession.tsx b/src/components/ClaudeCodeSession.tsx index 790d0dd..8ff2b27 100644 --- a/src/components/ClaudeCodeSession.tsx +++ b/src/components/ClaudeCodeSession.tsx @@ -33,6 +33,9 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Di import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { SplitPane } from "@/components/ui/split-pane"; import { WebviewPreview } from "./WebviewPreview"; +import { FileExplorerPanel } from "./FileExplorerPanel"; +import { GitPanel } from "./GitPanel"; +import { FileEditor } from "./FileEditor"; import type { ClaudeStreamMessage } from "./AgentExecution"; import { useVirtualizer } from "@tanstack/react-virtual"; import { useTrackEvent, useComponentMetrics, useWorkflowTracking } from "@/hooks"; @@ -110,6 +113,15 @@ export const ClaudeCodeSession: React.FC = ({ // Add collapsed state for queued prompts const [queuedPromptsCollapsed, setQueuedPromptsCollapsed] = useState(false); + // New state for file explorer and git panel + const [showFileExplorer, setShowFileExplorer] = useState(false); + const [showGitPanel, setShowGitPanel] = useState(false); + const [fileExplorerWidth] = useState(280); + const [gitPanelWidth] = useState(320); + + // File editor state + const [editingFile, setEditingFile] = useState(null); + const parentRef = useRef(null); const unlistenRefs = useRef([]); const hasActiveSessionRef = useRef(false); @@ -438,7 +450,7 @@ export const ClaudeCodeSession: React.FC = ({ // If already loading, queue the prompt if (isLoading) { const newPrompt = { - id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + id: `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, prompt, model }; @@ -1022,7 +1034,7 @@ export const ClaudeCodeSession: React.FC = ({ setIsLoading(true); setError(null); - const newSessionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + const newSessionId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; await api.forkFromCheckpoint( forkCheckpointId, effectiveSession.id, @@ -1280,6 +1292,48 @@ export const ClaudeCodeSession: React.FC = ({
+ {/* File Explorer Toggle */} + {projectPath && ( + + + + + + +

File Explorer

+
+
+
+ )} + + {/* Git Panel Toggle */} + {projectPath && ( + + + + + + +

Git Panel

+
+
+
+ )} + {projectPath && onProjectSettings && ( @@ -1352,7 +1406,7 @@ export const ClaudeCodeSession: React.FC = ({ -

Timeline Navigator

+

{t('app.timeline')}

@@ -1398,11 +1452,33 @@ export const ClaudeCodeSession: React.FC = ({
- {/* Main Content Area */} + {/* Main Content Area with panels */}
+ {/* File Explorer Panel */} + { + // Add file path to prompt input (double click) + floatingPromptRef.current?.addImage(path); + }} + onFileOpen={(path) => { + // Open file in editor (single click) + setEditingFile(path); + }} + onToggle={() => setShowFileExplorer(!showFileExplorer)} + width={fileExplorerWidth} + /> + + {/* Main Content with Input */} +
{showPreview ? ( // Split pane layout when preview is active = ({ minRightWidth={400} className="h-full" /> + ) : editingFile ? ( + // File Editor layout +
+ setEditingFile(null)} + className="flex-1" + /> +
) : ( // Original layout when no preview -
- {projectPathInput} - {messagesList} - - {isLoading && messages.length === 0 && ( -
-
-
- - {session ? "Loading session history..." : "Initializing Claude Code..."} - -
-
- )} -
- )} -
- - {/* Floating Prompt Input - Always visible */} - - {/* Queued Prompts Display */} - - {queuedPrompts.length > 0 && ( - -
-
-
- Queued Prompts ({queuedPrompts.length}) +
+
+ {projectPathInput} + {messagesList} + + {isLoading && messages.length === 0 && ( +
+
+
+ + {session ? "Loading session history..." : "Initializing Claude Code..."} +
-
- {!queuedPromptsCollapsed && queuedPrompts.map((queuedPrompt, index) => ( - -
-
- #{index + 1} - - {queuedPrompt.model === "opus" ? "Opus" : "Sonnet"} - -
-

{queuedPrompt.prompt}

-
- -
- ))} -
- - )} - - - {/* Navigation Arrows - positioned above prompt bar with spacing */} - {displayableMessages.length > 5 && ( - -
- -
- + )}
- - )} + + {/* Floating Prompt Input - Bound to Main Content */} + + {/* Queued Prompts Display */} + + {queuedPrompts.length > 0 && ( + +
+
+
+
+ Queued Prompts ({queuedPrompts.length}) +
+ +
+ {!queuedPromptsCollapsed && queuedPrompts.map((queuedPrompt, index) => ( + +
+
+ #{index + 1} + + {queuedPrompt.model === "opus" ? "Opus" : "Sonnet"} + +
+

{queuedPrompt.prompt}

+
+ +
+ ))} +
+
+
+ )} +
-
- -
- - {/* Token Counter - positioned under the Send button */} - {totalTokens > 0 && ( -
-
-
+ {/* Navigation Arrows */} + {displayableMessages.length > 5 && ( -
- - {totalTokens.toLocaleString()} - tokens +
+ +
+
+ )} + +
+
-
+ + {/* Token Counter */} + {totalTokens > 0 && ( +
+
+
+ +
+ + {totalTokens.toLocaleString()} + tokens +
+
+
+
+
+ )} +
)} - +
+ + {/* Git Panel */} + setShowGitPanel(!showGitPanel)} + width={gitPanelWidth} + /> +
{/* Timeline */} @@ -1665,7 +1757,7 @@ export const ClaudeCodeSession: React.FC = ({ placeholder="e.g., Alternative approach" value={forkSessionName} onChange={(e) => setForkSessionName(e.target.value)} - onKeyPress={(e) => { + onKeyDown={(e) => { if (e.key === "Enter" && !isLoading) { handleConfirmFork(); } @@ -1731,3 +1823,6 @@ export const ClaudeCodeSession: React.FC = ({
); }; + +// Add default export for lazy loading +export default ClaudeCodeSession; diff --git a/src/components/TabContent.tsx b/src/components/TabContent.tsx index b9f9983..5b488bd 100644 --- a/src/components/TabContent.tsx +++ b/src/components/TabContent.tsx @@ -12,7 +12,7 @@ import { Button } from '@/components/ui/button'; import { useTranslation } from '@/hooks/useTranslation'; // Lazy load heavy components -const ClaudeCodeSession = lazy(() => import('@/components/ClaudeCodeSession').then(m => ({ default: m.ClaudeCodeSession }))); +const ClaudeCodeSession = lazy(() => import('./ClaudeCodeSession')); const AgentRunOutputViewer = lazy(() => import('@/components/AgentRunOutputViewer')); const AgentExecution = lazy(() => import('@/components/AgentExecution').then(m => ({ default: m.AgentExecution }))); const CreateAgent = lazy(() => import('@/components/CreateAgent').then(m => ({ default: m.CreateAgent }))); diff --git a/src/locales/en/common.json b/src/locales/en/common.json index 72c5afa..35be688 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -61,7 +61,38 @@ "noCheckpointsYet": "No checkpoints yet", "sessionTimeline": "Session Timeline", "checkpoints": "checkpoints", - "loadingTimeline": "Loading timeline..." + "loadingTimeline": "Loading timeline...", + "fileExplorer": "File Explorer", + "searchFiles": "Search files...", + "copyPath": "Copy Path", + "openFile": "Open File", + "addToChat": "Add to Chat", + "noFilesFound": "No files found", + "viewMode": "View Mode", + "editMode": "Edit Mode", + "saved": "Saved", + "unsavedChangesConfirm": "You have unsaved changes. Are you sure you want to leave?", + "gitPanel": "Git", + "gitStatus": "Status", + "gitHistory": "History", + "gitBranches": "Branches", + "workingTreeClean": "Working tree clean", + "staged": "Staged", + "modified": "Modified", + "untracked": "Untracked", + "conflicted": "Conflicted", + "noGitRepository": "No Git repository found", + "noCommitsFound": "No commits found", + "noBranchesFound": "No branches found", + "localBranches": "Local Branches", + "remoteBranches": "Remote Branches", + "current": "current", + "andMore": "... and {{count}} more", + "filesChanged": "files", + "justNow": "just now", + "minutesAgo": "{{count}} minute{{plural}} ago", + "hoursAgo": "{{count}} hour{{plural}} ago", + "daysAgo": "{{count}} day{{plural}} ago" }, "navigation": { "projects": "CC Projects", diff --git a/src/locales/zh/common.json b/src/locales/zh/common.json index 3a0fe67..a9308a5 100644 --- a/src/locales/zh/common.json +++ b/src/locales/zh/common.json @@ -58,7 +58,38 @@ "noCheckpointsYet": "尚无检查点", "sessionTimeline": "会话时间线", "checkpoints": "个检查点", - "loadingTimeline": "加载时间线中..." + "loadingTimeline": "加载时间线中...", + "fileExplorer": "文件管理器", + "searchFiles": "搜索文件...", + "copyPath": "复制路径", + "openFile": "打开文件", + "addToChat": "添加到聊天", + "noFilesFound": "未找到文件", + "viewMode": "查看模式", + "editMode": "编辑模式", + "saved": "已保存", + "unsavedChangesConfirm": "您有未保存的更改。确定要离开吗?", + "gitPanel": "Git", + "gitStatus": "状态", + "gitHistory": "历史", + "gitBranches": "分支", + "workingTreeClean": "工作区清洁", + "staged": "已暂存", + "modified": "已修改", + "untracked": "未跟踪", + "conflicted": "冲突", + "noGitRepository": "未找到Git仓库", + "noCommitsFound": "未找到提交", + "noBranchesFound": "未找到分支", + "localBranches": "本地分支", + "remoteBranches": "远程分支", + "current": "当前", + "andMore": "... 还有 {{count}} 个", + "filesChanged": "个文件", + "justNow": "刚刚", + "minutesAgo": "{{count}} 分钟前", + "hoursAgo": "{{count}} 小时前", + "daysAgo": "{{count}} 天前" }, "navigation": { "projects": "Claude Code 项目",