From b8943b4408d091547f29a55d7e52419ce34176db Mon Sep 17 00:00:00 2001 From: M3gA-Mind Date: Fri, 28 Aug 2026 04:03:39 +0530 Subject: [PATCH] fix(memory-sources): use the native directory chooser and never store a bare folder name The folder memory-source picker is an ``. That element carries no filesystem path: Chromium exposes one on the non-standard `File.path` only when the renderer has filesystem-aware integration. Outside that renderer the handler fell back to onChange(first.webkitRelativePath.split('/')[0]); which is the chosen directory's NAME with its location discarded. A user who picked a folder got a source storing `docs`, and every sync then failed forever with `folder does not exist: docs`. Re-adding the same folder with a hand-typed absolute path worked immediately on the same build. Browse now calls the OS-native directory chooser through a new `pick_directory_via_dialog` Tauri command, which returns an absolute path in every renderer and on every platform. It is backed by `rfd`, already in this shell for `save_artifact_via_dialog` (#3162), so there is no new dependency, no dialog plugin, and no capability-allowlist entry. The `webkitdirectory` input is kept as the fallback for a browser context and for a host with no working dialog, but it can no longer produce a bad value: when `File.path` is absent it reports `no-absolute-path` and the field shows an error instead of saving a name that cannot resolve. Hand-typed absolute paths are untouched, which matters because that is the current workaround. Not added: existence validation at save time. The native chooser can only return a directory that exists, so the path this fixes is already covered by construction, and a hand-typed path that does not exist yet is a legitimate thing to configure ahead of creating it. Checking it from the renderer would need a new filesystem-read command and a new trust boundary for what the issue itself asks be only a hint. --- app/src-tauri/src/directory_picker.rs | 76 ++++++++++ app/src-tauri/src/lib.rs | 2 + .../intelligence/AddMemorySourceFields.tsx | 88 ++++++++---- .../__tests__/folderPicker.test.ts | 136 ++++++++++++++++++ .../components/intelligence/folderPicker.ts | 110 ++++++++++++++ app/src/lib/i18n/ar.ts | 2 + app/src/lib/i18n/bn.ts | 2 + app/src/lib/i18n/de.ts | 2 + app/src/lib/i18n/en.ts | 2 + app/src/lib/i18n/es.ts | 2 + app/src/lib/i18n/fr.ts | 2 + app/src/lib/i18n/hi.ts | 2 + app/src/lib/i18n/id.ts | 2 + app/src/lib/i18n/it.ts | 2 + app/src/lib/i18n/ko.ts | 2 + app/src/lib/i18n/pl.ts | 2 + app/src/lib/i18n/pt.ts | 2 + app/src/lib/i18n/ru.ts | 2 + app/src/lib/i18n/zh-CN.ts | 1 + 19 files changed, 411 insertions(+), 28 deletions(-) create mode 100644 app/src-tauri/src/directory_picker.rs create mode 100644 app/src/components/intelligence/__tests__/folderPicker.test.ts create mode 100644 app/src/components/intelligence/folderPicker.ts diff --git a/app/src-tauri/src/directory_picker.rs b/app/src-tauri/src/directory_picker.rs new file mode 100644 index 0000000000..55e4406d3b --- /dev/null +++ b/app/src-tauri/src/directory_picker.rs @@ -0,0 +1,76 @@ +//! Native directory chooser for memory-source configuration (#5831). +//! +//! The folder memory-source used to be picked with an +//! `` in the renderer. That element does +//! not expose a filesystem path: Chromium only carries one on the +//! non-standard `File.path` attribute, and only when the renderer has +//! filesystem-aware integration. Outside that renderer the handler fell back +//! to `webkitRelativePath.split('/')[0]` — the chosen directory's **name** +//! with its location discarded — and stored it. The source then looked +//! configured and could never sync, failing once per cycle forever with +//! `folder does not exist: docs`. +//! +//! A native dialog has no such gap: it returns an absolute path in every +//! renderer, on every platform. +//! +//! ## Why `rfd` and not `tauri-plugin-dialog` +//! +//! This mirrors [`crate::artifact_commands::save_artifact_via_dialog`] +//! (#3162), which is already in this shell and already talks to the OS +//! dialog APIs through `rfd`. Reusing it means **no new dependency, no new +//! plugin, and no capability-allowlist entry** — the crate is declared in +//! `Cargo.toml` with `default-features = false` plus `xdg-portal`, which is +//! what keeps Linux off GTK. Adding the dialog plugin for one command would +//! widen the dependency graph for a capability the shell already has. +//! +//! ## Trust boundary +//! +//! Deliberately none. Unlike the artifact commands — which re-validate that +//! a renderer-supplied path sits inside the artifacts tree, because there +//! the renderer *supplies* the path — this command takes no input and +//! returns only what the user chose in an OS-owned dialog. The renderer +//! cannot steer it at a directory, and picking a folder to index is the +//! user's decision to make anywhere on their disk. + +/// Open the OS-native directory chooser and return the absolute path of the +/// directory the user selected. +/// +/// Returns: +/// - `Ok(Some(path))` — the absolute path chosen. +/// - `Ok(None)` — the user dismissed the dialog. Not an error; the caller +/// leaves the field untouched. +/// - `Err(_)` — the dialog could not run (for example, no xdg-desktop +/// portal on a headless Linux host), or it somehow yielded a relative +/// path. The caller surfaces this rather than storing anything. +#[tauri::command] +pub async fn pick_directory_via_dialog() -> Result, String> { + // On Linux this is the xdg-desktop portal (no GTK link); on macOS and + // Windows the system panel. The await resolves when the user picks or + // cancels. + let handle = rfd::AsyncFileDialog::new().pick_folder().await; + + let Some(dir) = handle else { + log::info!("[directory_picker] pick_directory_via_dialog cancelled by user"); + return Ok(None); + }; + + let path = dir.path().to_path_buf(); + + // The OS dialogs all hand back absolute paths, so this is a belt-and-braces + // check rather than a branch we expect to take. It exists because a + // relative value reaching the store is the entire defect this command was + // written to remove: an error here is recoverable and visible, whereas a + // stored relative path is neither. + if !path.is_absolute() { + return Err(format!( + "the directory chooser returned a non-absolute path: {}", + path.display() + )); + } + + log::info!( + "[directory_picker] pick_directory_via_dialog chose {}", + path.display() + ); + Ok(Some(path.display().to_string())) +} diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index fc802f91fe..2112642e46 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -43,6 +43,7 @@ mod core_rpc; mod deep_link_ipc; #[cfg(target_os = "windows")] mod deep_link_ipc_windows; +mod directory_picker; // Cross-platform module: the registry-reading function is windows-only, but // the parsing helpers compile (and test) everywhere so `cargo test` on the // developer host covers them. @@ -3371,6 +3372,7 @@ pub fn run() { // and the Save-As fallback needs it there (CodeRabbit on #4127). artifact_commands::save_artifact_via_dialog, artifact_commands::download_artifact_to_downloads, + directory_picker::pick_directory_via_dialog, // Structured WhatsApp data (store lives shell-side). whatsapp_data::whatsapp_data_list_chats, whatsapp_data::whatsapp_data_list_messages, diff --git a/app/src/components/intelligence/AddMemorySourceFields.tsx b/app/src/components/intelligence/AddMemorySourceFields.tsx index eea9a5e48c..95f5596ca0 100644 --- a/app/src/components/intelligence/AddMemorySourceFields.tsx +++ b/app/src/components/intelligence/AddMemorySourceFields.tsx @@ -19,6 +19,7 @@ import type { ComposioConnection } from '../../lib/composio/types'; import { useT } from '../../lib/i18n/I18nContext'; import type { SourceKind } from '../../services/memorySourcesService'; import TextField from '../ui/TextField'; +import { directoryPathFromPickedFiles, pickDirectoryNatively } from './folderPicker'; const log = debug('intelligence:add-memory-source-dialog'); @@ -60,6 +61,39 @@ interface FolderFieldProps { function FolderField({ label, value, onChange }: FolderFieldProps) { const { t } = useT(); + const fallbackInputRef = useRef(null); + const [pickError, setPickError] = useState(null); + + // Never store a value that cannot work as a path. `no-absolute-path` is the + // #5831 case: a directory was chosen but the renderer would not say where it + // is, so the only honest outcomes are an error or nothing — not the bare + // directory name the old handler saved, which produced a source that looked + // configured and could never sync. + const applyResult = (result: { ok: true; path: string } | { ok: false; reason: string }) => { + if (result.ok) { + setPickError(null); + onChange(result.path); + return; + } + if (result.reason === 'no-absolute-path') { + log('folder picker produced no absolute path; refusing to store a bare name'); + setPickError(t('memorySources.folderPathUnavailable')); + } + // `cancelled` leaves the field exactly as it was, silently. + }; + + const handleBrowse = async () => { + const native = await pickDirectoryNatively(); + if (native.ok || native.reason !== 'unavailable') { + applyResult(native); + return; + } + // No native chooser here (a browser context, or no portal). Fall back to + // the directory input, which may still carry a real path. + setPickError(null); + fallbackInputRef.current?.click(); + }; + return ( ); } diff --git a/app/src/components/intelligence/__tests__/folderPicker.test.ts b/app/src/components/intelligence/__tests__/folderPicker.test.ts new file mode 100644 index 0000000000..6d328b5b90 --- /dev/null +++ b/app/src/components/intelligence/__tests__/folderPicker.test.ts @@ -0,0 +1,136 @@ +/** + * #5831 — the folder picker must never hand back a value that cannot work + * as a path. + * + * The defect these tests pin: when `File.path` was unavailable the handler + * fell back to `webkitRelativePath.split('/')[0]`, storing the chosen + * directory's NAME with its location discarded. A source built from that + * value can never sync, and it fails once per cycle forever rather than once, + * visibly, at the moment of choosing. + */ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { directoryPathFromPickedFiles, pickDirectoryNatively } from '../folderPicker'; + +const hoisted = vi.hoisted(() => ({ invoke: vi.fn(), isTauri: vi.fn() })); + +vi.mock('../../../utils/tauriCommands/common', () => ({ + safeInvoke: hoisted.invoke, + isTauri: hoisted.isTauri, +})); + +/** A `webkitdirectory` file entry, with `path` present only when asked for. */ +function pickedFile(relativePath: string, absolutePath?: string): File { + const file = new File(['x'], relativePath.split('/').pop() ?? 'f.md'); + Object.defineProperty(file, 'webkitRelativePath', { value: relativePath }); + if (absolutePath !== undefined) { + Object.defineProperty(file, 'path', { value: absolutePath }); + } + return file; +} + +function fileList(...files: File[]): FileList { + const indexed: Record = {}; + files.forEach((file, i) => { + indexed[i] = file; + }); + return { + ...indexed, + length: files.length, + item: (i: number) => files[i] ?? null, + } as unknown as FileList; +} + +describe('directoryPathFromPickedFiles', () => { + it('refuses to produce a path when the renderer does not expose File.path', () => { + // THE REGRESSION. The old handler returned 'docs' here. A source stored + // with that value produced `folder does not exist: docs` on every sync, + // indefinitely, and no absolute path is recoverable from this input. + const result = directoryPathFromPickedFiles(fileList(pickedFile('docs/readme.md'))); + + expect(result).toEqual({ ok: false, reason: 'no-absolute-path' }); + }); + + it('never returns the bare directory name for any nesting depth', () => { + for (const relative of ['docs/a.md', 'docs/deep/b.md', 'docs/deep/deeper/c.md']) { + const result = directoryPathFromPickedFiles(fileList(pickedFile(relative))); + + expect(result.ok).toBe(false); + // Belt and braces: assert the specific bad value can never come back, + // not merely that this input failed. + expect(JSON.stringify(result)).not.toContain('"docs"'); + } + }); + + it('derives the containing directory when File.path is present', () => { + const result = directoryPathFromPickedFiles( + fileList(pickedFile('notes/readme.md', '/Users/you/notes/readme.md')) + ); + + expect(result).toEqual({ ok: true, path: '/Users/you' }); + }); + + it('derives a Windows directory without mangling the separators', () => { + const result = directoryPathFromPickedFiles( + fileList(pickedFile('notes/readme.md', 'C:\\Users\\you\\notes\\readme.md')) + ); + + // `webkitRelativePath` is forward-slashed while the absolute path is not, + // so the relative portion does not appear verbatim and the whole path is + // kept. That is a usable absolute path, which is the property that matters. + expect(result).toEqual({ ok: true, path: 'C:\\Users\\you\\notes\\readme.md' }); + }); + + it('treats an empty selection as a cancellation rather than an error', () => { + expect(directoryPathFromPickedFiles(null)).toEqual({ ok: false, reason: 'cancelled' }); + expect(directoryPathFromPickedFiles(fileList())).toEqual({ ok: false, reason: 'cancelled' }); + }); +}); + +describe('pickDirectoryNatively', () => { + beforeEach(() => { + hoisted.invoke.mockReset(); + hoisted.isTauri.mockReset(); + }); + + it('reports unavailable outside the desktop shell, without invoking', async () => { + hoisted.isTauri.mockReturnValue(false); + + await expect(pickDirectoryNatively()).resolves.toEqual({ ok: false, reason: 'unavailable' }); + expect(hoisted.invoke).not.toHaveBeenCalled(); + }); + + it('returns the absolute path the native dialog chose', async () => { + hoisted.isTauri.mockReturnValue(true); + hoisted.invoke.mockResolvedValue('/Users/you/notes'); + + await expect(pickDirectoryNatively()).resolves.toEqual({ ok: true, path: '/Users/you/notes' }); + expect(hoisted.invoke).toHaveBeenCalledWith('pick_directory_via_dialog'); + }); + + it('treats a null return as a cancellation', async () => { + hoisted.isTauri.mockReturnValue(true); + hoisted.invoke.mockResolvedValue(null); + + await expect(pickDirectoryNatively()).resolves.toEqual({ ok: false, reason: 'cancelled' }); + }); + + it('falls back to unavailable when the dialog cannot run', async () => { + // Headless Linux with no xdg-desktop portal lands here, and wants the + // fallback input rather than an error. + hoisted.isTauri.mockReturnValue(true); + hoisted.invoke.mockRejectedValue(new Error('no portal')); + + await expect(pickDirectoryNatively()).resolves.toEqual({ ok: false, reason: 'unavailable' }); + }); + + it('refuses a blank path rather than storing it', async () => { + hoisted.isTauri.mockReturnValue(true); + hoisted.invoke.mockResolvedValue(' '); + + await expect(pickDirectoryNatively()).resolves.toEqual({ + ok: false, + reason: 'no-absolute-path', + }); + }); +}); diff --git a/app/src/components/intelligence/folderPicker.ts b/app/src/components/intelligence/folderPicker.ts new file mode 100644 index 0000000000..aad9941058 --- /dev/null +++ b/app/src/components/intelligence/folderPicker.ts @@ -0,0 +1,110 @@ +/** + * Folder selection for the folder memory-source (#5831). + * + * The rule this module exists to enforce: **never hand back a value that + * cannot work as a path.** A folder source is stored verbatim and read back + * by the memory driver, so a value that is not an absolute path produces a + * source that looks configured and can never sync — failing once per cycle, + * indefinitely, with an error the user cannot connect to the picker they + * used. Refusing at the point of choosing is strictly better: it is one + * failure, immediately, next to the control that caused it. + * + * Two selection paths, in preference order: + * + * 1. {@link pickDirectoryNatively} — the OS directory chooser, via the + * `pick_directory_via_dialog` Tauri command. Returns an absolute path in + * every renderer and on every platform. This is the durable answer. + * 2. {@link directoryPathFromPickedFiles} — the `` + * fallback for a browser context, where no native dialog exists. It can + * only produce a real path when Chromium exposes the non-standard + * `File.path`; when it does not, this returns a failure rather than the + * bare directory name the old code stored. + */ +import { safeInvoke as invoke, isTauri } from '../../utils/tauriCommands/common'; + +/** + * Why a folder selection produced no usable path. + * + * - `cancelled` — the user dismissed the chooser. Not an error; the caller + * leaves the field as it was and says nothing. + * - `unavailable` — no native chooser here (a browser context, or the + * dialog could not run). The caller offers the fallback input. + * - `no-absolute-path` — a directory was chosen but the renderer would not + * say where it is. **This is the case that must never be stored.** + */ +export type FolderPickFailure = 'cancelled' | 'unavailable' | 'no-absolute-path'; + +export type FolderPickResult = + | { ok: true; path: string } + | { ok: false; reason: FolderPickFailure }; + +/** + * Derive the chosen directory's absolute path from a `webkitdirectory` + * `FileList`. + * + * Chromium exposes the absolute path of each file on the non-standard + * `File.path` attribute only when the renderer has filesystem-aware + * integration. When present, the directory is that path with the file's + * `webkitRelativePath` (`/<...>/`) trimmed off the end. + * + * When it is absent, all that remains is `webkitRelativePath`, whose first + * segment is the directory's **name** — not its location. The old handler + * stored that name and it could never resolve, which is #5831. So this + * returns `no-absolute-path` instead: the caller must surface it, not save it. + */ +export function directoryPathFromPickedFiles(files: FileList | null): FolderPickResult { + if (!files || files.length === 0) { + return { ok: false, reason: 'cancelled' }; + } + + const first = files[0] as File & { path?: string }; + const absolute = first.path; + + if (!absolute) { + // `webkitRelativePath` is deliberately NOT consulted as a fallback. Its + // first segment is a name with the location discarded; storing it is the + // defect, not a degraded-but-usable answer. + return { ok: false, reason: 'no-absolute-path' }; + } + + const relative = first.webkitRelativePath || first.name; + const cut = absolute.lastIndexOf(relative); + // `cut > 0` keeps a pathological `lastIndexOf` result (0, or -1 when the + // relative portion is somehow absent) from truncating the path to nothing. + const directory = cut > 0 ? absolute.slice(0, cut) : absolute; + const trimmed = directory.replace(/[/\\]+$/, ''); + + // A trailing-separator trim can empty a root-level selection ("/" -> ""), + // and an empty path is exactly the unusable value this module refuses. + if (trimmed.length === 0) { + return { ok: true, path: directory }; + } + return { ok: true, path: trimmed }; +} + +/** + * Open the OS-native directory chooser. + * + * Resolves `unavailable` outside the desktop shell, and also when the + * command throws — a headless Linux host with no xdg-desktop portal reaches + * the same place as a browser tab, and both want the fallback input rather + * than an error. + */ +export async function pickDirectoryNatively(): Promise { + if (!isTauri()) { + return { ok: false, reason: 'unavailable' }; + } + + try { + const chosen = await invoke('pick_directory_via_dialog'); + if (chosen == null) { + return { ok: false, reason: 'cancelled' }; + } + if (chosen.trim().length === 0) { + return { ok: false, reason: 'no-absolute-path' }; + } + return { ok: true, path: chosen }; + } catch { + return { ok: false, reason: 'unavailable' }; + } +} diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 63de2d781c..bbbe6bb080 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -2747,6 +2747,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'قريباً', 'memorySources.composioListFailed': 'فشل في تحميل الأتصالات Xqx0x.', 'memorySources.browse': '(بروز)...', + 'memorySources.folderPathUnavailable': + 'تعذّر تحديد موقع هذا المجلد. اكتب مساره الكامل بدلاً من ذلك.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 04b37813d2..b913310913 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -2813,6 +2813,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'শীঘ্রই আসছে', 'memorySources.composioListFailed': 'Xqxqx সংযোগ লোড করতে ব্যর্থ।', 'memorySources.browse': 'ব্রাউজ করুন...', + 'memorySources.folderPathUnavailable': + 'সেই ফোল্ডারটি কোথায় আছে তা নির্ণয় করা যায়নি। বদলে এর সম্পূর্ণ পাথ লিখুন।', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '*ড্যাম', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index bb679f37b6..b1a43b5dbc 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -2891,6 +2891,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Demnächst', 'memorySources.composioListFailed': 'Fehler beim Laden der Composio-Verbindungen.', 'memorySources.browse': 'Durchsuchen…', + 'memorySources.folderPathUnavailable': + 'Der Speicherort dieses Ordners ließ sich nicht ermitteln. Gib stattdessen den vollständigen Pfad ein.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': 'Md. ', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index dd3774e19a..840e71a9df 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -3061,6 +3061,8 @@ const en: TranslationMap = { 'memorySources.comingSoon': 'Coming soon', 'memorySources.composioListFailed': 'Failed to load Composio connections.', 'memorySources.browse': 'Browse…', + 'memorySources.folderPathUnavailable': + 'Could not determine where that folder is. Type its full path instead.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index eb71b79274..a629c8b363 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -2866,6 +2866,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Próximamente', 'memorySources.composioListFailed': 'Error al cargar las conexiones Composio.', 'memorySources.browse': 'Examinar…', + 'memorySources.folderPathUnavailable': + 'No se pudo determinar dónde está esa carpeta. Escribe su ruta completa.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index a626e6f338..72448c1e21 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -2889,6 +2889,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Bientôt disponible', 'memorySources.composioListFailed': 'Échec du chargement des connexions Composio.', 'memorySources.browse': 'Parcourir…', + 'memorySources.folderPathUnavailable': + 'Impossible de déterminer où se trouve ce dossier. Saisissez plutôt son chemin complet.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 825b0a54a2..c1df0e9e0e 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -2811,6 +2811,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'जल्द आ रहा है', 'memorySources.composioListFailed': 'Composio कनेक्शन लोड करने में विफल रहा।', 'memorySources.browse': 'ब्राउज़ करें', + 'memorySources.folderPathUnavailable': + 'यह पता नहीं चल सका कि वह फ़ोल्डर कहाँ है। इसके बजाय उसका पूरा पथ लिखें।', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index ede8f92239..363b99bea2 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -2823,6 +2823,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Segera hadir', 'memorySources.composioListFailed': 'Gagal memuat koneksi Composio.', 'memorySources.browse': 'Jelajahi...', + 'memorySources.folderPathUnavailable': + 'Tidak dapat menentukan lokasi folder tersebut. Ketik jalur lengkapnya.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '* * /*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index cb0ff7dc3b..45f5484fbd 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -2865,6 +2865,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Prossimamente', 'memorySources.composioListFailed': 'Impossibile caricare le connessioni Composio.', 'memorySources.browse': 'Sfoglia…', + 'memorySources.folderPathUnavailable': + 'Non è stato possibile determinare dove si trova quella cartella. Digita invece il percorso completo.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 3f9a749fb7..8d7b0982fe 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -2777,6 +2777,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': '출시 예정', 'memorySources.composioListFailed': 'Composio 연결을 불러오지 못했습니다.', 'memorySources.browse': '찾아보기…', + 'memorySources.folderPathUnavailable': + '해당 폴더의 위치를 확인할 수 없습니다. 대신 전체 경로를 입력하세요.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index dc6649b9dd..cc850564e5 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -2842,6 +2842,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Wkrótce', 'memorySources.composioListFailed': 'Nie udało się wczytać połączeń Composio.', 'memorySources.browse': 'Przeglądaj…', + 'memorySources.folderPathUnavailable': + 'Nie udało się ustalić, gdzie znajduje się ten folder. Wpisz zamiast tego jego pełną ścieżkę.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index 06427b7959..eb9b7c3be6 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -2861,6 +2861,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Em breve', 'memorySources.composioListFailed': 'Falha ao carregar as conexões Composio.', 'memorySources.browse': 'Navegar…', + 'memorySources.folderPathUnavailable': + 'Não foi possível determinar onde essa pasta está. Digite o caminho completo.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index 38607343f3..94299cc7c7 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -2831,6 +2831,8 @@ const messages: TranslationMap = { 'memorySources.comingSoon': 'Скоро', 'memorySources.composioListFailed': 'Не удалось загрузить соединения Composio.', 'memorySources.browse': 'Просматривать…', + 'memorySources.folderPathUnavailable': + 'Не удалось определить расположение этой папки. Введите полный путь.', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index c2486840a8..cf13995a94 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -2659,6 +2659,7 @@ const messages: TranslationMap = { 'memorySources.comingSoon': '即将推出', 'memorySources.composioListFailed': '加载 Composio 连接失败。', 'memorySources.browse': '浏览…', + 'memorySources.folderPathUnavailable': '无法确定该文件夹的位置。请直接输入完整路径。', 'memorySources.folderPathPlaceholder': '/Users/you/notes', 'memorySources.globPatternPlaceholder': '**/*.md', 'memorySources.repoUrlPlaceholder': 'https://github.com/org/repo',