diff --git a/package.json b/package.json index 71965f4..9317a3f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "codeg", "private": true, - "version": "0.2.1", + "version": "0.2.3", "scripts": { "dev": "next dev --turbopack", "build": "next build", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index fcb1c4b..6399a58 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -409,7 +409,7 @@ dependencies = [ "rustc-hash", "shlex", "syn 2.0.114", - "which", + "which 4.4.2", ] [[package]] @@ -792,7 +792,7 @@ checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "codeg" -version = "0.2.1" +version = "0.2.3" dependencies = [ "agent-client-protocol-schema", "base64 0.22.1", @@ -827,6 +827,7 @@ dependencies = [ "urlencoding", "uuid", "walkdir", + "which 7.0.3", "windows-sys 0.59.0", "zip 2.4.2", ] @@ -1422,6 +1423,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "equivalent" version = "1.0.2" @@ -7185,6 +7192,18 @@ dependencies = [ "rustix 0.38.44", ] +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix 1.1.3", + "winsafe", +] + [[package]] name = "whoami" version = "1.6.1" @@ -7762,6 +7781,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5eda92d..29898b0 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codeg" -version = "0.2.1" +version = "0.2.3" description = "Agent Code Generation App" authors = ["feitao"] edition = "2021" @@ -48,6 +48,7 @@ notify = "6" base64 = "0.22" agent-client-protocol-schema = { version = "0.10", features = ["unstable_session_usage", "unstable_session_fork"] } kill_tree = { version = "0.2", features = ["tokio"] } +which = "7" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-window-state = "2" diff --git a/src-tauri/src/acp/connection.rs b/src-tauri/src/acp/connection.rs index c6a876d..a456fea 100644 --- a/src-tauri/src/acp/connection.rs +++ b/src-tauri/src/acp/connection.rs @@ -131,9 +131,13 @@ async fn build_agent( parts.push(format!("{k}={v}")); } parts.push( - crate::process::normalized_program(cmd) - .to_string_lossy() - .to_string(), + which::which(cmd) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|_| { + crate::process::normalized_program(cmd) + .to_string_lossy() + .to_string() + }), ); for a in args { parts.push((*a).into()); diff --git a/src-tauri/src/acp/preflight.rs b/src-tauri/src/acp/preflight.rs index 45ee429..4890978 100644 --- a/src-tauri/src/acp/preflight.rs +++ b/src-tauri/src/acp/preflight.rs @@ -93,14 +93,24 @@ async fn check_npm_environment(node_required: Option<&str>) -> Vec { return checks; } - // Run node and npm checks in parallel + // Resolve absolute paths via `which` crate to avoid GUI PATH issues, + // then run version checks in parallel. + let node_path = which::which("node").ok(); + let npm_path = which::which("npm").ok(); + let (node_result, npm_result) = tokio::join!( - crate::process::tokio_command("node") - .arg("--version") - .output(), - crate::process::tokio_command("npm") - .arg("--version") - .output(), + async { + match &node_path { + Some(p) => crate::process::tokio_command(p).arg("--version").output().await, + None => Err(std::io::Error::new(std::io::ErrorKind::NotFound, "node not found in PATH")), + } + }, + async { + match &npm_path { + Some(p) => crate::process::tokio_command(p).arg("--version").output().await, + None => Err(std::io::Error::new(std::io::ErrorKind::NotFound, "npm not found in PATH")), + } + }, ); // Track the raw node version string for reuse in the version check diff --git a/src-tauri/src/acp/registry.rs b/src-tauri/src/acp/registry.rs index e5a87d7..2491d7c 100644 --- a/src-tauri/src/acp/registry.rs +++ b/src-tauri/src/acp/registry.rs @@ -113,8 +113,8 @@ pub fn get_agent_meta(agent_type: AgentType) -> AcpAgentMeta { name: "Claude Code", description: "ACP wrapper for Anthropic's Claude", distribution: AgentDistribution::Npx { - version: "0.22.1", - package: "@zed-industries/claude-agent-acp@0.22.1", + version: "0.22.2", + package: "@zed-industries/claude-agent-acp@0.22.2", cmd: "claude-agent-acp", args: &[], env: &[], diff --git a/src-tauri/src/commands/acp.rs b/src-tauri/src/commands/acp.rs index e7afba2..145b9d9 100644 --- a/src-tauri/src/commands/acp.rs +++ b/src-tauri/src/commands/acp.rs @@ -81,31 +81,49 @@ fn package_name_from_spec(package: &str) -> String { /// Check whether a command is available on the system PATH. /// Uses `which` on unix and `where` on windows — lightweight and does not /// invoke the target binary itself, avoiding side-effects or slow startups. -async fn is_cmd_available(cmd: &str) -> bool { - #[cfg(unix)] - let check_cmd = "which"; - #[cfg(windows)] - let check_cmd = "where"; +fn is_cmd_available(cmd: &str) -> bool { + which::which(cmd).is_ok() +} - crate::process::tokio_command(check_cmd) - .arg(cmd) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status() +/// Detect the actual installed version of an npm global package by running +/// `npm list -g --json` and parsing the JSON output. +async fn detect_npm_global_version(package_name: &str) -> Option { + let npm_path = which::which("npm").ok()?; + let output = crate::process::tokio_command(npm_path) + .arg("list") + .arg("-g") + .arg(package_name) + .arg("--json") + .arg("--depth=0") + .output() .await - .map(|s| s.success()) - .unwrap_or(false) + .ok()?; + // npm list --json may exit non-zero when package is missing, but still + // outputs valid JSON with an empty dependencies object. + let stdout = String::from_utf8_lossy(&output.stdout); + let json: serde_json::Value = serde_json::from_str(&stdout).ok()?; + let version = json + .get("dependencies")? + .get(package_name)? + .get("version")? + .as_str()?; + normalize_version_candidate(version) } async fn detect_local_version(agent_type: AgentType) -> Option { let meta = registry::get_agent_meta(agent_type); match meta.distribution { registry::AgentDistribution::Npx { cmd, package, .. } => { - if is_cmd_available(cmd).await { - version_from_package_spec(package) - } else { - None + if !is_cmd_available(cmd) { + return None; } + // Try `npm list -g --json` to get the real installed version. + let pkg_name = package_name_from_spec(package); + if let Some(v) = detect_npm_global_version(&pkg_name).await { + return Some(v); + } + // Fallback: parse version from registry package spec + version_from_package_spec(package) } registry::AgentDistribution::Binary { cmd, .. } => { binary_cache::detect_installed_version(agent_type, cmd) @@ -1047,7 +1065,7 @@ pub async fn acp_connect( } if let registry::AgentDistribution::Npx { cmd, .. } = meta.distribution { - if !is_cmd_available(cmd).await { + if !is_cmd_available(cmd) { return Err(AcpError::protocol(format!( "{} SDK is not installed. Please install it in Agent Settings.", meta.name diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 73861a7..08475f2 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "codeg", - "version": "0.2.1", + "version": "0.2.3", "identifier": "app.codeg", "build": { "beforeDevCommand": "pnpm dev", diff --git a/src/components/conversations/search-command-dialog.tsx b/src/components/conversations/search-command-dialog.tsx index 4e87f7b..e3a01e5 100644 --- a/src/components/conversations/search-command-dialog.tsx +++ b/src/components/conversations/search-command-dialog.tsx @@ -1,16 +1,25 @@ "use client" -import { useState, useEffect, useRef, useCallback } from "react" +import { useState, useEffect, useRef, useCallback, useMemo } from "react" import { formatDistanceToNow } from "date-fns" import { enUS, zhCN, zhTW } from "date-fns/locale" +import { File, Folder } from "lucide-react" +import ig from "ignore" import { useLocale, useTranslations } from "next-intl" +import { useAuxPanelContext } from "@/contexts/aux-panel-context" import { useFolderContext } from "@/contexts/folder-context" import { useTabContext } from "@/contexts/tab-context" -import { listFolderConversations } from "@/lib/tauri" +import { useWorkspaceContext } from "@/contexts/workspace-context" +import { + getFileTree, + listFolderConversations, + readFilePreview, +} from "@/lib/tauri" import type { AgentType, ConversationStatus, DbConversationSummary, + FileTreeNode, } from "@/lib/types" import { AGENT_LABELS, STATUS_COLORS, compareAgentType } from "@/lib/types" import { AgentIcon } from "@/components/agent-icon" @@ -24,6 +33,51 @@ import { } from "@/components/ui/command" import { cn } from "@/lib/utils" +type SearchTab = "conversations" | "files" + +interface FlatFileEntry { + name: string + /** Relative path from folder root (same as FileTreeNode.path) */ + relativePath: string + kind: "file" | "dir" + /** Pre-computed lowercase relativePath for filtering */ + lowerPath: string + /** Pre-computed lowercase name for filtering */ + lowerName: string +} + +function flattenTree(nodes: FileTreeNode[]): FlatFileEntry[] { + const entries: FlatFileEntry[] = [] + function walk(node: FileTreeNode) { + entries.push({ + name: node.name, + relativePath: node.path, + kind: node.kind, + lowerPath: node.path.toLowerCase(), + lowerName: node.name.toLowerCase(), + }) + if (node.kind === "dir" && node.children) { + for (const child of node.children) { + walk(child) + } + } + } + for (const node of nodes) { + walk(node) + } + return entries +} + +/** Check whether any ancestor directory of `path` is in `ignoredDirs`. */ +function hasIgnoredAncestor(path: string, ignoredDirs: Set): boolean { + let idx = path.indexOf("/") + while (idx !== -1) { + if (ignoredDirs.has(path.slice(0, idx))) return true + idx = path.indexOf("/", idx + 1) + } + return false +} + interface SearchCommandDialogProps { open: boolean onOpenChange: (open: boolean) => void @@ -37,20 +91,125 @@ export function SearchCommandDialog({ const locale = useLocale() const dateFnsLocale = locale === "zh-CN" ? zhCN : locale === "zh-TW" ? zhTW : enUS - const { folderId, conversations } = useFolderContext() + const { folderId, folder, conversations } = useFolderContext() const { openTab } = useTabContext() + const { openFilePreview } = useWorkspaceContext() + const { revealInFileTree } = useAuxPanelContext() + const [activeTab, setActiveTab] = useState("conversations") const [query, setQuery] = useState("") const [agentFilter, setAgentFilter] = useState(null) const [results, setResults] = useState([]) const [searching, setSearching] = useState(false) const debounceRef = useRef>(undefined) + // File search state + const [allFiles, setAllFiles] = useState([]) + const [filesLoading, setFilesLoading] = useState(false) + const filesLoadedRef = useRef(false) + + const folderPath = folder?.path ?? "" + // Compute which agent types exist in current folder const availableAgents = Array.from( new Set(conversations.map((c) => c.agent_type)) ).sort(compareAgentType) + // Load file tree when switching to files tab, filtering by .gitignore + useEffect(() => { + if (activeTab !== "files" || !folderPath || filesLoadedRef.current) return + let canceled = false + setFilesLoading(true) + + async function load() { + try { + const tree = await getFileTree(folderPath, 10) + const flat = flattenTree(tree) + + // Collect all .gitignore files from the tree + const gitignoreEntries = flat.filter( + (f) => f.kind === "file" && f.name === ".gitignore" + ) + + // Build matchers keyed by directory prefix + const matchers: { prefix: string; matcher: ReturnType }[] = + [] + await Promise.all( + gitignoreEntries.map(async (entry) => { + try { + const result = await readFilePreview( + folderPath, + entry.relativePath + ) + const lastSlash = entry.relativePath.lastIndexOf("/") + const dir = + lastSlash === -1 ? "" : entry.relativePath.slice(0, lastSlash) + matchers.push({ + prefix: dir ? dir + "/" : "", + matcher: ig().add(result.content), + }) + } catch { + // skip unreadable .gitignore + } + }) + ) + + // Sort matchers by prefix length (shortest/root first) so that + // parent rules are evaluated before child rules. + matchers.sort((a, b) => a.prefix.length - b.prefix.length) + + // Filter: check each entry against all applicable .gitignore matchers + const ignoredDirs = new Set() + const filtered = flat.filter((f) => { + // Skip .gitignore files themselves from results + if (f.name === ".gitignore") return false + // If an ancestor directory is already ignored, skip — O(depth) lookup + if (hasIgnoredAncestor(f.relativePath, ignoredDirs)) return false + for (const { prefix, matcher } of matchers) { + if (!f.relativePath.startsWith(prefix)) continue + const relPath = f.relativePath.slice(prefix.length) + if (!relPath) continue + const testPath = f.kind === "dir" ? `${relPath}/` : relPath + if (matcher.ignores(testPath)) { + if (f.kind === "dir") ignoredDirs.add(f.relativePath) + return false + } + } + return true + }) + + if (!canceled) { + setAllFiles(filtered) + filesLoadedRef.current = true + } + } catch { + if (!canceled) setAllFiles([]) + } finally { + if (!canceled) setFilesLoading(false) + } + } + + void load() + return () => { + canceled = true + } + }, [activeTab, folderPath]) + + // Filter files by query using pre-computed lowercase fields + const filteredFiles = useMemo(() => { + const trimmed = query.trim() + if (!trimmed) return allFiles.slice(0, 100) + const lower = trimmed.toLowerCase() + const matched: FlatFileEntry[] = [] + for (const f of allFiles) { + if (f.lowerName.includes(lower) || f.lowerPath.includes(lower)) { + matched.push(f) + if (matched.length >= 100) break + } + } + return matched + }, [allFiles, query]) + const doSearch = useCallback( async (q: string, agent: AgentType | null) => { if (!q.trim() && !agent) { @@ -75,8 +234,9 @@ export function SearchCommandDialog({ [folderId] ) - // Debounced search on query change + // Debounced search on query change (conversations tab only) useEffect(() => { + if (activeTab !== "conversations") return if (debounceRef.current) clearTimeout(debounceRef.current) debounceRef.current = setTimeout(() => { doSearch(query, agentFilter) @@ -84,7 +244,7 @@ export function SearchCommandDialog({ return () => { if (debounceRef.current) clearTimeout(debounceRef.current) } - }, [query, agentFilter, doSearch]) + }, [query, agentFilter, doSearch, activeTab]) // Reset state when dialog closes useEffect(() => { @@ -92,26 +252,87 @@ export function SearchCommandDialog({ setQuery("") setAgentFilter(null) setResults([]) + setActiveTab("conversations") + filesLoadedRef.current = false + setAllFiles([]) } }, [open]) - const handleSelect = (conv: DbConversationSummary) => { - openTab(conv.id, conv.agent_type, true) - onOpenChange(false) - } + const handleSelectConversation = useCallback( + (conv: DbConversationSummary) => { + openTab(conv.id, conv.agent_type, true) + onOpenChange(false) + }, + [openTab, onOpenChange] + ) + + const handleSelectFile = useCallback( + (entry: FlatFileEntry) => { + if (entry.kind === "dir") { + revealInFileTree(entry.relativePath) + } else { + // Reveal parent directory in file tree, then open the file + const lastSlash = entry.relativePath.lastIndexOf("/") + if (lastSlash > 0) { + revealInFileTree(entry.relativePath.slice(0, lastSlash)) + } + openFilePreview(entry.relativePath) + } + onOpenChange(false) + }, + [revealInFileTree, openFilePreview, onOpenChange] + ) + + const placeholder = + activeTab === "conversations" ? t("placeholder") : t("filePlaceholder") return ( + {/* Tabs */} +
+ + +
+ - {availableAgents.length > 1 && ( + + {/* Agent filter (conversations tab only) */} + {activeTab === "conversations" && availableAgents.length > 1 && (
)} + - - {searching - ? t("searching") - : !query.trim() && !agentFilter - ? t("typeToSearch") - : t("noResults")} - - {results.length > 0 && ( - - {results.map((conv) => ( - handleSelect(conv)} - > - - - {conv.title || t("untitledConversation")} - - - {AGENT_LABELS[conv.agent_type]} - - - {formatDistanceToNow(new Date(conv.created_at), { - addSuffix: true, - locale: dateFnsLocale, - })} - - - ))} - + {/* Conversations tab */} + {activeTab === "conversations" && ( + <> + + {searching + ? t("searching") + : !query.trim() && !agentFilter + ? t("typeToSearch") + : t("noResults")} + + {results.length > 0 && ( + + {results.map((conv) => ( + handleSelectConversation(conv)} + > + + + {conv.title || t("untitledConversation")} + + + {AGENT_LABELS[conv.agent_type]} + + + {formatDistanceToNow(new Date(conv.created_at), { + addSuffix: true, + locale: dateFnsLocale, + })} + + + ))} + + )} + + )} + + {/* Files tab */} + {activeTab === "files" && ( + <> + + {filesLoading + ? t("searching") + : !query.trim() + ? t("typeToSearchFiles") + : t("noResults")} + + {filteredFiles.length > 0 && ( + + {filteredFiles.map((entry) => ( + handleSelectFile(entry)} + > + {entry.kind === "dir" ? ( + + ) : ( + + )} + {entry.name} + + {entry.relativePath} + + + ))} + + )} + )}
diff --git a/src/components/layout/aux-panel-file-tree-tab.tsx b/src/components/layout/aux-panel-file-tree-tab.tsx index bfa4a7b..09de0f0 100644 --- a/src/components/layout/aux-panel-file-tree-tab.tsx +++ b/src/components/layout/aux-panel-file-tree-tab.tsx @@ -744,7 +744,8 @@ function RenderNode({ export function FileTreeTab() { const t = useTranslations("Folder.fileTreeTab") const tCommon = useTranslations("Folder.common") - const { activeTab } = useAuxPanelContext() + const { activeTab, pendingRevealPath, consumePendingRevealPath } = + useAuxPanelContext() const { folder } = useFolderContext() const { tabs, activeTabId } = useTabContext() const { createTerminalInDirectory } = useTerminalContext() @@ -857,6 +858,24 @@ export function FileTreeTab() { externalConflictSignatureByPathRef.current.clear() }, [folder?.path]) + // Handle pending reveal path: expand all ancestor directories once tree is loaded + const hasNodes = nodes.length > 0 + useEffect(() => { + if (!pendingRevealPath || !hasNodes) return + consumePendingRevealPath() + setExpandedPaths((prev) => { + const next = new Set(prev) + next.add(FILE_TREE_ROOT_PATH) + let idx = pendingRevealPath.indexOf("/") + while (idx !== -1) { + next.add(pendingRevealPath.slice(0, idx)) + idx = pendingRevealPath.indexOf("/", idx + 1) + } + next.add(pendingRevealPath) + return next + }) + }, [pendingRevealPath, consumePendingRevealPath, hasNodes]) + useEffect(() => { if (!activeFileTab || activeFileTab.kind !== "file") return if (!activeFileTab.path) return diff --git a/src/components/layout/aux-panel.tsx b/src/components/layout/aux-panel.tsx index 21db2d8..0f3022e 100644 --- a/src/components/layout/aux-panel.tsx +++ b/src/components/layout/aux-panel.tsx @@ -13,32 +13,23 @@ import { GitChangesTab } from "./aux-panel-git-changes-tab" import { GitLogTab } from "./aux-panel-git-log-tab" import { SessionFilesTab } from "./aux-panel-session-files-tab" +const LAZY_TABS: AuxPanelTab[] = ["file_tree", "changes", "git_log"] + export function AuxPanel() { const t = useTranslations("Folder.auxPanel.tabs") const { isOpen, activeTab, setActiveTab } = useAuxPanelContext() - const [hasMountedFileTree, setHasMountedFileTree] = useState( - activeTab === "file_tree" - ) - const [hasMountedChanges, setHasMountedChanges] = useState( - activeTab === "changes" - ) - const [hasMountedGitLog, setHasMountedGitLog] = useState( - activeTab === "git_log" + const [mountedTabs, setMountedTabs] = useState>( + () => new Set(LAZY_TABS.filter((tab) => tab === activeTab)) ) + // Ensure the active tab is mounted (covers both user clicks and programmatic changes) + if (isOpen && LAZY_TABS.includes(activeTab) && !mountedTabs.has(activeTab)) { + setMountedTabs((prev) => new Set(prev).add(activeTab)) + } + const handleTabValueChange = useCallback( (value: string) => { - const nextTab = value as AuxPanelTab - setActiveTab(nextTab) - if (nextTab === "file_tree") { - setHasMountedFileTree(true) - } - if (nextTab === "changes") { - setHasMountedChanges(true) - } - if (nextTab === "git_log") { - setHasMountedGitLog(true) - } + setActiveTab(value as AuxPanelTab) }, [setActiveTab] ) @@ -97,21 +88,21 @@ export function AuxPanel() { forceMount className="mt-0 flex-1 min-h-0 overflow-hidden" > - {hasMountedFileTree ? : null} + {mountedTabs.has("file_tree") ? : null} - {hasMountedChanges ? : null} + {mountedTabs.has("changes") ? : null} - {hasMountedGitLog ? : null} + {mountedTabs.has("git_log") ? : null} diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index 8239f36..b52b7d4 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -26,15 +26,22 @@ function Command({ function CommandDialog({ title = "Command", children, + shouldFilter, ...props -}: React.ComponentProps & { title?: string }) { +}: React.ComponentProps & { + title?: string + shouldFilter?: boolean +}) { return ( {title} - + {children} diff --git a/src/contexts/aux-panel-context.tsx b/src/contexts/aux-panel-context.tsx index cf2926d..74de057 100644 --- a/src/contexts/aux-panel-context.tsx +++ b/src/contexts/aux-panel-context.tsx @@ -31,6 +31,9 @@ interface AuxPanelContextValue { setWidth: (w: number) => void setActiveTab: (tab: AuxPanelTab) => void openTab: (tab: AuxPanelTab) => void + pendingRevealPath: string | null + revealInFileTree: (path: string) => void + consumePendingRevealPath: () => void } const AuxPanelContext = createContext(null) @@ -64,6 +67,9 @@ export function AuxPanelProvider({ const [width, setWidthState] = useState(DEFAULT_WIDTH) const [restored, setRestored] = useState(false) const [activeTab, setActiveTab] = useState("session_files") + const [pendingRevealPath, setPendingRevealPath] = useState( + null + ) const toggle = useCallback(() => setIsOpen((prev) => !prev), []) @@ -76,6 +82,16 @@ export function AuxPanelProvider({ setIsOpen(true) }, []) + const revealInFileTree = useCallback((path: string) => { + setPendingRevealPath(path) + setActiveTab("file_tree") + setIsOpen(true) + }, []) + + const consumePendingRevealPath = useCallback(() => { + setPendingRevealPath(null) + }, []) + useEffect(() => { const stored = loadPersistedPanelState(storageKey) // Hydrate from localStorage after mount to keep SSR/CSR markup consistent. @@ -101,8 +117,21 @@ export function AuxPanelProvider({ setWidth, setActiveTab, openTab, + pendingRevealPath, + revealInFileTree, + consumePendingRevealPath, }), - [isOpen, width, activeTab, toggle, setWidth, openTab] + [ + isOpen, + width, + activeTab, + toggle, + setWidth, + openTab, + pendingRevealPath, + revealInFileTree, + consumePendingRevealPath, + ] ) return ( diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 7cbd224..cf5e02b 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -648,11 +648,15 @@ "save": "حفظ" }, "search": { - "dialogTitle": "البحث في المحادثات", + "dialogTitle": "بحث", + "tabConversations": "المحادثات", + "tabFiles": "الملفات", "placeholder": "البحث في المحادثات...", + "filePlaceholder": "البحث في الملفات أو المجلدات...", "allAgents": "الكل", "searching": "جارٍ البحث...", "typeToSearch": "اكتب للبحث في المحادثات", + "typeToSearchFiles": "اكتب للبحث في الملفات أو المجلدات", "noResults": "لم يتم العثور على نتائج.", "untitledConversation": "محادثة بدون عنوان" }, diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 32d3553..b0ce5c2 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -648,11 +648,15 @@ "save": "Speichern" }, "search": { - "dialogTitle": "Konversationen suchen", + "dialogTitle": "Suchen", + "tabConversations": "Konversationen", + "tabFiles": "Dateien", "placeholder": "Konversationen suchen...", + "filePlaceholder": "Dateien oder Verzeichnisse suchen...", "allAgents": "Alle", "searching": "Suche...", "typeToSearch": "Tippen, um Konversationen zu suchen", + "typeToSearchFiles": "Tippen, um Dateien oder Verzeichnisse zu suchen", "noResults": "Keine Ergebnisse gefunden.", "untitledConversation": "Unbenannte Konversation" }, diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 062cba2..f862749 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -648,11 +648,15 @@ "save": "Save" }, "search": { - "dialogTitle": "Search conversations", + "dialogTitle": "Search", + "tabConversations": "Conversations", + "tabFiles": "Files", "placeholder": "Search conversations...", + "filePlaceholder": "Search files or directories...", "allAgents": "All", "searching": "Searching...", "typeToSearch": "Type to search conversations", + "typeToSearchFiles": "Type to search files or directories", "noResults": "No results found.", "untitledConversation": "Untitled conversation" }, diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 75af880..46ae9cc 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -648,11 +648,15 @@ "save": "Guardar" }, "search": { - "dialogTitle": "Buscar conversaciones", + "dialogTitle": "Buscar", + "tabConversations": "Conversaciones", + "tabFiles": "Archivos", "placeholder": "Buscar conversaciones...", + "filePlaceholder": "Buscar archivos o directorios...", "allAgents": "Todo", "searching": "Buscando...", "typeToSearch": "Escribe para buscar conversaciones", + "typeToSearchFiles": "Escribe para buscar archivos o directorios", "noResults": "No se encontraron resultados.", "untitledConversation": "Conversación sin título" }, diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 433ca77..29588c1 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -648,11 +648,15 @@ "save": "Enregistrer" }, "search": { - "dialogTitle": "Rechercher des conversations", + "dialogTitle": "Rechercher", + "tabConversations": "Conversations", + "tabFiles": "Fichiers", "placeholder": "Rechercher des conversations...", + "filePlaceholder": "Rechercher des fichiers ou répertoires...", "allAgents": "Tout", "searching": "Recherche...", "typeToSearch": "Tapez pour rechercher des conversations", + "typeToSearchFiles": "Tapez pour rechercher des fichiers ou répertoires", "noResults": "Aucun résultat trouvé.", "untitledConversation": "Conversation sans titre" }, diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index fb3d574..9678d07 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -648,11 +648,15 @@ "save": "保存" }, "search": { - "dialogTitle": "会話を検索", + "dialogTitle": "検索", + "tabConversations": "会話", + "tabFiles": "ファイル", "placeholder": "会話を検索...", + "filePlaceholder": "ファイルまたはディレクトリを検索...", "allAgents": "すべて", "searching": "検索中...", "typeToSearch": "入力して会話を検索", + "typeToSearchFiles": "入力してファイルまたはディレクトリを検索", "noResults": "結果が見つかりません。", "untitledConversation": "無題の会話" }, diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 562cc63..e56259a 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -648,11 +648,15 @@ "save": "저장" }, "search": { - "dialogTitle": "대화 검색", + "dialogTitle": "검색", + "tabConversations": "대화", + "tabFiles": "파일", "placeholder": "대화 검색...", + "filePlaceholder": "파일 또는 디렉토리 검색...", "allAgents": "전체", "searching": "검색 중...", "typeToSearch": "입력하여 대화를 검색하세요", + "typeToSearchFiles": "입력하여 파일 또는 디렉토리를 검색하세요", "noResults": "검색 결과가 없습니다.", "untitledConversation": "제목 없는 대화" }, diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 4de8e41..ae6a578 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -648,11 +648,15 @@ "save": "Salvar" }, "search": { - "dialogTitle": "Buscar conversas", + "dialogTitle": "Buscar", + "tabConversations": "Conversas", + "tabFiles": "Arquivos", "placeholder": "Buscar conversas...", + "filePlaceholder": "Buscar arquivos ou diretórios...", "allAgents": "Todos", "searching": "Buscando...", "typeToSearch": "Digite para buscar conversas", + "typeToSearchFiles": "Digite para buscar arquivos ou diretórios", "noResults": "Nenhum resultado encontrado.", "untitledConversation": "Conversa sem título" }, diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index a52e923..2d5c288 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -648,11 +648,15 @@ "save": "保存" }, "search": { - "dialogTitle": "搜索会话", + "dialogTitle": "搜索", + "tabConversations": "会话", + "tabFiles": "文件", "placeholder": "搜索会话...", + "filePlaceholder": "搜索文件或目录...", "allAgents": "全部", "searching": "搜索中...", "typeToSearch": "输入关键词搜索会话", + "typeToSearchFiles": "输入关键词搜索文件或目录", "noResults": "未找到结果。", "untitledConversation": "未命名会话" }, diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 226c5bd..3025fd0 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -648,11 +648,15 @@ "save": "儲存" }, "search": { - "dialogTitle": "搜尋會話", + "dialogTitle": "搜尋", + "tabConversations": "會話", + "tabFiles": "檔案", "placeholder": "搜尋會話...", + "filePlaceholder": "搜尋檔案或目錄...", "allAgents": "全部", "searching": "搜尋中...", "typeToSearch": "輸入關鍵字搜尋會話", + "typeToSearchFiles": "輸入關鍵字搜尋檔案或目錄", "noResults": "找不到結果。", "untitledConversation": "未命名會話" },