mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
co-authored by
Steven Enamakel
parent
56a31c88bd
commit
9683653211
@@ -0,0 +1,92 @@
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { describe, expect, it, type Mock, vi } from 'vitest';
|
||||
|
||||
import { renderWithProviders } from '../../test/test-utils';
|
||||
import type { VaultHealthCheck } from '../../utils/tauriCommands';
|
||||
import { VaultHealthChecklist } from './VaultHealthChecklist';
|
||||
|
||||
vi.mock('../../utils/tauriCommands', () => ({ memoryTreeVaultHealthCheck: vi.fn() }));
|
||||
|
||||
vi.mock('../../utils/openUrl', () => ({
|
||||
openUrl: vi.fn().mockResolvedValue(undefined),
|
||||
revealPath: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
const { memoryTreeVaultHealthCheck } = (await import('../../utils/tauriCommands')) as unknown as {
|
||||
memoryTreeVaultHealthCheck: Mock;
|
||||
};
|
||||
|
||||
const { openUrl, revealPath } = (await import('../../utils/openUrl')) as unknown as {
|
||||
openUrl: Mock;
|
||||
revealPath: Mock;
|
||||
};
|
||||
|
||||
function health(overrides: Partial<VaultHealthCheck> = {}): VaultHealthCheck {
|
||||
return {
|
||||
content_root_abs: '/tmp/workspace/memory_tree/content',
|
||||
exists: true,
|
||||
readable: true,
|
||||
writable: true,
|
||||
obsidian_registered: true,
|
||||
pipeline_healthy: true,
|
||||
last_sync_ms: Date.now() - 60_000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('<VaultHealthChecklist />', () => {
|
||||
it('shows all checks as passed when the health RPC is fully healthy', async () => {
|
||||
memoryTreeVaultHealthCheck.mockResolvedValueOnce(health());
|
||||
renderWithProviders(<VaultHealthChecklist />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('vault-health-item-exists')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/Passed · Workspace vault path exists/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Passed · Vault is writable by OpenHuman/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Passed · Vault is registered in Obsidian/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Passed · Memory pipeline is healthy/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('surfaces a recovery hint when the vault folder is missing', async () => {
|
||||
memoryTreeVaultHealthCheck.mockResolvedValueOnce(health({ exists: false, readable: false }));
|
||||
renderWithProviders(<VaultHealthChecklist />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('vault-health-item-exists')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/Vault folder is missing/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('surfaces a recovery hint when the vault is not writable', async () => {
|
||||
memoryTreeVaultHealthCheck.mockResolvedValueOnce(health({ writable: false }));
|
||||
renderWithProviders(<VaultHealthChecklist />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('vault-health-item-writable')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/cannot write to this vault yet/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('surfaces Obsidian registration guidance and action buttons', async () => {
|
||||
memoryTreeVaultHealthCheck.mockResolvedValueOnce(health({ obsidian_registered: false }));
|
||||
renderWithProviders(<VaultHealthChecklist />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('vault-health-item-obsidian')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/Open folder as vault/)).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByTestId('vault-health-open-obsidian'));
|
||||
await waitFor(() => {
|
||||
expect(openUrl).toHaveBeenCalledWith(
|
||||
'obsidian://open?path=' + encodeURIComponent('/tmp/workspace/memory_tree/content')
|
||||
);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId('vault-health-reveal'));
|
||||
await waitFor(() => {
|
||||
expect(revealPath).toHaveBeenCalledWith('/tmp/workspace/memory_tree/content');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,230 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { useT } from '../../lib/i18n/I18nContext';
|
||||
import type { ToastNotification } from '../../types/intelligence';
|
||||
import { openUrl, revealPath } from '../../utils/openUrl';
|
||||
import { memoryTreeVaultHealthCheck, type VaultHealthCheck } from '../../utils/tauriCommands';
|
||||
|
||||
const OBSIDIAN_DOWNLOAD_URL = 'https://obsidian.md/download';
|
||||
|
||||
interface VaultHealthChecklistProps {
|
||||
onToast?: (toast: Omit<ToastNotification, 'id'>) => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
function formatRelativeTime(ms: number, t: (key: string, fallback?: string) => string): string {
|
||||
if (!ms || ms <= 0) return t('vaultHealth.timeNever');
|
||||
const diff = Date.now() - ms;
|
||||
if (diff < 0) return t('vaultHealth.timeNever');
|
||||
const sec = Math.floor(diff / 1000);
|
||||
if (sec < 45) return t('vaultHealth.timeJustNow');
|
||||
const min = Math.floor(sec / 60);
|
||||
if (min < 60) return t('vaultHealth.timeMinAgo').replace('{n}', String(min));
|
||||
const hr = Math.floor(min / 60);
|
||||
if (hr < 24) return t('vaultHealth.timeHrAgo').replace('{n}', String(hr));
|
||||
const day = Math.floor(hr / 24);
|
||||
return (day === 1 ? t('vaultHealth.timeDayAgo') : t('vaultHealth.timeDaysAgo')).replace(
|
||||
'{n}',
|
||||
String(day)
|
||||
);
|
||||
}
|
||||
|
||||
function dirname(path: string): string {
|
||||
const normalized = path.replace(/[\\/]+$/, '');
|
||||
const slash = Math.max(normalized.lastIndexOf('/'), normalized.lastIndexOf('\\'));
|
||||
if (slash <= 0) return normalized;
|
||||
return normalized.slice(0, slash);
|
||||
}
|
||||
|
||||
export function VaultHealthChecklist({ onToast, title }: VaultHealthChecklistProps) {
|
||||
const { t } = useT();
|
||||
const resolvedTitle = title ?? t('vaultHealth.title');
|
||||
const [health, setHealth] = useState<VaultHealthCheck | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const runCheck = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
try {
|
||||
const next = await memoryTreeVaultHealthCheck();
|
||||
setHealth(next);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
setError(message);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void runCheck();
|
||||
}, [runCheck]);
|
||||
|
||||
const revealTarget = useMemo(() => {
|
||||
if (!health?.content_root_abs) return '';
|
||||
return health.exists ? health.content_root_abs : dirname(health.content_root_abs);
|
||||
}, [health]);
|
||||
|
||||
const openObsidian = useCallback(() => {
|
||||
if (!health?.content_root_abs) return;
|
||||
void (async () => {
|
||||
try {
|
||||
await openUrl(`obsidian://open?path=${encodeURIComponent(health.content_root_abs)}`);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
onToast?.({ type: 'error', title: t('vaultHealth.openObsidianError'), message });
|
||||
}
|
||||
})();
|
||||
}, [health, onToast, t]);
|
||||
|
||||
const revealVault = useCallback(() => {
|
||||
if (!revealTarget) return;
|
||||
void (async () => {
|
||||
try {
|
||||
await revealPath(revealTarget);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
onToast?.({ type: 'error', title: t('vaultHealth.revealError'), message });
|
||||
}
|
||||
})();
|
||||
}, [onToast, revealTarget, t]);
|
||||
|
||||
const installObsidian = useCallback(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
await openUrl(OBSIDIAN_DOWNLOAD_URL);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
onToast?.({ type: 'error', title: t('vaultHealth.downloadError'), message });
|
||||
}
|
||||
})();
|
||||
}, [onToast, t]);
|
||||
|
||||
const checklist = health
|
||||
? [
|
||||
{
|
||||
key: 'exists',
|
||||
label: t('vaultHealth.existsLabel'),
|
||||
ok: health.exists,
|
||||
recovery: t('vaultHealth.existsRecovery'),
|
||||
},
|
||||
{
|
||||
key: 'writable',
|
||||
label: t('vaultHealth.writableLabel'),
|
||||
ok: health.writable,
|
||||
recovery: t('vaultHealth.writableRecovery'),
|
||||
},
|
||||
{
|
||||
key: 'obsidian',
|
||||
label: t('vaultHealth.obsidianLabel'),
|
||||
ok: health.obsidian_registered,
|
||||
recovery: t('vaultHealth.obsidianRecovery'),
|
||||
},
|
||||
{
|
||||
key: 'pipeline',
|
||||
label: t('vaultHealth.pipelineLabel'),
|
||||
ok: health.pipeline_healthy,
|
||||
recovery: t('vaultHealth.pipelineRecovery'),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-xl border border-stone-200 dark:border-neutral-700 bg-white dark:bg-neutral-900 p-4 space-y-3"
|
||||
data-testid="vault-health-checklist">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{resolvedTitle}
|
||||
</h3>
|
||||
<p className="mt-1 text-xs text-stone-600 dark:text-neutral-300">
|
||||
{t('vaultHealth.workspaceVault')} <code className="font-mono">memory_tree/content</code>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void runCheck();
|
||||
}}
|
||||
disabled={refreshing}
|
||||
className="rounded-md border border-stone-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 px-2.5 py-1 text-xs font-medium text-stone-700 dark:text-neutral-200 disabled:opacity-60"
|
||||
data-testid="vault-health-refresh">
|
||||
{refreshing ? t('vaultHealth.refreshing') : t('vaultHealth.refresh')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{health?.content_root_abs ? (
|
||||
<code
|
||||
className="block break-all rounded-md bg-stone-100 dark:bg-neutral-800 px-2 py-1 text-[11px] text-stone-700 dark:text-neutral-200"
|
||||
data-testid="vault-health-path">
|
||||
{health.content_root_abs}
|
||||
</code>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={revealVault}
|
||||
disabled={!health?.content_root_abs}
|
||||
className="rounded-md border border-stone-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 px-3 py-1.5 text-xs font-semibold text-stone-700 dark:text-neutral-200 disabled:opacity-50"
|
||||
data-testid="vault-health-reveal">
|
||||
{t('vaultHealth.revealFolder')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={openObsidian}
|
||||
disabled={!health?.content_root_abs}
|
||||
className="rounded-md border border-violet-300 dark:border-violet-500/40 bg-white dark:bg-neutral-800 px-3 py-1.5 text-xs font-semibold text-violet-700 dark:text-violet-300 disabled:opacity-50"
|
||||
data-testid="vault-health-open-obsidian">
|
||||
{t('vaultHealth.openInObsidian')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={installObsidian}
|
||||
className="rounded-md border border-stone-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 px-3 py-1.5 text-xs font-semibold text-stone-700 dark:text-neutral-200"
|
||||
data-testid="vault-health-install-obsidian">
|
||||
{t('vaultHealth.installObsidian')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="h-16 rounded-md bg-stone-100 dark:bg-neutral-800 animate-pulse" />
|
||||
) : error ? (
|
||||
<div
|
||||
className="rounded-md border border-coral-200 dark:border-coral-500/30 bg-coral-50 dark:bg-coral-500/10 px-3 py-2 text-xs text-coral-700 dark:text-coral-300"
|
||||
data-testid="vault-health-error">
|
||||
{t('vaultHealth.loadError')} {error}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{checklist.map(item => (
|
||||
<div
|
||||
key={item.key}
|
||||
data-testid={`vault-health-item-${item.key}`}
|
||||
className={`rounded-md border px-3 py-2 text-xs ${
|
||||
item.ok
|
||||
? 'border-sage-200 dark:border-sage-500/30 bg-sage-50 dark:bg-sage-500/10 text-sage-800 dark:text-sage-200'
|
||||
: 'border-amber-200 dark:border-amber-500/30 bg-amber-50 dark:bg-amber-500/10 text-amber-800 dark:text-amber-200'
|
||||
}`}>
|
||||
<div className="font-semibold">
|
||||
{item.ok ? t('vaultHealth.passed') : t('vaultHealth.needsAttention')} · {item.label}
|
||||
</div>
|
||||
{!item.ok ? <p className="mt-1 leading-relaxed">{item.recovery}</p> : null}
|
||||
</div>
|
||||
))}
|
||||
<p
|
||||
className="text-[11px] text-stone-600 dark:text-neutral-300"
|
||||
data-testid="vault-health-last-sync">
|
||||
{t('vaultHealth.lastSync')} {formatRelativeTime(health?.last_sync_ms ?? 0, t)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VaultHealthChecklist;
|
||||
@@ -154,7 +154,9 @@ const MemoryWindowControl = ({ onError, onSaved }: Props) => {
|
||||
data-testid="memory-window-control">
|
||||
<div className="flex items-baseline justify-between">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold">{t('settings.memoryWindow.title')}</h3>
|
||||
<h3 className="text-base font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{t('settings.memoryWindow.title')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">{t('settings.memoryWindow.description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import type { ToastNotification } from '../../../types/intelligence';
|
||||
import { MemoryWorkspace } from '../../intelligence/MemoryWorkspace';
|
||||
import { ToastContainer } from '../../intelligence/Toast';
|
||||
import { VaultHealthChecklist } from '../../intelligence/VaultHealthChecklist';
|
||||
import MemoryWindowControl from '../components/MemoryWindowControl';
|
||||
import SettingsHeader from '../components/SettingsHeader';
|
||||
import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
|
||||
@@ -30,16 +31,20 @@ const MemoryDataPanel = ({ embedded = false }: MemoryDataPanelProps = {}) => {
|
||||
|
||||
const handleWindowError = useCallback(
|
||||
(message: string) => {
|
||||
addToast({ type: 'error', title: 'Memory window', message });
|
||||
addToast({ type: 'error', title: t('memoryData.windowError'), message });
|
||||
},
|
||||
[addToast]
|
||||
[addToast, t]
|
||||
);
|
||||
|
||||
const handleWindowSaved = useCallback(
|
||||
(window: string) => {
|
||||
addToast({ type: 'success', title: 'Memory window updated', message: `Set to ${window}.` });
|
||||
addToast({
|
||||
type: 'success',
|
||||
title: t('memoryData.windowUpdated'),
|
||||
message: t('memoryData.windowUpdatedMsg').replace('{window}', window),
|
||||
});
|
||||
},
|
||||
[addToast]
|
||||
[addToast, t]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -53,6 +58,38 @@ const MemoryDataPanel = ({ embedded = false }: MemoryDataPanelProps = {}) => {
|
||||
/>
|
||||
)}
|
||||
<div className={embedded ? 'space-y-4' : 'p-4 space-y-4'}>
|
||||
<section className="rounded-xl border border-stone-200 dark:border-neutral-700 bg-white dark:bg-neutral-900 p-4 space-y-3">
|
||||
<h3 className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{t('memoryData.howItWorks')}
|
||||
</h3>
|
||||
<dl className="space-y-2.5">
|
||||
<div>
|
||||
<dt className="text-xs font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{t('memoryData.workspaceVault')}
|
||||
</dt>
|
||||
<dd className="text-xs leading-relaxed text-stone-600 dark:text-neutral-300">
|
||||
{t('memoryData.workspaceVaultDesc')}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{t('memoryData.connectedSources')}
|
||||
</dt>
|
||||
<dd className="text-xs leading-relaxed text-stone-600 dark:text-neutral-300">
|
||||
{t('memoryData.connectedSourcesDesc')}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{t('memoryData.internalFiles')}
|
||||
</dt>
|
||||
<dd className="text-xs leading-relaxed text-stone-600 dark:text-neutral-300">
|
||||
{t('memoryData.internalFilesDesc')}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
<VaultHealthChecklist onToast={addToast} title={t('vaultHealth.setupTitle')} />
|
||||
<MemoryWindowControl onError={handleWindowError} onSaved={handleWindowSaved} />
|
||||
<MemoryWorkspace onToast={addToast} />
|
||||
</div>
|
||||
|
||||
@@ -4431,11 +4431,68 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'النشاط',
|
||||
'onboarding.custom.stepperVault': 'الخزينة',
|
||||
'onboarding.custom.activity.title': 'نشاط الوكيل',
|
||||
'onboarding.custom.activity.subtitle': 'مدى استباقية وكيلك في المراقبة والتصرف في الخلفية.',
|
||||
'onboarding.custom.activity.defaultDesc': 'نشاط متوسط — مزامنة كل ساعة، ملخص يومي.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'اختر مستوى نشاطك الخاص. الإعداد في الإعدادات › مستوى نشاط الوكيل.',
|
||||
'onboarding.custom.vault.title': 'إعداد الذاكرة والخزينة',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'تأكيد موضع كتابة ملاحظات الذاكرة، وكيفية قراءة البيانات المصدر، وسلامة مسار الخزينة.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'استخدم الإعدادات الافتراضية لذاكرة OpenHuman المُدارة. يمكن مراجعة مسار الخزينة وصحة المزامنة لاحقًا.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'راجع ملكية الخزينة، وشغّل فحوصات الصحة، واضبط عناصر التحكم في الذاكرة الآن.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'يتطلب الإعداد المُدار تسجيل الدخول إلى OpenHuman وغير متاح في الوضع المحلي.',
|
||||
'onboarding.custom.vault.exitError': 'تعذّر إتمام الإعداد. يُرجى المحاولة مجددًا.',
|
||||
'vaultHealth.title': 'قائمة فحص صحة الخزينة',
|
||||
'vaultHealth.setupTitle': 'صحة إعداد الخزينة',
|
||||
'vaultHealth.workspaceVault': 'خزينة مساحة العمل:',
|
||||
'vaultHealth.refresh': 'تحديث',
|
||||
'vaultHealth.refreshing': 'جارٍ التحديث…',
|
||||
'vaultHealth.revealFolder': 'إظهار المجلد',
|
||||
'vaultHealth.openInObsidian': 'فتح في Obsidian',
|
||||
'vaultHealth.installObsidian': 'تثبيت Obsidian',
|
||||
'vaultHealth.openObsidianError': 'تعذّر فتح Obsidian',
|
||||
'vaultHealth.revealError': 'تعذّر إظهار مجلد الخزينة',
|
||||
'vaultHealth.downloadError': 'تعذّر فتح صفحة تنزيل Obsidian',
|
||||
'vaultHealth.loadError': 'تعذّر تحميل صحة الخزينة:',
|
||||
'vaultHealth.lastSync': 'آخر مزامنة:',
|
||||
'vaultHealth.passed': 'ناجح',
|
||||
'vaultHealth.needsAttention': 'يحتاج إلى انتباه',
|
||||
'vaultHealth.existsLabel': 'مسار خزينة مساحة العمل موجود',
|
||||
'vaultHealth.existsRecovery':
|
||||
'مجلد الخزينة مفقود. ابدأ مزامنة أو أنشئ هذا المجلد، ثم حدّث هذه القائمة.',
|
||||
'vaultHealth.writableLabel': 'الخزينة قابلة للكتابة بواسطة OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
'لا يستطيع OpenHuman الكتابة في هذه الخزينة بعد. امنح أذونات الكتابة ثم حدّث.',
|
||||
'vaultHealth.obsidianLabel': 'الخزينة مسجّلة في Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'في Obsidian، اختر "فتح المجلد كخزينة" لهذا المسار، ثم حدّث هذه القائمة.',
|
||||
'vaultHealth.pipelineLabel': 'مسار الذاكرة سليم',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'مسار الذاكرة متوقف أو في حالة خطأ. أعد تفعيل المزامنة التلقائية في حالة شجرة الذاكرة وأعد المحاولة.',
|
||||
'vaultHealth.timeNever': 'قط',
|
||||
'vaultHealth.timeJustNow': 'الآن',
|
||||
'vaultHealth.timeMinAgo': 'منذ {n} دقيقة',
|
||||
'vaultHealth.timeHrAgo': 'منذ {n} ساعة',
|
||||
'vaultHealth.timeDayAgo': 'منذ {n} يوم',
|
||||
'vaultHealth.timeDaysAgo': 'منذ {n} أيام',
|
||||
'memoryData.howItWorks': 'كيف يعمل تخزين الذاكرة',
|
||||
'memoryData.workspaceVault': 'خزينة مساحة العمل · كتابة',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'يكتب OpenHuman ملاحظات الذاكرة المُولَّدة إلى memory_tree/content.',
|
||||
'memoryData.connectedSources': 'المصادر المتصلة · قراءة',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'تُستورد المجلدات وصناديق البريد والمحادثات والمستودعات لفهرسة الذاكرة — ولا تُعاد كتابة ملفاتها الأصلية أبدًا.',
|
||||
'memoryData.internalFiles': 'ملفات شجرة الذاكرة الداخلية',
|
||||
'memoryData.internalFilesDesc':
|
||||
'تُدار الفهارس وحالة قائمة الانتظار والملخصات بواسطة OpenHuman للحفاظ على سلامة الاسترجاع والمزامنة.',
|
||||
'memoryData.windowError': 'نافذة الذاكرة',
|
||||
'memoryData.windowUpdated': 'تم تحديث نافذة الذاكرة',
|
||||
'memoryData.windowUpdatedMsg': 'تم الضبط على {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4512,6 +4512,7 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'কার্যকলাপ',
|
||||
'onboarding.custom.stepperVault': 'ভল্ট',
|
||||
'onboarding.custom.activity.title': 'এজেন্টের কার্যকলাপ',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'আপনার এজেন্ট পটভূমিতে কতটা সক্রিয়ভাবে পর্যবেক্ষণ ও কাজ করে।',
|
||||
@@ -4519,6 +4520,61 @@ const messages: TranslationMap = {
|
||||
'মাঝারি কার্যকলাপ — প্রতি ঘণ্টায় সিঙ্ক, দৈনিক সারসংক্ষেপ।',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'নিজের কার্যকলাপের স্তর বেছে নিন। সেটিংস › এজেন্ট কার্যকলাপ স্তরে কনফিগার করুন।',
|
||||
'onboarding.custom.vault.title': 'মেমোরি ও ভল্ট সেটআপ',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'নিশ্চিত করুন মেমোরি নোট কোথায় লেখা হয়, উৎস ডেটা কীভাবে পড়া হয় এবং আপনার ভল্ট পাইপলাইন সুস্থ কিনা।',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'OpenHuman-পরিচালিত মেমোরির ডিফল্ট ব্যবহার করুন। ভল্ট পাথ এবং সিঙ্ক স্বাস্থ্য পরে পর্যালোচনা করা যাবে।',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'ভল্টের মালিকানা পর্যালোচনা করুন, স্বাস্থ্য পরীক্ষা চালান এবং এখনই মেমোরি নিয়ন্ত্রণ সামঞ্জস্য করুন।',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'পরিচালিত সেটআপের জন্য OpenHuman সাইন-ইন প্রয়োজন এবং লোকাল মোডে উপলব্ধ নয়।',
|
||||
'onboarding.custom.vault.exitError': 'অনবোর্ডিং শেষ করা যায়নি। আবার চেষ্টা করুন।',
|
||||
'vaultHealth.title': 'ভল্ট স্বাস্থ্য চেকলিস্ট',
|
||||
'vaultHealth.setupTitle': 'ভল্ট সেটআপ স্বাস্থ্য',
|
||||
'vaultHealth.workspaceVault': 'ওয়ার্কস্পেস ভল্ট:',
|
||||
'vaultHealth.refresh': 'রিফ্রেশ',
|
||||
'vaultHealth.refreshing': 'রিফ্রেশ হচ্ছে…',
|
||||
'vaultHealth.revealFolder': 'ফোল্ডার দেখান',
|
||||
'vaultHealth.openInObsidian': 'Obsidian-এ খুলুন',
|
||||
'vaultHealth.installObsidian': 'Obsidian ইনস্টল করুন',
|
||||
'vaultHealth.openObsidianError': 'Obsidian খোলা যায়নি',
|
||||
'vaultHealth.revealError': 'ভল্ট ফোল্ডার দেখানো যায়নি',
|
||||
'vaultHealth.downloadError': 'Obsidian ডাউনলোড পৃষ্ঠা খোলা যায়নি',
|
||||
'vaultHealth.loadError': 'ভল্ট স্বাস্থ্য লোড করা যায়নি:',
|
||||
'vaultHealth.lastSync': 'শেষ সিঙ্ক:',
|
||||
'vaultHealth.passed': 'পাস হয়েছে',
|
||||
'vaultHealth.needsAttention': 'মনোযোগ প্রয়োজন',
|
||||
'vaultHealth.existsLabel': 'ওয়ার্কস্পেস ভল্ট পাথ বিদ্যমান',
|
||||
'vaultHealth.existsRecovery':
|
||||
'ভল্ট ফোল্ডার নেই। একটি সিঙ্ক শুরু করুন বা এই ফোল্ডারটি তৈরি করুন, তারপর এই চেকলিস্ট রিফ্রেশ করুন।',
|
||||
'vaultHealth.writableLabel': 'ভল্ট OpenHuman দ্বারা লিখনযোগ্য',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman এখনও এই ভল্টে লিখতে পারছে না। লেখার অনুমতি দিন এবং রিফ্রেশ করুন।',
|
||||
'vaultHealth.obsidianLabel': 'ভল্ট Obsidian-এ নিবন্ধিত',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'Obsidian-এ এই পাথটির জন্য "ফোল্ডার ভল্ট হিসেবে খুলুন" বেছে নিন, তারপর এই চেকলিস্ট রিফ্রেশ করুন।',
|
||||
'vaultHealth.pipelineLabel': 'মেমোরি পাইপলাইন সুস্থ',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'মেমোরি পাইপলাইন বিরতিতে আছে বা ত্রুটিতে আছে। মেমোরি ট্রি স্ট্যাটাসে অটো-সিঙ্ক পুনরায় সক্ষম করুন এবং পুনরায় চেষ্টা করুন।',
|
||||
'vaultHealth.timeNever': 'কখনো না',
|
||||
'vaultHealth.timeJustNow': 'এইমাত্র',
|
||||
'vaultHealth.timeMinAgo': '{n} মিনিট আগে',
|
||||
'vaultHealth.timeHrAgo': '{n} ঘণ্টা আগে',
|
||||
'vaultHealth.timeDayAgo': '{n} দিন আগে',
|
||||
'vaultHealth.timeDaysAgo': '{n} দিন আগে',
|
||||
'memoryData.howItWorks': 'মেমোরি স্টোরেজ কীভাবে কাজ করে',
|
||||
'memoryData.workspaceVault': 'ওয়ার্কস্পেস ভল্ট · লেখা',
|
||||
'memoryData.workspaceVaultDesc': 'OpenHuman তৈরি মেমোরি নোট memory_tree/content-এ লেখে।',
|
||||
'memoryData.connectedSources': 'সংযুক্ত উৎস · পড়া',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'ফোল্ডার, মেইলবক্স, চ্যাট এবং রেপো মেমোরি ইন্ডেক্সিংয়ের জন্য আমদানি করা হয় — তাদের মূল ফাইল কখনো পুনরায় লেখা হয় না।',
|
||||
'memoryData.internalFiles': 'অভ্যন্তরীণ মেমোরি-ট্রি ফাইল',
|
||||
'memoryData.internalFilesDesc':
|
||||
'ইন্ডেক্স, কিউ স্টেট এবং সারসংক্ষেপ OpenHuman দ্বারা পরিচালিত হয় যাতে রিকল ও সিঙ্ক সুস্থ থাকে।',
|
||||
'memoryData.windowError': 'মেমোরি উইন্ডো',
|
||||
'memoryData.windowUpdated': 'মেমোরি উইন্ডো আপডেট হয়েছে',
|
||||
'memoryData.windowUpdatedMsg': '{window}-এ সেট করা হয়েছে।',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4639,6 +4639,70 @@ const messages: TranslationMap = {
|
||||
'Moderate Aktivität – stündliche Synchronisierung, tägliche Zusammenfassung.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Eigene Aktivitätsstufe wählen. Konfigurieren in Einstellungen › Agent-Aktivitätsstufe.',
|
||||
|
||||
// Onboarding: Custom > Vault
|
||||
'onboarding.custom.stepperVault': 'Vault',
|
||||
'onboarding.custom.vault.title': 'Speicher & Vault-Einrichtung',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Bestätigen Sie, wohin Speichernotizen geschrieben werden, wie Quelldaten gelesen werden und ob Ihre Vault-Pipeline fehlerfrei ist.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Von OpenHuman verwaltete Standardspeichereinstellungen verwenden. Vault-Pfad und Synchronisierungsstatus können später eingesehen werden.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Vault-Eigentümerschaft prüfen, Integritätsprüfungen durchführen und Speicherkontrollen jetzt anpassen.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'Die verwaltete Einrichtung erfordert eine OpenHuman-Anmeldung und ist im lokalen Modus nicht verfügbar.',
|
||||
'onboarding.custom.vault.exitError':
|
||||
'Onboarding konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut.',
|
||||
|
||||
// Vault Health
|
||||
'vaultHealth.title': 'Vault-Integritätsprüfliste',
|
||||
'vaultHealth.setupTitle': 'Vault-Einrichtungsstatus',
|
||||
'vaultHealth.workspaceVault': 'Arbeitsbereich-Vault:',
|
||||
'vaultHealth.refresh': 'Aktualisieren',
|
||||
'vaultHealth.refreshing': 'Wird aktualisiert…',
|
||||
'vaultHealth.revealFolder': 'Ordner anzeigen',
|
||||
'vaultHealth.openInObsidian': 'In Obsidian öffnen',
|
||||
'vaultHealth.installObsidian': 'Obsidian installieren',
|
||||
'vaultHealth.openObsidianError': 'Obsidian konnte nicht geöffnet werden',
|
||||
'vaultHealth.revealError': 'Vault-Ordner konnte nicht angezeigt werden',
|
||||
'vaultHealth.downloadError': 'Obsidian-Downloadseite konnte nicht geöffnet werden',
|
||||
'vaultHealth.loadError': 'Vault-Status konnte nicht geladen werden:',
|
||||
'vaultHealth.lastSync': 'Letzte Synchronisierung:',
|
||||
'vaultHealth.passed': 'Bestanden',
|
||||
'vaultHealth.needsAttention': 'Aufmerksamkeit erforderlich',
|
||||
'vaultHealth.existsLabel': 'Arbeitsbereich-Vault-Pfad vorhanden',
|
||||
'vaultHealth.existsRecovery':
|
||||
'Vault-Ordner fehlt. Starten Sie eine Synchronisierung oder erstellen Sie diesen Ordner und aktualisieren Sie dann diese Prüfliste.',
|
||||
'vaultHealth.writableLabel': 'Vault ist durch OpenHuman beschreibbar',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman kann noch nicht in diesen Vault schreiben. Schreibberechtigungen erteilen und aktualisieren.',
|
||||
'vaultHealth.obsidianLabel': 'Vault ist in Obsidian registriert',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'Wählen Sie in Obsidian „Ordner als Vault öffnen" für diesen Pfad und aktualisieren Sie dann diese Prüfliste.',
|
||||
'vaultHealth.pipelineLabel': 'Speicher-Pipeline ist fehlerfrei',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'Speicher-Pipeline ist pausiert oder fehlerhaft. Auto-Synchronisierung im Memory-Tree-Status wieder aktivieren und erneut versuchen.',
|
||||
'vaultHealth.timeNever': 'Nie',
|
||||
'vaultHealth.timeJustNow': 'gerade eben',
|
||||
'vaultHealth.timeMinAgo': 'vor {n} Min.',
|
||||
'vaultHealth.timeHrAgo': 'vor {n} Std.',
|
||||
'vaultHealth.timeDayAgo': 'vor {n} Tag',
|
||||
'vaultHealth.timeDaysAgo': 'vor {n} Tagen',
|
||||
|
||||
// Memory Data
|
||||
'memoryData.howItWorks': 'So funktioniert die Speicherablage',
|
||||
'memoryData.workspaceVault': 'Arbeitsbereich-Vault · Schreiben',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman schreibt generierte Speichernotizen nach memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Verbundene Quellen · Lesen',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'Ordner, Postfächer, Chats und Repositories werden für die Speicherindizierung importiert – die Originaldateien werden dabei nie überschrieben.',
|
||||
'memoryData.internalFiles': 'Interne Memory-Tree-Dateien',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Indizes, Warteschlangenstatus und Zusammenfassungen werden von OpenHuman verwaltet, um Erinnerung und Synchronisierung fehlerfrei zu halten.',
|
||||
'memoryData.windowError': 'Speicherfenster',
|
||||
'memoryData.windowUpdated': 'Speicherfenster aktualisiert',
|
||||
'memoryData.windowUpdatedMsg': 'Auf {window} gesetzt.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
+64
-1
@@ -609,13 +609,14 @@ const en: TranslationMap = {
|
||||
'onboarding.apiKeys.continue': 'Save and continue',
|
||||
'onboarding.apiKeys.saving': 'Saving…',
|
||||
|
||||
// Onboarding: Custom wizard (Inference / Voice / OAuth / Search / Activity / Memory)
|
||||
// Onboarding: Custom wizard (Inference / Voice / OAuth / Search / Activity / Vault / Memory)
|
||||
'onboarding.custom.stepperInference': 'Inference',
|
||||
'onboarding.custom.stepperVoice': 'Voice',
|
||||
'onboarding.custom.stepperOAuth': 'OAuth',
|
||||
'onboarding.custom.stepperSearch': 'Search',
|
||||
'onboarding.custom.stepperEmbeddings': 'Embeddings',
|
||||
'onboarding.custom.stepperActivity': 'Activity',
|
||||
'onboarding.custom.stepperVault': 'Vault',
|
||||
'onboarding.custom.stepperMemory': 'Memory',
|
||||
'onboarding.custom.stepCounter': 'Step {n} of {total}',
|
||||
'onboarding.custom.defaultTitle': 'Default',
|
||||
@@ -682,6 +683,18 @@ const en: TranslationMap = {
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Pick your own activity level. Configure in Settings › Agent activity level.',
|
||||
|
||||
// Onboarding: Custom > Vault
|
||||
'onboarding.custom.vault.title': 'Memory & Vault Setup',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Confirm where memory notes are written, how source data is read, and whether your vault pipeline is healthy.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Use OpenHuman-managed memory defaults. Vault path and sync health can still be reviewed later.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Review vault ownership, run health checks, and tune memory controls now.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'Managed setup requires OpenHuman sign-in and is unavailable in local mode.',
|
||||
'onboarding.custom.vault.exitError': 'Could not finish onboarding. Please try again.',
|
||||
|
||||
// Onboarding: Custom > Memory
|
||||
'onboarding.custom.memory.title': 'Memory',
|
||||
'onboarding.custom.memory.subtitle':
|
||||
@@ -2122,6 +2135,56 @@ const en: TranslationMap = {
|
||||
'workspace.trees': 'Trees',
|
||||
'workspace.contacts': 'Contacts',
|
||||
|
||||
// Vault health checklist
|
||||
'vaultHealth.title': 'Vault Health Checklist',
|
||||
'vaultHealth.setupTitle': 'Vault setup health',
|
||||
'vaultHealth.workspaceVault': 'Workspace vault:',
|
||||
'vaultHealth.refresh': 'Refresh',
|
||||
'vaultHealth.refreshing': 'Refreshing…',
|
||||
'vaultHealth.revealFolder': 'Reveal Folder',
|
||||
'vaultHealth.openInObsidian': 'Open in Obsidian',
|
||||
'vaultHealth.installObsidian': 'Install Obsidian',
|
||||
'vaultHealth.openObsidianError': 'Could not open Obsidian',
|
||||
'vaultHealth.revealError': 'Could not reveal vault folder',
|
||||
'vaultHealth.downloadError': 'Could not open Obsidian download page',
|
||||
'vaultHealth.loadError': 'Could not load vault health:',
|
||||
'vaultHealth.lastSync': 'Last sync:',
|
||||
'vaultHealth.passed': 'Passed',
|
||||
'vaultHealth.needsAttention': 'Needs attention',
|
||||
'vaultHealth.existsLabel': 'Workspace vault path exists',
|
||||
'vaultHealth.existsRecovery':
|
||||
'Vault folder is missing. Start a sync or create this folder, then refresh this checklist.',
|
||||
'vaultHealth.writableLabel': 'Vault is writable by OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman cannot write to this vault yet. Grant write permissions and refresh.',
|
||||
'vaultHealth.obsidianLabel': 'Vault is registered in Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'In Obsidian, choose "Open folder as vault" for this path, then refresh this checklist.',
|
||||
'vaultHealth.pipelineLabel': 'Memory pipeline is healthy',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'Memory pipeline is paused or in error. Re-enable Auto-sync in Memory Tree status and retry.',
|
||||
'vaultHealth.timeNever': 'Never',
|
||||
'vaultHealth.timeJustNow': 'just now',
|
||||
'vaultHealth.timeMinAgo': '{n} min ago',
|
||||
'vaultHealth.timeHrAgo': '{n} hr ago',
|
||||
'vaultHealth.timeDayAgo': '{n} day ago',
|
||||
'vaultHealth.timeDaysAgo': '{n} days ago',
|
||||
|
||||
// Memory data panel (storage explainer)
|
||||
'memoryData.howItWorks': 'How memory storage works',
|
||||
'memoryData.workspaceVault': 'Workspace vault · write',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman writes generated memory notes to memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Connected sources · read',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'Folders, mailboxes, chats, and repos are imported for memory indexing — their original files are never rewritten.',
|
||||
'memoryData.internalFiles': 'Internal memory-tree files',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Indexes, queue state, and summaries are managed by OpenHuman to keep recall and sync healthy.',
|
||||
'memoryData.windowError': 'Memory window',
|
||||
'memoryData.windowUpdated': 'Memory window updated',
|
||||
'memoryData.windowUpdatedMsg': 'Set to {window}.',
|
||||
|
||||
// Graph
|
||||
'graph.noContactMentions': 'No contact mentions',
|
||||
'graph.noMemory': 'No memory',
|
||||
|
||||
@@ -4596,6 +4596,7 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Actividad',
|
||||
'onboarding.custom.stepperVault': 'Bóveda',
|
||||
'onboarding.custom.activity.title': 'Actividad del agente',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Qué tan proactivamente monitorea y actúa tu agente en segundo plano.',
|
||||
@@ -4603,6 +4604,69 @@ const messages: TranslationMap = {
|
||||
'Actividad moderada — sincronización por hora, resumen diario.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Elige tu propio nivel de actividad. Configurar en Ajustes › Nivel de actividad del agente.',
|
||||
|
||||
// Onboarding: Custom > Vault
|
||||
'onboarding.custom.vault.title': 'Configuración de memoria y bóveda',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Confirma dónde se escriben las notas de memoria, cómo se leen los datos de origen y si el flujo de bóveda está en buen estado.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Usa los valores predeterminados de memoria administrada por OpenHuman. La ruta de la bóveda y el estado de sincronización se pueden revisar más adelante.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Revisa la propiedad de la bóveda, ejecuta comprobaciones de estado y ajusta los controles de memoria ahora.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'La configuración administrada requiere inicio de sesión en OpenHuman y no está disponible en modo local.',
|
||||
'onboarding.custom.vault.exitError':
|
||||
'No se pudo completar el proceso de incorporación. Por favor, inténtalo de nuevo.',
|
||||
|
||||
// Vault Health
|
||||
'vaultHealth.title': 'Lista de verificación de salud de la bóveda',
|
||||
'vaultHealth.setupTitle': 'Estado de configuración de la bóveda',
|
||||
'vaultHealth.workspaceVault': 'Bóveda del espacio de trabajo:',
|
||||
'vaultHealth.refresh': 'Actualizar',
|
||||
'vaultHealth.refreshing': 'Actualizando…',
|
||||
'vaultHealth.revealFolder': 'Mostrar carpeta',
|
||||
'vaultHealth.openInObsidian': 'Abrir en Obsidian',
|
||||
'vaultHealth.installObsidian': 'Instalar Obsidian',
|
||||
'vaultHealth.openObsidianError': 'No se pudo abrir Obsidian',
|
||||
'vaultHealth.revealError': 'No se pudo mostrar la carpeta de la bóveda',
|
||||
'vaultHealth.downloadError': 'No se pudo abrir la página de descarga de Obsidian',
|
||||
'vaultHealth.loadError': 'No se pudo cargar el estado de la bóveda:',
|
||||
'vaultHealth.lastSync': 'Última sincronización:',
|
||||
'vaultHealth.passed': 'Superado',
|
||||
'vaultHealth.needsAttention': 'Requiere atención',
|
||||
'vaultHealth.existsLabel': 'La ruta de la bóveda del espacio de trabajo existe',
|
||||
'vaultHealth.existsRecovery':
|
||||
'La carpeta de la bóveda no existe. Inicia una sincronización o crea esta carpeta y luego actualiza esta lista.',
|
||||
'vaultHealth.writableLabel': 'OpenHuman puede escribir en la bóveda',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman aún no puede escribir en esta bóveda. Concede permisos de escritura y actualiza.',
|
||||
'vaultHealth.obsidianLabel': 'La bóveda está registrada en Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'En Obsidian, elige "Abrir carpeta como bóveda" para esta ruta y luego actualiza esta lista.',
|
||||
'vaultHealth.pipelineLabel': 'El flujo de memoria está en buen estado',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'El flujo de memoria está pausado o en error. Vuelve a habilitar la sincronización automática en el estado del árbol de memoria y reintenta.',
|
||||
'vaultHealth.timeNever': 'Nunca',
|
||||
'vaultHealth.timeJustNow': 'ahora mismo',
|
||||
'vaultHealth.timeMinAgo': 'hace {n} min',
|
||||
'vaultHealth.timeHrAgo': 'hace {n} h',
|
||||
'vaultHealth.timeDayAgo': 'hace {n} día',
|
||||
'vaultHealth.timeDaysAgo': 'hace {n} días',
|
||||
|
||||
// Memory Data
|
||||
'memoryData.howItWorks': 'Cómo funciona el almacenamiento de memoria',
|
||||
'memoryData.workspaceVault': 'Bóveda del espacio de trabajo · escritura',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman escribe las notas de memoria generadas en memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Fuentes conectadas · lectura',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'Carpetas, buzones, chats y repositorios se importan para la indexación de memoria; sus archivos originales nunca se reescriben.',
|
||||
'memoryData.internalFiles': 'Archivos internos del árbol de memoria',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Los índices, el estado de la cola y los resúmenes son administrados por OpenHuman para mantener en buen estado la recuperación y la sincronización.',
|
||||
'memoryData.windowError': 'Ventana de memoria',
|
||||
'memoryData.windowUpdated': 'Ventana de memoria actualizada',
|
||||
'memoryData.windowUpdatedMsg': 'Establecida en {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4613,6 +4613,7 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Activité',
|
||||
'onboarding.custom.stepperVault': 'Coffre',
|
||||
'onboarding.custom.activity.title': "Activité de l'agent",
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'À quel point votre agent surveille et agit en arrière-plan.',
|
||||
@@ -4620,6 +4621,68 @@ const messages: TranslationMap = {
|
||||
'Activité modérée — synchronisation horaire, résumé quotidien.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
"Choisissez votre propre niveau d'activité. Configurer dans Paramètres › Niveau d'activité de l'agent.",
|
||||
|
||||
// Onboarding: Custom > Vault
|
||||
'onboarding.custom.vault.title': 'Configuration de la mémoire et du coffre',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Confirmez où les notes de mémoire sont écrites, comment les données sources sont lues et si votre pipeline de coffre est en bon état.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
"Utiliser les paramètres de mémoire gérés par OpenHuman. Le chemin du coffre et l'état de synchronisation peuvent encore être consultés ultérieurement.",
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Vérifiez la propriété du coffre, exécutez des contrôles de santé et affinez les contrôles de mémoire maintenant.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
"La configuration gérée nécessite une connexion à OpenHuman et n'est pas disponible en mode local.",
|
||||
'onboarding.custom.vault.exitError': "Impossible de terminer l'intégration. Veuillez réessayer.",
|
||||
|
||||
// Vault Health
|
||||
'vaultHealth.title': 'Liste de contrôle de santé du coffre',
|
||||
'vaultHealth.setupTitle': 'Santé de la configuration du coffre',
|
||||
'vaultHealth.workspaceVault': "Coffre de l'espace de travail :",
|
||||
'vaultHealth.refresh': 'Actualiser',
|
||||
'vaultHealth.refreshing': 'Actualisation…',
|
||||
'vaultHealth.revealFolder': 'Afficher le dossier',
|
||||
'vaultHealth.openInObsidian': 'Ouvrir dans Obsidian',
|
||||
'vaultHealth.installObsidian': 'Installer Obsidian',
|
||||
'vaultHealth.openObsidianError': "Impossible d'ouvrir Obsidian",
|
||||
'vaultHealth.revealError': "Impossible d'afficher le dossier du coffre",
|
||||
'vaultHealth.downloadError': "Impossible d'ouvrir la page de téléchargement d'Obsidian",
|
||||
'vaultHealth.loadError': 'Impossible de charger la santé du coffre :',
|
||||
'vaultHealth.lastSync': 'Dernière synchronisation :',
|
||||
'vaultHealth.passed': 'Réussi',
|
||||
'vaultHealth.needsAttention': 'Attention requise',
|
||||
'vaultHealth.existsLabel': "Le chemin du coffre de l'espace de travail existe",
|
||||
'vaultHealth.existsRecovery':
|
||||
'Le dossier du coffre est manquant. Lancez une synchronisation ou créez ce dossier, puis actualisez cette liste de contrôle.',
|
||||
'vaultHealth.writableLabel': 'Le coffre est accessible en écriture par OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
"OpenHuman ne peut pas encore écrire dans ce coffre. Accordez les autorisations d'écriture et actualisez.",
|
||||
'vaultHealth.obsidianLabel': 'Le coffre est enregistré dans Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'Dans Obsidian, choisissez « Ouvrir le dossier comme coffre » pour ce chemin, puis actualisez cette liste de contrôle.',
|
||||
'vaultHealth.pipelineLabel': 'Le pipeline de mémoire est en bon état',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
"Le pipeline de mémoire est en pause ou en erreur. Réactivez la synchronisation automatique dans le statut de l'arbre de mémoire et réessayez.",
|
||||
'vaultHealth.timeNever': 'Jamais',
|
||||
'vaultHealth.timeJustNow': "à l'instant",
|
||||
'vaultHealth.timeMinAgo': 'il y a {n} min',
|
||||
'vaultHealth.timeHrAgo': 'il y a {n} h',
|
||||
'vaultHealth.timeDayAgo': 'il y a {n} jour',
|
||||
'vaultHealth.timeDaysAgo': 'il y a {n} jours',
|
||||
|
||||
// Memory Data
|
||||
'memoryData.howItWorks': 'Fonctionnement du stockage de la mémoire',
|
||||
'memoryData.workspaceVault': "Coffre de l'espace de travail · écriture",
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman écrit les notes de mémoire générées dans memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Sources connectées · lecture',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
"Les dossiers, boîtes mail, conversations et dépôts sont importés pour l'indexation de la mémoire — leurs fichiers originaux ne sont jamais réécrits.",
|
||||
'memoryData.internalFiles': "Fichiers internes de l'arbre de mémoire",
|
||||
'memoryData.internalFilesDesc':
|
||||
"Les index, l'état de la file d'attente et les résumés sont gérés par OpenHuman pour maintenir la mémoire et la synchronisation en bon état.",
|
||||
'memoryData.windowError': 'Fenêtre de mémoire',
|
||||
'memoryData.windowUpdated': 'Fenêtre de mémoire mise à jour',
|
||||
'memoryData.windowUpdatedMsg': 'Définie sur {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4517,12 +4517,69 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'गतिविधि',
|
||||
'onboarding.custom.stepperVault': 'वॉल्ट',
|
||||
'onboarding.custom.activity.title': 'एजेंट गतिविधि',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'आपका एजेंट पृष्ठभूमि में कितनी सक्रियता से निगरानी और कार्य करता है।',
|
||||
'onboarding.custom.activity.defaultDesc': 'मध्यम गतिविधि — प्रति घंटे सिंक, दैनिक सारांश।',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'अपना गतिविधि स्तर चुनें। सेटिंग्स › एजेंट गतिविधि स्तर में कॉन्फ़िगर करें।',
|
||||
'onboarding.custom.vault.title': 'मेमोरी और वॉल्ट सेटअप',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'पुष्टि करें कि मेमोरी नोट्स कहाँ लिखे जाते हैं, स्रोत डेटा कैसे पढ़ा जाता है, और आपका वॉल्ट पाइपलाइन स्वस्थ है या नहीं।',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'OpenHuman-प्रबंधित मेमोरी डिफ़ॉल्ट का उपयोग करें। वॉल्ट पाथ और सिंक स्वास्थ्य बाद में भी देखे जा सकते हैं।',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'वॉल्ट स्वामित्व की समीक्षा करें, स्वास्थ्य जाँच चलाएँ और अभी मेमोरी नियंत्रण ठीक करें।',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'प्रबंधित सेटअप के लिए OpenHuman साइन-इन आवश्यक है और लोकल मोड में उपलब्ध नहीं है।',
|
||||
'onboarding.custom.vault.exitError': 'ऑनबोर्डिंग पूरी नहीं हो सकी। कृपया पुनः प्रयास करें।',
|
||||
'vaultHealth.title': 'वॉल्ट स्वास्थ्य चेकलिस्ट',
|
||||
'vaultHealth.setupTitle': 'वॉल्ट सेटअप स्वास्थ्य',
|
||||
'vaultHealth.workspaceVault': 'वर्कस्पेस वॉल्ट:',
|
||||
'vaultHealth.refresh': 'रिफ्रेश',
|
||||
'vaultHealth.refreshing': 'रिफ्रेश हो रहा है…',
|
||||
'vaultHealth.revealFolder': 'फ़ोल्डर दिखाएँ',
|
||||
'vaultHealth.openInObsidian': 'Obsidian में खोलें',
|
||||
'vaultHealth.installObsidian': 'Obsidian इंस्टॉल करें',
|
||||
'vaultHealth.openObsidianError': 'Obsidian नहीं खोला जा सका',
|
||||
'vaultHealth.revealError': 'वॉल्ट फ़ोल्डर नहीं दिखाया जा सका',
|
||||
'vaultHealth.downloadError': 'Obsidian डाउनलोड पृष्ठ नहीं खोला जा सका',
|
||||
'vaultHealth.loadError': 'वॉल्ट स्वास्थ्य लोड नहीं हो सका:',
|
||||
'vaultHealth.lastSync': 'अंतिम सिंक:',
|
||||
'vaultHealth.passed': 'पास',
|
||||
'vaultHealth.needsAttention': 'ध्यान आवश्यक',
|
||||
'vaultHealth.existsLabel': 'वर्कस्पेस वॉल्ट पाथ मौजूद है',
|
||||
'vaultHealth.existsRecovery':
|
||||
'वॉल्ट फ़ोल्डर गायब है। एक सिंक शुरू करें या यह फ़ोल्डर बनाएँ, फिर इस चेकलिस्ट को रिफ्रेश करें।',
|
||||
'vaultHealth.writableLabel': 'वॉल्ट OpenHuman द्वारा लिखने योग्य है',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman अभी इस वॉल्ट में नहीं लिख सकता। लिखने की अनुमति दें और रिफ्रेश करें।',
|
||||
'vaultHealth.obsidianLabel': 'वॉल्ट Obsidian में पंजीकृत है',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'Obsidian में इस पाथ के लिए "फ़ोल्डर को वॉल्ट के रूप में खोलें" चुनें, फिर इस चेकलिस्ट को रिफ्रेश करें।',
|
||||
'vaultHealth.pipelineLabel': 'मेमोरी पाइपलाइन स्वस्थ है',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'मेमोरी पाइपलाइन रुकी हुई है या त्रुटि में है। मेमोरी ट्री स्थिति में ऑटो-सिंक पुनः सक्षम करें और पुनः प्रयास करें।',
|
||||
'vaultHealth.timeNever': 'कभी नहीं',
|
||||
'vaultHealth.timeJustNow': 'अभी-अभी',
|
||||
'vaultHealth.timeMinAgo': '{n} मिनट पहले',
|
||||
'vaultHealth.timeHrAgo': '{n} घंटे पहले',
|
||||
'vaultHealth.timeDayAgo': '{n} दिन पहले',
|
||||
'vaultHealth.timeDaysAgo': '{n} दिन पहले',
|
||||
'memoryData.howItWorks': 'मेमोरी स्टोरेज कैसे काम करता है',
|
||||
'memoryData.workspaceVault': 'वर्कस्पेस वॉल्ट · लिखना',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman जनरेट किए गए मेमोरी नोट्स को memory_tree/content में लिखता है।',
|
||||
'memoryData.connectedSources': 'कनेक्टेड स्रोत · पढ़ना',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'फ़ोल्डर, मेलबॉक्स, चैट और रेपो मेमोरी इंडेक्सिंग के लिए आयात किए जाते हैं — उनकी मूल फ़ाइलें कभी नहीं बदली जातीं।',
|
||||
'memoryData.internalFiles': 'आंतरिक मेमोरी-ट्रि फ़ाइलें',
|
||||
'memoryData.internalFilesDesc':
|
||||
'इंडेक्स, क्यू स्थिति और सारांश OpenHuman द्वारा प्रबंधित किए जाते हैं ताकि रिकॉल और सिंक स्वस्थ रहे।',
|
||||
'memoryData.windowError': 'मेमोरी विंडो',
|
||||
'memoryData.windowUpdated': 'मेमोरी विंडो अपडेट हुई',
|
||||
'memoryData.windowUpdatedMsg': '{window} पर सेट किया गया।',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4530,6 +4530,7 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Aktivitas',
|
||||
'onboarding.custom.stepperVault': 'Vault',
|
||||
'onboarding.custom.activity.title': 'Aktivitas agen',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Seberapa proaktif agen Anda memantau dan bertindak di latar belakang.',
|
||||
@@ -4537,6 +4538,62 @@ const messages: TranslationMap = {
|
||||
'Aktivitas sedang — sinkronisasi per jam, ringkasan harian.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Pilih tingkat aktivitas Anda sendiri. Konfigurasi di Pengaturan › Tingkat aktivitas agen.',
|
||||
'onboarding.custom.vault.title': 'Pengaturan Memori & Vault',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Konfirmasi di mana catatan memori ditulis, bagaimana data sumber dibaca, dan apakah pipeline vault Anda sehat.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Gunakan default memori yang dikelola OpenHuman. Jalur vault dan kondisi sinkronisasi tetap dapat ditinjau nanti.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Tinjau kepemilikan vault, jalankan pemeriksaan kesehatan, dan sesuaikan kontrol memori sekarang.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'Pengaturan terkelola memerlukan masuk OpenHuman dan tidak tersedia dalam mode lokal.',
|
||||
'onboarding.custom.vault.exitError': 'Tidak dapat menyelesaikan orientasi. Silakan coba lagi.',
|
||||
'vaultHealth.title': 'Daftar Periksa Kesehatan Vault',
|
||||
'vaultHealth.setupTitle': 'Kesehatan pengaturan vault',
|
||||
'vaultHealth.workspaceVault': 'Vault ruang kerja:',
|
||||
'vaultHealth.refresh': 'Segarkan',
|
||||
'vaultHealth.refreshing': 'Menyegarkan…',
|
||||
'vaultHealth.revealFolder': 'Tampilkan Folder',
|
||||
'vaultHealth.openInObsidian': 'Buka di Obsidian',
|
||||
'vaultHealth.installObsidian': 'Pasang Obsidian',
|
||||
'vaultHealth.openObsidianError': 'Tidak dapat membuka Obsidian',
|
||||
'vaultHealth.revealError': 'Tidak dapat menampilkan folder vault',
|
||||
'vaultHealth.downloadError': 'Tidak dapat membuka halaman unduhan Obsidian',
|
||||
'vaultHealth.loadError': 'Tidak dapat memuat kesehatan vault:',
|
||||
'vaultHealth.lastSync': 'Sinkronisasi terakhir:',
|
||||
'vaultHealth.passed': 'Lulus',
|
||||
'vaultHealth.needsAttention': 'Perlu perhatian',
|
||||
'vaultHealth.existsLabel': 'Jalur vault ruang kerja ada',
|
||||
'vaultHealth.existsRecovery':
|
||||
'Folder vault tidak ditemukan. Mulai sinkronisasi atau buat folder ini, lalu segarkan daftar periksa ini.',
|
||||
'vaultHealth.writableLabel': 'Vault dapat ditulis oleh OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman belum dapat menulis ke vault ini. Berikan izin tulis dan segarkan.',
|
||||
'vaultHealth.obsidianLabel': 'Vault terdaftar di Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'Di Obsidian, pilih "Buka folder sebagai vault" untuk jalur ini, lalu segarkan daftar periksa ini.',
|
||||
'vaultHealth.pipelineLabel': 'Pipeline memori sehat',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'Pipeline memori dijeda atau mengalami kesalahan. Aktifkan kembali Sinkronisasi Otomatis di status Pohon Memori dan coba lagi.',
|
||||
'vaultHealth.timeNever': 'Tidak pernah',
|
||||
'vaultHealth.timeJustNow': 'baru saja',
|
||||
'vaultHealth.timeMinAgo': '{n} menit lalu',
|
||||
'vaultHealth.timeHrAgo': '{n} jam lalu',
|
||||
'vaultHealth.timeDayAgo': '{n} hari lalu',
|
||||
'vaultHealth.timeDaysAgo': '{n} hari lalu',
|
||||
'memoryData.howItWorks': 'Cara kerja penyimpanan memori',
|
||||
'memoryData.workspaceVault': 'Vault ruang kerja · tulis',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman menulis catatan memori yang dihasilkan ke memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Sumber terhubung · baca',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'Folder, kotak surat, obrolan, dan repositori diimpor untuk pengindeksan memori — file aslinya tidak pernah ditulis ulang.',
|
||||
'memoryData.internalFiles': 'File pohon memori internal',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Indeks, status antrean, dan ringkasan dikelola oleh OpenHuman agar pemanggilan dan sinkronisasi tetap sehat.',
|
||||
'memoryData.windowError': 'Jendela memori',
|
||||
'memoryData.windowUpdated': 'Jendela memori diperbarui',
|
||||
'memoryData.windowUpdatedMsg': 'Diatur ke {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4590,6 +4590,7 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Attività',
|
||||
'onboarding.custom.stepperVault': 'Vault',
|
||||
'onboarding.custom.activity.title': "Attività dell'agente",
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Quanto proattivamente il tuo agente monitora e agisce in background.',
|
||||
@@ -4597,6 +4598,68 @@ const messages: TranslationMap = {
|
||||
'Attività moderata — sincronizzazione oraria, riepilogo giornaliero.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
"Scegli il tuo livello di attività. Configura in Impostazioni › Livello di attività dell'agente.",
|
||||
|
||||
// Onboarding: Custom > Vault
|
||||
'onboarding.custom.vault.title': 'Configurazione memoria e Vault',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Conferma dove vengono scritte le note di memoria, come vengono letti i dati sorgente e se la pipeline del vault è integra.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Usa le impostazioni di memoria predefinite gestite da OpenHuman. Il percorso del vault e la salute della sincronizzazione possono essere verificati in seguito.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Verifica la proprietà del vault, esegui controlli di integrità e ottimizza i controlli della memoria ora.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'La configurazione gestita richiede il login a OpenHuman e non è disponibile in modalità locale.',
|
||||
'onboarding.custom.vault.exitError': "Impossibile completare l'onboarding. Riprova.",
|
||||
|
||||
// Vault Health
|
||||
'vaultHealth.title': 'Checklist integrità vault',
|
||||
'vaultHealth.setupTitle': 'Integrità configurazione vault',
|
||||
'vaultHealth.workspaceVault': 'Vault workspace:',
|
||||
'vaultHealth.refresh': 'Aggiorna',
|
||||
'vaultHealth.refreshing': 'Aggiornamento in corso…',
|
||||
'vaultHealth.revealFolder': 'Mostra cartella',
|
||||
'vaultHealth.openInObsidian': 'Apri in Obsidian',
|
||||
'vaultHealth.installObsidian': 'Installa Obsidian',
|
||||
'vaultHealth.openObsidianError': 'Impossibile aprire Obsidian',
|
||||
'vaultHealth.revealError': 'Impossibile mostrare la cartella del vault',
|
||||
'vaultHealth.downloadError': 'Impossibile aprire la pagina di download di Obsidian',
|
||||
'vaultHealth.loadError': "Impossibile caricare l'integrità del vault:",
|
||||
'vaultHealth.lastSync': 'Ultima sincronizzazione:',
|
||||
'vaultHealth.passed': 'Superato',
|
||||
'vaultHealth.needsAttention': 'Richiede attenzione',
|
||||
'vaultHealth.existsLabel': 'Il percorso del vault workspace esiste',
|
||||
'vaultHealth.existsRecovery':
|
||||
'La cartella del vault è assente. Avvia una sincronizzazione o crea questa cartella, poi aggiorna la checklist.',
|
||||
'vaultHealth.writableLabel': 'Il vault è scrivibile da OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman non riesce ancora a scrivere in questo vault. Concedi i permessi di scrittura e aggiorna.',
|
||||
'vaultHealth.obsidianLabel': 'Il vault è registrato in Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'In Obsidian, scegli "Apri cartella come vault" per questo percorso, poi aggiorna la checklist.',
|
||||
'vaultHealth.pipelineLabel': 'La pipeline di memoria è integra',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'La pipeline di memoria è in pausa o in errore. Riattiva la sincronizzazione automatica nello stato del Memory Tree e riprova.',
|
||||
'vaultHealth.timeNever': 'Mai',
|
||||
'vaultHealth.timeJustNow': 'poco fa',
|
||||
'vaultHealth.timeMinAgo': '{n} min fa',
|
||||
'vaultHealth.timeHrAgo': '{n} ora fa',
|
||||
'vaultHealth.timeDayAgo': '{n} giorno fa',
|
||||
'vaultHealth.timeDaysAgo': '{n} giorni fa',
|
||||
|
||||
// Memory Data
|
||||
'memoryData.howItWorks': 'Come funziona la memorizzazione',
|
||||
'memoryData.workspaceVault': 'Vault workspace · scrittura',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman scrive le note di memoria generate in memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Fonti collegate · lettura',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
"Cartelle, caselle di posta, chat e repository vengono importati per l'indicizzazione della memoria — i file originali non vengono mai riscritti.",
|
||||
'memoryData.internalFiles': 'File interni del memory-tree',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Indici, stato della coda e riepiloghi sono gestiti da OpenHuman per mantenere efficiente il richiamo e la sincronizzazione.',
|
||||
'memoryData.windowError': 'Finestra di memoria',
|
||||
'memoryData.windowUpdated': 'Finestra di memoria aggiornata',
|
||||
'memoryData.windowUpdatedMsg': 'Impostata su {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4473,12 +4473,69 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': '활동',
|
||||
'onboarding.custom.stepperVault': '볼트',
|
||||
'onboarding.custom.activity.title': '에이전트 활동',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'에이전트가 백그라운드에서 얼마나 능동적으로 모니터링하고 행동하는지.',
|
||||
'onboarding.custom.activity.defaultDesc': '보통 활동 — 매시간 동기화, 일일 요약.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'자신만의 활동 수준을 선택하세요. 설정 › 에이전트 활동 수준에서 구성하세요.',
|
||||
'onboarding.custom.vault.title': '메모리 및 볼트 설정',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'메모리 노트가 기록되는 위치, 소스 데이터를 읽는 방법, 볼트 파이프라인이 정상인지 확인하세요.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'OpenHuman 관리형 메모리 기본값을 사용합니다. 볼트 경로와 동기화 상태는 나중에 검토할 수 있습니다.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'볼트 소유권을 검토하고 상태 검사를 실행하며 메모리 컨트롤을 지금 조정하세요.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'관리형 설정은 OpenHuman 로그인이 필요하며 로컬 모드에서는 사용할 수 없습니다.',
|
||||
'onboarding.custom.vault.exitError': '온보딩을 완료할 수 없습니다. 다시 시도해 주세요.',
|
||||
'vaultHealth.title': '볼트 상태 체크리스트',
|
||||
'vaultHealth.setupTitle': '볼트 설정 상태',
|
||||
'vaultHealth.workspaceVault': '워크스페이스 볼트:',
|
||||
'vaultHealth.refresh': '새로 고침',
|
||||
'vaultHealth.refreshing': '새로 고침 중…',
|
||||
'vaultHealth.revealFolder': '폴더 표시',
|
||||
'vaultHealth.openInObsidian': 'Obsidian에서 열기',
|
||||
'vaultHealth.installObsidian': 'Obsidian 설치',
|
||||
'vaultHealth.openObsidianError': 'Obsidian을 열 수 없습니다',
|
||||
'vaultHealth.revealError': '볼트 폴더를 표시할 수 없습니다',
|
||||
'vaultHealth.downloadError': 'Obsidian 다운로드 페이지를 열 수 없습니다',
|
||||
'vaultHealth.loadError': '볼트 상태를 불러올 수 없습니다:',
|
||||
'vaultHealth.lastSync': '마지막 동기화:',
|
||||
'vaultHealth.passed': '통과',
|
||||
'vaultHealth.needsAttention': '주의 필요',
|
||||
'vaultHealth.existsLabel': '워크스페이스 볼트 경로가 존재합니다',
|
||||
'vaultHealth.existsRecovery':
|
||||
'볼트 폴더가 없습니다. 동기화를 시작하거나 이 폴더를 만든 후 체크리스트를 새로 고침하세요.',
|
||||
'vaultHealth.writableLabel': 'OpenHuman이 볼트에 쓸 수 있습니다',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman이 아직 이 볼트에 쓸 수 없습니다. 쓰기 권한을 부여하고 새로 고침하세요.',
|
||||
'vaultHealth.obsidianLabel': '볼트가 Obsidian에 등록되어 있습니다',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'Obsidian에서 이 경로에 대해 "폴더를 볼트로 열기"를 선택한 후 체크리스트를 새로 고침하세요.',
|
||||
'vaultHealth.pipelineLabel': '메모리 파이프라인이 정상입니다',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'메모리 파이프라인이 일시 중지되었거나 오류 상태입니다. 메모리 트리 상태에서 자동 동기화를 다시 활성화하고 재시도하세요.',
|
||||
'vaultHealth.timeNever': '없음',
|
||||
'vaultHealth.timeJustNow': '방금 전',
|
||||
'vaultHealth.timeMinAgo': '{n}분 전',
|
||||
'vaultHealth.timeHrAgo': '{n}시간 전',
|
||||
'vaultHealth.timeDayAgo': '{n}일 전',
|
||||
'vaultHealth.timeDaysAgo': '{n}일 전',
|
||||
'memoryData.howItWorks': '메모리 저장 방식',
|
||||
'memoryData.workspaceVault': '워크스페이스 볼트 · 쓰기',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman이 생성된 메모리 노트를 memory_tree/content에 기록합니다.',
|
||||
'memoryData.connectedSources': '연결된 소스 · 읽기',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'폴더, 사서함, 채팅, 저장소가 메모리 인덱싱을 위해 가져와집니다 — 원본 파일은 절대 수정되지 않습니다.',
|
||||
'memoryData.internalFiles': '내부 메모리 트리 파일',
|
||||
'memoryData.internalFilesDesc':
|
||||
'인덱스, 큐 상태, 요약은 OpenHuman이 관리하여 회상과 동기화를 정상 상태로 유지합니다.',
|
||||
'memoryData.windowError': '메모리 창',
|
||||
'memoryData.windowUpdated': '메모리 창 업데이트됨',
|
||||
'memoryData.windowUpdatedMsg': '{window}(으)로 설정되었습니다.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4587,12 +4587,69 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Aktywność',
|
||||
'onboarding.custom.stepperVault': 'Skarbiec',
|
||||
'onboarding.custom.activity.title': 'Aktywność agenta',
|
||||
'onboarding.custom.activity.subtitle': 'Jak proaktywnie Twój agent monitoruje i działa w tle.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'Umiarkowana aktywność — synchronizacja co godzinę, codzienny skrót.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Wybierz własny poziom aktywności. Skonfiguruj w Ustawieniach › Poziom aktywności agenta.',
|
||||
'onboarding.custom.vault.title': 'Konfiguracja pamięci i skarbca',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Potwierdź, gdzie zapisywane są notatki pamięci, jak odczytywane są dane źródłowe i czy potok skarbca działa poprawnie.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Użyj domyślnych ustawień pamięci zarządzanej przez OpenHuman. Ścieżkę skarbca i stan synchronizacji można sprawdzić później.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Sprawdź właściciela skarbca, uruchom kontrole stanu i dostosuj teraz ustawienia pamięci.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'Konfiguracja zarządzana wymaga logowania do OpenHuman i jest niedostępna w trybie lokalnym.',
|
||||
'onboarding.custom.vault.exitError': 'Nie udało się zakończyć wdrożenia. Spróbuj ponownie.',
|
||||
'vaultHealth.title': 'Lista kontrolna stanu skarbca',
|
||||
'vaultHealth.setupTitle': 'Stan konfiguracji skarbca',
|
||||
'vaultHealth.workspaceVault': 'Skarbiec obszaru roboczego:',
|
||||
'vaultHealth.refresh': 'Odśwież',
|
||||
'vaultHealth.refreshing': 'Odświeżanie…',
|
||||
'vaultHealth.revealFolder': 'Pokaż folder',
|
||||
'vaultHealth.openInObsidian': 'Otwórz w Obsidian',
|
||||
'vaultHealth.installObsidian': 'Zainstaluj Obsidian',
|
||||
'vaultHealth.openObsidianError': 'Nie można otworzyć Obsidian',
|
||||
'vaultHealth.revealError': 'Nie można wyświetlić folderu skarbca',
|
||||
'vaultHealth.downloadError': 'Nie można otworzyć strony pobierania Obsidian',
|
||||
'vaultHealth.loadError': 'Nie można załadować stanu skarbca:',
|
||||
'vaultHealth.lastSync': 'Ostatnia synchronizacja:',
|
||||
'vaultHealth.passed': 'Zaliczone',
|
||||
'vaultHealth.needsAttention': 'Wymaga uwagi',
|
||||
'vaultHealth.existsLabel': 'Ścieżka skarbca obszaru roboczego istnieje',
|
||||
'vaultHealth.existsRecovery':
|
||||
'Folder skarbca jest niedostępny. Rozpocznij synchronizację lub utwórz ten folder, a następnie odśwież listę kontrolną.',
|
||||
'vaultHealth.writableLabel': 'Skarbiec jest zapisywalny przez OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman nie może jeszcze zapisywać do tego skarbca. Nadaj uprawnienia do zapisu i odśwież.',
|
||||
'vaultHealth.obsidianLabel': 'Skarbiec jest zarejestrowany w Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'W Obsidian wybierz „Otwórz folder jako skarbiec" dla tej ścieżki, a następnie odśwież listę kontrolną.',
|
||||
'vaultHealth.pipelineLabel': 'Potok pamięci działa poprawnie',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'Potok pamięci jest wstrzymany lub w stanie błędu. Włącz ponownie automatyczną synchronizację w statusie drzewa pamięci i spróbuj ponownie.',
|
||||
'vaultHealth.timeNever': 'Nigdy',
|
||||
'vaultHealth.timeJustNow': 'przed chwilą',
|
||||
'vaultHealth.timeMinAgo': '{n} min temu',
|
||||
'vaultHealth.timeHrAgo': '{n} godz. temu',
|
||||
'vaultHealth.timeDayAgo': '{n} dzień temu',
|
||||
'vaultHealth.timeDaysAgo': '{n} dni temu',
|
||||
'memoryData.howItWorks': 'Jak działa przechowywanie pamięci',
|
||||
'memoryData.workspaceVault': 'Skarbiec obszaru roboczego · zapis',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman zapisuje wygenerowane notatki pamięci do memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Połączone źródła · odczyt',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'Foldery, skrzynki pocztowe, czaty i repozytoria są importowane do indeksowania pamięci — ich oryginalne pliki nigdy nie są nadpisywane.',
|
||||
'memoryData.internalFiles': 'Wewnętrzne pliki drzewa pamięci',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Indeksy, stan kolejki i podsumowania są zarządzane przez OpenHuman, aby utrzymać poprawne działanie przywołania i synchronizacji.',
|
||||
'memoryData.windowError': 'Okno pamięci',
|
||||
'memoryData.windowUpdated': 'Okno pamięci zaktualizowane',
|
||||
'memoryData.windowUpdatedMsg': 'Ustawiono na {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4592,6 +4592,70 @@ const messages: TranslationMap = {
|
||||
'Atividade moderada — sincronização por hora, resumo diário.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Escolha seu próprio nível de atividade. Configurar em Configurações › Nível de atividade do agente.',
|
||||
|
||||
// Onboarding: Custom > Vault
|
||||
'onboarding.custom.stepperVault': 'Vault',
|
||||
'onboarding.custom.vault.title': 'Configuração de Memória e Vault',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Confirme onde as notas de memória são gravadas, como os dados de origem são lidos e se o pipeline do vault está saudável.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Use os padrões de memória gerenciados pelo OpenHuman. O caminho do vault e a integridade da sincronização ainda podem ser revisados depois.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Revise a propriedade do vault, execute verificações de integridade e ajuste os controles de memória agora.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'A configuração gerenciada requer login no OpenHuman e não está disponível no modo local.',
|
||||
'onboarding.custom.vault.exitError':
|
||||
'Não foi possível concluir o processo de integração. Por favor, tente novamente.',
|
||||
|
||||
// Vault Health
|
||||
'vaultHealth.title': 'Checklist de integridade do vault',
|
||||
'vaultHealth.setupTitle': 'Integridade da configuração do vault',
|
||||
'vaultHealth.workspaceVault': 'Vault do espaço de trabalho:',
|
||||
'vaultHealth.refresh': 'Atualizar',
|
||||
'vaultHealth.refreshing': 'Atualizando…',
|
||||
'vaultHealth.revealFolder': 'Revelar pasta',
|
||||
'vaultHealth.openInObsidian': 'Abrir no Obsidian',
|
||||
'vaultHealth.installObsidian': 'Instalar Obsidian',
|
||||
'vaultHealth.openObsidianError': 'Não foi possível abrir o Obsidian',
|
||||
'vaultHealth.revealError': 'Não foi possível revelar a pasta do vault',
|
||||
'vaultHealth.downloadError': 'Não foi possível abrir a página de download do Obsidian',
|
||||
'vaultHealth.loadError': 'Não foi possível carregar a integridade do vault:',
|
||||
'vaultHealth.lastSync': 'Última sincronização:',
|
||||
'vaultHealth.passed': 'Aprovado',
|
||||
'vaultHealth.needsAttention': 'Requer atenção',
|
||||
'vaultHealth.existsLabel': 'O caminho do vault do espaço de trabalho existe',
|
||||
'vaultHealth.existsRecovery':
|
||||
'A pasta do vault está ausente. Inicie uma sincronização ou crie esta pasta e atualize este checklist.',
|
||||
'vaultHealth.writableLabel': 'O vault pode ser gravado pelo OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
'O OpenHuman ainda não consegue gravar neste vault. Conceda permissões de escrita e atualize.',
|
||||
'vaultHealth.obsidianLabel': 'O vault está registrado no Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'No Obsidian, escolha "Abrir pasta como vault" para este caminho e atualize este checklist.',
|
||||
'vaultHealth.pipelineLabel': 'O pipeline de memória está saudável',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'O pipeline de memória está pausado ou com erro. Reative a Sincronização automática no status da Árvore de Memória e tente novamente.',
|
||||
'vaultHealth.timeNever': 'Nunca',
|
||||
'vaultHealth.timeJustNow': 'agora mesmo',
|
||||
'vaultHealth.timeMinAgo': 'há {n} min',
|
||||
'vaultHealth.timeHrAgo': 'há {n} h',
|
||||
'vaultHealth.timeDayAgo': 'há {n} dia',
|
||||
'vaultHealth.timeDaysAgo': 'há {n} dias',
|
||||
|
||||
// Memory Data
|
||||
'memoryData.howItWorks': 'Como o armazenamento de memória funciona',
|
||||
'memoryData.workspaceVault': 'Vault do espaço de trabalho · gravação',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'O OpenHuman grava as notas de memória geradas em memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Fontes conectadas · leitura',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'Pastas, caixas de correio, chats e repositórios são importados para indexação de memória — seus arquivos originais nunca são reescritos.',
|
||||
'memoryData.internalFiles': 'Arquivos internos da árvore de memória',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Índices, estado da fila e resumos são gerenciados pelo OpenHuman para manter a recuperação e a sincronização saudáveis.',
|
||||
'memoryData.windowError': 'Janela de memória',
|
||||
'memoryData.windowUpdated': 'Janela de memória atualizada',
|
||||
'memoryData.windowUpdatedMsg': 'Definida para {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4556,6 +4556,7 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Активность',
|
||||
'onboarding.custom.stepperVault': 'Хранилище',
|
||||
'onboarding.custom.activity.title': 'Активность агента',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Насколько проактивно агент отслеживает события и действует в фоне.',
|
||||
@@ -4563,6 +4564,63 @@ const messages: TranslationMap = {
|
||||
'Умеренная активность — синхронизация каждый час, ежедневная сводка.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Выберите свой уровень активности. Настройка в Параметры › Уровень активности агента.',
|
||||
'onboarding.custom.vault.title': 'Настройка памяти и хранилища',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'Подтвердите, куда записываются заметки памяти, как считываются исходные данные и исправно ли работает конвейер хранилища.',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'Использовать настройки памяти по умолчанию, управляемые OpenHuman. Путь к хранилищу и состояние синхронизации можно проверить позже.',
|
||||
'onboarding.custom.vault.configureDesc':
|
||||
'Проверьте владельца хранилища, выполните проверки состояния и настройте параметры памяти прямо сейчас.',
|
||||
'onboarding.custom.vault.localDisabledReason':
|
||||
'Управляемая настройка требует входа в OpenHuman и недоступна в локальном режиме.',
|
||||
'onboarding.custom.vault.exitError':
|
||||
'Не удалось завершить настройку. Пожалуйста, попробуйте ещё раз.',
|
||||
'vaultHealth.title': 'Контрольный список состояния хранилища',
|
||||
'vaultHealth.setupTitle': 'Состояние настройки хранилища',
|
||||
'vaultHealth.workspaceVault': 'Хранилище рабочего пространства:',
|
||||
'vaultHealth.refresh': 'Обновить',
|
||||
'vaultHealth.refreshing': 'Обновление…',
|
||||
'vaultHealth.revealFolder': 'Показать папку',
|
||||
'vaultHealth.openInObsidian': 'Открыть в Obsidian',
|
||||
'vaultHealth.installObsidian': 'Установить Obsidian',
|
||||
'vaultHealth.openObsidianError': 'Не удалось открыть Obsidian',
|
||||
'vaultHealth.revealError': 'Не удалось показать папку хранилища',
|
||||
'vaultHealth.downloadError': 'Не удалось открыть страницу загрузки Obsidian',
|
||||
'vaultHealth.loadError': 'Не удалось загрузить состояние хранилища:',
|
||||
'vaultHealth.lastSync': 'Последняя синхронизация:',
|
||||
'vaultHealth.passed': 'Пройдено',
|
||||
'vaultHealth.needsAttention': 'Требует внимания',
|
||||
'vaultHealth.existsLabel': 'Путь к хранилищу рабочего пространства существует',
|
||||
'vaultHealth.existsRecovery':
|
||||
'Папка хранилища отсутствует. Запустите синхронизацию или создайте эту папку, затем обновите контрольный список.',
|
||||
'vaultHealth.writableLabel': 'Хранилище доступно для записи OpenHuman',
|
||||
'vaultHealth.writableRecovery':
|
||||
'OpenHuman пока не может записывать в это хранилище. Предоставьте права на запись и обновите страницу.',
|
||||
'vaultHealth.obsidianLabel': 'Хранилище зарегистрировано в Obsidian',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'В Obsidian выберите «Открыть папку как хранилище» для этого пути, затем обновите контрольный список.',
|
||||
'vaultHealth.pipelineLabel': 'Конвейер памяти работает исправно',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'Конвейер памяти приостановлен или находится в состоянии ошибки. Повторно включите автосинхронизацию в статусе дерева памяти и повторите попытку.',
|
||||
'vaultHealth.timeNever': 'Никогда',
|
||||
'vaultHealth.timeJustNow': 'только что',
|
||||
'vaultHealth.timeMinAgo': '{n} мин назад',
|
||||
'vaultHealth.timeHrAgo': '{n} ч назад',
|
||||
'vaultHealth.timeDayAgo': '{n} день назад',
|
||||
'vaultHealth.timeDaysAgo': '{n} дн назад',
|
||||
'memoryData.howItWorks': 'Как работает хранение памяти',
|
||||
'memoryData.workspaceVault': 'Хранилище рабочего пространства · запись',
|
||||
'memoryData.workspaceVaultDesc':
|
||||
'OpenHuman записывает сгенерированные заметки памяти в memory_tree/content.',
|
||||
'memoryData.connectedSources': 'Подключённые источники · чтение',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'Папки, почтовые ящики, чаты и репозитории импортируются для индексирования памяти — исходные файлы никогда не перезаписываются.',
|
||||
'memoryData.internalFiles': 'Внутренние файлы дерева памяти',
|
||||
'memoryData.internalFilesDesc':
|
||||
'Индексы, состояние очереди и сводки управляются OpenHuman для поддержания исправного восстановления и синхронизации.',
|
||||
'memoryData.windowError': 'Окно памяти',
|
||||
'memoryData.windowUpdated': 'Окно памяти обновлено',
|
||||
'memoryData.windowUpdatedMsg': 'Установлено значение {window}.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4293,11 +4293,69 @@ const messages: TranslationMap = {
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': '活动',
|
||||
'onboarding.custom.stepperVault': '保险库',
|
||||
'onboarding.custom.activity.title': '智能体活动',
|
||||
'onboarding.custom.activity.subtitle': '您的智能体在后台监控和行动的主动程度。',
|
||||
'onboarding.custom.activity.defaultDesc': '适中活动——每小时同步,每日摘要。',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'选择您自己的活动级别。在设置 › 智能体活动级别中配置。',
|
||||
|
||||
// Onboarding: Custom > Vault
|
||||
'onboarding.custom.vault.title': '记忆与保险库设置',
|
||||
'onboarding.custom.vault.subtitle':
|
||||
'确认记忆笔记的写入位置、源数据的读取方式,以及保险库管道是否正常运行。',
|
||||
'onboarding.custom.vault.defaultDesc':
|
||||
'使用 OpenHuman 托管的记忆默认设置。保险库路径和同步健康状态仍可稍后查看。',
|
||||
'onboarding.custom.vault.configureDesc': '立即查看保险库所有权、运行健康检查并调整记忆控制。',
|
||||
'onboarding.custom.vault.localDisabledReason': '托管设置需要登录 OpenHuman,在本地模式下不可用。',
|
||||
'onboarding.custom.vault.exitError': '无法完成引导流程,请重试。',
|
||||
|
||||
// Vault Health
|
||||
'vaultHealth.title': '保险库健康检查清单',
|
||||
'vaultHealth.setupTitle': '保险库设置健康状态',
|
||||
'vaultHealth.workspaceVault': '工作区保险库:',
|
||||
'vaultHealth.refresh': '刷新',
|
||||
'vaultHealth.refreshing': '刷新中…',
|
||||
'vaultHealth.revealFolder': '显示文件夹',
|
||||
'vaultHealth.openInObsidian': '在 Obsidian 中打开',
|
||||
'vaultHealth.installObsidian': '安装 Obsidian',
|
||||
'vaultHealth.openObsidianError': '无法打开 Obsidian',
|
||||
'vaultHealth.revealError': '无法显示保险库文件夹',
|
||||
'vaultHealth.downloadError': '无法打开 Obsidian 下载页面',
|
||||
'vaultHealth.loadError': '无法加载保险库健康状态:',
|
||||
'vaultHealth.lastSync': '上次同步:',
|
||||
'vaultHealth.passed': '通过',
|
||||
'vaultHealth.needsAttention': '需要关注',
|
||||
'vaultHealth.existsLabel': '工作区保险库路径存在',
|
||||
'vaultHealth.existsRecovery': '保险库文件夹缺失。请启动同步或创建该文件夹,然后刷新此检查清单。',
|
||||
'vaultHealth.writableLabel': 'OpenHuman 可写入保险库',
|
||||
'vaultHealth.writableRecovery': 'OpenHuman 暂时无法写入此保险库。请授予写入权限后刷新。',
|
||||
'vaultHealth.obsidianLabel': '保险库已在 Obsidian 中注册',
|
||||
'vaultHealth.obsidianRecovery':
|
||||
'请在 Obsidian 中选择"将文件夹作为保险库打开",然后刷新此检查清单。',
|
||||
'vaultHealth.pipelineLabel': '记忆管道运行正常',
|
||||
'vaultHealth.pipelineRecovery':
|
||||
'记忆管道已暂停或出现错误。请在记忆树状态中重新启用自动同步并重试。',
|
||||
'vaultHealth.timeNever': '从未',
|
||||
'vaultHealth.timeJustNow': '刚刚',
|
||||
'vaultHealth.timeMinAgo': '{n} 分钟前',
|
||||
'vaultHealth.timeHrAgo': '{n} 小时前',
|
||||
'vaultHealth.timeDayAgo': '{n} 天前',
|
||||
'vaultHealth.timeDaysAgo': '{n} 天前',
|
||||
|
||||
// Memory Data
|
||||
'memoryData.howItWorks': '记忆存储原理',
|
||||
'memoryData.workspaceVault': '工作区保险库 · 写入',
|
||||
'memoryData.workspaceVaultDesc': 'OpenHuman 将生成的记忆笔记写入 memory_tree/content。',
|
||||
'memoryData.connectedSources': '已连接来源 · 读取',
|
||||
'memoryData.connectedSourcesDesc':
|
||||
'文件夹、邮箱、聊天记录和代码仓库会导入用于记忆索引——其原始文件不会被改写。',
|
||||
'memoryData.internalFiles': '内部记忆树文件',
|
||||
'memoryData.internalFilesDesc':
|
||||
'索引、队列状态和摘要由 OpenHuman 管理,以保持召回和同步的正常运行。',
|
||||
'memoryData.windowError': '记忆时间窗口',
|
||||
'memoryData.windowUpdated': '记忆时间窗口已更新',
|
||||
'memoryData.windowUpdatedMsg': '已设置为 {window}。',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4,13 +4,11 @@ import OnboardingLayout from './OnboardingLayout';
|
||||
import CustomActivityPage from './pages/CustomActivityPage';
|
||||
import CustomEmbeddingsPage from './pages/CustomEmbeddingsPage';
|
||||
import CustomInferencePage from './pages/CustomInferencePage';
|
||||
// Memory step is hidden from the flow for now (file kept on disk;
|
||||
// uncomment alongside customWizardSteps.ts to re-enable):
|
||||
// import CustomMemoryPage from './pages/CustomMemoryPage';
|
||||
import CustomOAuthPage from './pages/CustomOAuthPage';
|
||||
import CustomSearchPage from './pages/CustomSearchPage';
|
||||
import CustomVoicePage from './pages/CustomVoicePage';
|
||||
import RuntimeChoicePage from './pages/RuntimeChoicePage';
|
||||
import VaultSetupStep from './pages/VaultSetupStep';
|
||||
import WelcomePage from './pages/WelcomePage';
|
||||
|
||||
/**
|
||||
@@ -18,7 +16,7 @@ import WelcomePage from './pages/WelcomePage';
|
||||
*
|
||||
* welcome → runtime-choice
|
||||
* ├── cloud → /home
|
||||
* └── custom → /custom/inference → voice → oauth → search → embeddings → memory → /home
|
||||
* └── custom → /custom/inference → voice → oauth → search → embeddings → vault → /home
|
||||
*
|
||||
* Each custom step asks Default (let OpenHuman manage it) vs Configure
|
||||
* (let me pick). Default is a one-click pick; Configure renders inline
|
||||
@@ -41,6 +39,7 @@ const Onboarding = () => {
|
||||
<Route path="custom/search" element={<CustomSearchPage />} />
|
||||
<Route path="custom/embeddings" element={<CustomEmbeddingsPage />} />
|
||||
<Route path="custom/activity" element={<CustomActivityPage />} />
|
||||
<Route path="custom/vault" element={<VaultSetupStep />} />
|
||||
{/* <Route path="custom/memory" element={<CustomMemoryPage />} /> */}
|
||||
<Route path="*" element={<Navigate to="welcome" replace />} />
|
||||
</Route>
|
||||
|
||||
@@ -9,7 +9,8 @@ export type CustomStepKey =
|
||||
| 'search'
|
||||
| 'embeddings'
|
||||
| 'memory'
|
||||
| 'activity';
|
||||
| 'activity'
|
||||
| 'vault';
|
||||
export type CustomStepChoice = 'default' | 'configure';
|
||||
|
||||
export interface OnboardingDraft {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { CustomStepKey } from './OnboardingContext';
|
||||
|
||||
/** Ordered list of custom-wizard steps. Index drives the step counter UI and
|
||||
* the back/continue navigation. `search` and `memory` are commented out for
|
||||
* now — their pages still exist and route in case we want to re-enable. */
|
||||
* the back/continue navigation. */
|
||||
export const CUSTOM_WIZARD_STEPS: CustomStepKey[] = [
|
||||
'inference',
|
||||
'voice',
|
||||
@@ -10,6 +9,7 @@ export const CUSTOM_WIZARD_STEPS: CustomStepKey[] = [
|
||||
'search',
|
||||
'embeddings',
|
||||
'activity',
|
||||
'vault',
|
||||
// 'memory',
|
||||
];
|
||||
|
||||
@@ -21,6 +21,7 @@ export const CUSTOM_WIZARD_ROUTES: Record<CustomStepKey, string> = {
|
||||
embeddings: '/onboarding/custom/embeddings',
|
||||
activity: '/onboarding/custom/activity',
|
||||
memory: '/onboarding/custom/memory',
|
||||
vault: '/onboarding/custom/vault',
|
||||
};
|
||||
|
||||
/** Deep-link target inside Settings for users who pick "Configure" and want
|
||||
@@ -33,4 +34,5 @@ export const CUSTOM_WIZARD_SETTINGS_ROUTES: Record<CustomStepKey, string> = {
|
||||
embeddings: '/settings/embeddings',
|
||||
activity: '/settings/activity-level',
|
||||
memory: '/settings/memory-data',
|
||||
vault: '/settings/memory-data',
|
||||
};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { I18nProvider } from '../../../lib/i18n/I18nContext';
|
||||
import type { Locale } from '../../../lib/i18n/types';
|
||||
import localeReducer from '../../../store/localeSlice';
|
||||
import VaultSetupStep from './VaultSetupStep';
|
||||
|
||||
const navigateMock = vi.fn();
|
||||
const setDraftMock = vi.fn();
|
||||
const completeAndExitMock = vi.fn();
|
||||
|
||||
let sessionToken = 'header.payload.local';
|
||||
|
||||
vi.mock('react-router-dom', async importOriginal => {
|
||||
const actual = await importOriginal<typeof import('react-router-dom')>();
|
||||
return { ...actual, useNavigate: () => navigateMock };
|
||||
});
|
||||
|
||||
vi.mock('../../../components/settings/panels/MemoryDataPanel', () => ({
|
||||
default: () => <div data-testid="memory-data-panel">Memory Data Panel</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../../../providers/CoreStateProvider', () => ({
|
||||
useCoreState: () => ({ snapshot: { sessionToken } }),
|
||||
}));
|
||||
|
||||
vi.mock('../OnboardingContext', () => ({
|
||||
useOnboardingContext: () => ({
|
||||
draft: { connectedSources: [], customChoices: {} },
|
||||
setDraft: setDraftMock,
|
||||
completeAndExit: completeAndExitMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
function renderPage() {
|
||||
const store = configureStore({
|
||||
reducer: { locale: localeReducer },
|
||||
preloadedState: { locale: { current: 'en' as Locale } },
|
||||
});
|
||||
|
||||
return render(
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<I18nProvider>
|
||||
<VaultSetupStep />
|
||||
</I18nProvider>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('VaultSetupStep', () => {
|
||||
beforeEach(() => {
|
||||
navigateMock.mockReset();
|
||||
setDraftMock.mockReset();
|
||||
completeAndExitMock.mockReset();
|
||||
sessionToken = 'header.payload.local';
|
||||
});
|
||||
|
||||
it('forces configure mode and hides chooser cards for local sessions', () => {
|
||||
renderPage();
|
||||
|
||||
expect(screen.getByTestId('memory-data-panel')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('onboarding-custom-vault-step-default')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('onboarding-custom-vault-step-configure')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows default/configure chooser cards for managed sessions', () => {
|
||||
sessionToken = 'header.payload.remote';
|
||||
renderPage();
|
||||
|
||||
expect(screen.getByTestId('onboarding-custom-vault-step-default')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('onboarding-custom-vault-step-configure')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import MemoryDataPanel from '../../../components/settings/panels/MemoryDataPanel';
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import { useCoreState } from '../../../providers/CoreStateProvider';
|
||||
import { trackEvent } from '../../../services/analytics';
|
||||
import { isLocalSessionToken } from '../../../utils/localSession';
|
||||
import { CUSTOM_WIZARD_ROUTES, CUSTOM_WIZARD_STEPS } from '../customWizardSteps';
|
||||
import { type CustomStepChoice, useOnboardingContext } from '../OnboardingContext';
|
||||
import CustomWizardStep from '../steps/CustomWizardStep';
|
||||
|
||||
const STEP_KEY = 'vault' as const;
|
||||
|
||||
export default function VaultSetupStep() {
|
||||
const { t } = useT();
|
||||
const navigate = useNavigate();
|
||||
const { snapshot } = useCoreState();
|
||||
const { draft, setDraft, completeAndExit } = useOnboardingContext();
|
||||
const stepIndex = CUSTOM_WIZARD_STEPS.indexOf(STEP_KEY);
|
||||
const isLocalSession = isLocalSessionToken(snapshot.sessionToken);
|
||||
|
||||
const appliedLocalRef = useRef(false);
|
||||
const initialChoice = isLocalSession ? 'configure' : (draft.customChoices?.[STEP_KEY] ?? null);
|
||||
const [choice, setChoice] = useState<CustomStepChoice | null>(initialChoice);
|
||||
const [exitError, setExitError] = useState<string | null>(null);
|
||||
|
||||
if (isLocalSession && !appliedLocalRef.current) {
|
||||
appliedLocalRef.current = true;
|
||||
if (choice !== 'configure') {
|
||||
setChoice('configure');
|
||||
}
|
||||
setDraft(prev => ({
|
||||
...prev,
|
||||
customChoices: { ...prev.customChoices, [STEP_KEY]: 'configure' },
|
||||
}));
|
||||
}
|
||||
|
||||
const persistChoice = useCallback(
|
||||
(next: CustomStepChoice) => {
|
||||
setChoice(next);
|
||||
setDraft(prev => ({ ...prev, customChoices: { ...prev.customChoices, [STEP_KEY]: next } }));
|
||||
},
|
||||
[setDraft]
|
||||
);
|
||||
|
||||
const configureContent = useMemo(() => <MemoryDataPanel embedded />, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomWizardStep
|
||||
testId="onboarding-custom-vault-step"
|
||||
stepIndex={stepIndex}
|
||||
stepCount={CUSTOM_WIZARD_STEPS.length}
|
||||
title={t('onboarding.custom.vault.title')}
|
||||
subtitle={t('onboarding.custom.vault.subtitle')}
|
||||
defaultDescription={t('onboarding.custom.vault.defaultDesc')}
|
||||
configureDescription={t('onboarding.custom.vault.configureDesc')}
|
||||
configureContent={configureContent}
|
||||
defaultDisabled={isLocalSession}
|
||||
defaultDisabledReason={
|
||||
isLocalSession ? t('onboarding.custom.vault.localDisabledReason') : undefined
|
||||
}
|
||||
hideChoiceCards={isLocalSession}
|
||||
choice={choice}
|
||||
onChoiceChange={persistChoice}
|
||||
onBack={() => navigate(CUSTOM_WIZARD_ROUTES[CUSTOM_WIZARD_STEPS[stepIndex - 1]])}
|
||||
onContinue={async () => {
|
||||
setExitError(null);
|
||||
trackEvent('onboarding_step_complete', {
|
||||
step_name: 'custom_vault',
|
||||
choice: choice ?? 'default',
|
||||
});
|
||||
try {
|
||||
await completeAndExit();
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error('[onboarding:custom-vault] completeAndExit failed', err);
|
||||
setExitError(message);
|
||||
}
|
||||
}}
|
||||
continueLabel={t('onboarding.custom.finish')}
|
||||
/>
|
||||
{exitError ? (
|
||||
<div
|
||||
className="mt-3 rounded-xl border border-coral-200 dark:border-coral-500/30 bg-coral-50 dark:bg-coral-500/10 px-4 py-3 text-sm text-coral-700 dark:text-coral-300"
|
||||
data-testid="onboarding-vault-exit-error">
|
||||
{t('onboarding.custom.vault.exitError')}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -115,6 +115,7 @@ const CustomWizardStep = ({
|
||||
t('onboarding.custom.stepperSearch'),
|
||||
t('onboarding.custom.stepperEmbeddings'),
|
||||
t('onboarding.custom.stepperActivity'),
|
||||
t('onboarding.custom.stepperVault'),
|
||||
t('onboarding.custom.stepperMemory'),
|
||||
].slice(0, stepCount);
|
||||
|
||||
|
||||
@@ -671,6 +671,53 @@ export async function memoryTreeObsidianVaultStatus(
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Response shape for `memory_tree_vault_health_check`. */
|
||||
export interface VaultHealthCheck {
|
||||
/** Absolute filesystem path to `<workspace>/memory_tree/content/`. */
|
||||
content_root_abs: string;
|
||||
/** True when the vault directory exists on disk. */
|
||||
exists: boolean;
|
||||
/** True when the vault directory is readable. */
|
||||
readable: boolean;
|
||||
/** True when a temp-file create+delete probe succeeds in the vault. */
|
||||
writable: boolean;
|
||||
/** True when Obsidian has this folder (or an ancestor) registered as a vault. */
|
||||
obsidian_registered: boolean;
|
||||
/** True when pipeline status is not paused and not in error. */
|
||||
pipeline_healthy: boolean;
|
||||
/** Epoch ms of newest chunk timestamp; zero when no chunks exist yet. */
|
||||
last_sync_ms: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidated onboarding/settings health snapshot for the workspace memory
|
||||
* vault (`<workspace>/memory_tree/content/`).
|
||||
*
|
||||
* Backed by `openhuman.memory_tree_vault_health_check`.
|
||||
*/
|
||||
export async function memoryTreeVaultHealthCheck(
|
||||
obsidianConfigDir?: string
|
||||
): Promise<VaultHealthCheck> {
|
||||
console.debug(
|
||||
'[memory-tree-rpc] memoryTreeVaultHealthCheck: entry override=%s',
|
||||
obsidianConfigDir ? 'set' : 'none'
|
||||
);
|
||||
const resp = await callCoreRpc<VaultHealthCheck | ResultEnvelope<VaultHealthCheck>>({
|
||||
method: 'openhuman.memory_tree_vault_health_check',
|
||||
params: obsidianConfigDir ? { obsidian_config_dir: obsidianConfigDir } : {},
|
||||
});
|
||||
const out = unwrapResult(resp);
|
||||
console.debug(
|
||||
'[memory-tree-rpc] memoryTreeVaultHealthCheck: exit exists=%s readable=%s writable=%s obsidian_registered=%s pipeline_healthy=%s',
|
||||
out.exists,
|
||||
out.readable,
|
||||
out.writable,
|
||||
out.obsidian_registered,
|
||||
out.pipeline_healthy
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* #1574 §4b: per-model embedding re-embed backfill status. The AI settings
|
||||
* panel polls this after an embedder change to warn that semantic recall
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
use rusqlite::params;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
|
||||
use crate::openhuman::config::Config;
|
||||
use crate::openhuman::memory_store::chunks::store::{self as chunk_store, with_connection};
|
||||
@@ -1071,6 +1072,125 @@ pub async fn obsidian_vault_status_rpc(
|
||||
Ok(RpcOutcome::single_log(resp, log))
|
||||
}
|
||||
|
||||
/// Response shape for [`vault_health_check_rpc`].
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct VaultHealthCheckResponse {
|
||||
/// Absolute path to `<workspace>/memory_tree/content/`.
|
||||
pub content_root_abs: String,
|
||||
/// `true` when the content-root directory exists on disk.
|
||||
pub exists: bool,
|
||||
/// `true` when the content-root directory is readable.
|
||||
pub readable: bool,
|
||||
/// `true` when a temp file can be created + removed under content-root.
|
||||
pub writable: bool,
|
||||
/// `true` when the content root (or an ancestor) is a registered Obsidian
|
||||
/// vault in Obsidian's `obsidian.json`.
|
||||
pub obsidian_registered: bool,
|
||||
/// `true` when the Memory Tree pipeline is neither paused nor in an error
|
||||
/// state.
|
||||
pub pipeline_healthy: bool,
|
||||
/// Epoch ms of the most-recent chunk timestamp. Zero when empty.
|
||||
pub last_sync_ms: i64,
|
||||
}
|
||||
|
||||
/// `memory_tree_vault_health_check` — consolidated onboarding/settings health
|
||||
/// snapshot for the workspace vault.
|
||||
///
|
||||
/// Combines:
|
||||
/// - filesystem reachability checks over `<workspace>/memory_tree/content/`
|
||||
/// - Obsidian registration check (same logic as `obsidian_vault_status_rpc`)
|
||||
/// - pipeline health signals from `memory_tree_pipeline_status`
|
||||
///
|
||||
/// `obsidian_config_dir` is optional and mirrors
|
||||
/// [`obsidian_vault_status_rpc`]: it overrides where we probe for
|
||||
/// `obsidian.json` for non-standard installs.
|
||||
pub async fn vault_health_check_rpc(
|
||||
config: &Config,
|
||||
obsidian_config_dir: Option<String>,
|
||||
) -> Result<RpcOutcome<VaultHealthCheckResponse>, String> {
|
||||
let cfg = config.clone();
|
||||
let fs_probe = tokio::task::spawn_blocking(move || {
|
||||
let content_root = cfg.memory_tree_content_root();
|
||||
let content_root_abs = content_root.to_string_lossy().to_string();
|
||||
let exists = content_root.is_dir();
|
||||
let readable = exists && std::fs::read_dir(&content_root).is_ok();
|
||||
let writable = exists && probe_directory_writable(&content_root);
|
||||
|
||||
let extra = obsidian_config_dir
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(std::path::Path::new);
|
||||
let obsidian_registered =
|
||||
obsidian_registry::vault_registration_status(&content_root, extra).registered;
|
||||
|
||||
(
|
||||
content_root_abs,
|
||||
exists,
|
||||
readable,
|
||||
writable,
|
||||
obsidian_registered,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("vault_health_check fs probe join error: {e}"))?;
|
||||
|
||||
let pipeline = crate::openhuman::memory_tree::tree::rpc::pipeline_status_rpc(config)
|
||||
.await
|
||||
.map_err(|e| format!("vault_health_check pipeline_status: {e}"))?;
|
||||
|
||||
let (content_root_abs, exists, readable, writable, obsidian_registered) = fs_probe;
|
||||
let pipeline_healthy = pipeline.value.status != "error" && !pipeline.value.is_paused;
|
||||
let last_sync_ms = pipeline.value.last_sync_ms.max(0);
|
||||
|
||||
let resp = VaultHealthCheckResponse {
|
||||
content_root_abs,
|
||||
exists,
|
||||
readable,
|
||||
writable,
|
||||
obsidian_registered,
|
||||
pipeline_healthy,
|
||||
last_sync_ms,
|
||||
};
|
||||
|
||||
let log = format!(
|
||||
"memory_tree::read: vault_health_check exists={} readable={} writable={} obsidian_registered={} pipeline_healthy={} last_sync_ms={} root_hash={}",
|
||||
resp.exists,
|
||||
resp.readable,
|
||||
resp.writable,
|
||||
resp.obsidian_registered,
|
||||
resp.pipeline_healthy,
|
||||
resp.last_sync_ms,
|
||||
crate::openhuman::memory::util::redact::redact(&resp.content_root_abs),
|
||||
);
|
||||
Ok(RpcOutcome::single_log(resp, log))
|
||||
}
|
||||
|
||||
fn probe_directory_writable(dir: &std::path::Path) -> bool {
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_nanos())
|
||||
.unwrap_or(0);
|
||||
let probe = dir.join(format!(
|
||||
".openhuman-vault-writecheck-{}-{ts}.tmp",
|
||||
std::process::id()
|
||||
));
|
||||
match std::fs::OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.open(&probe)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
let write_ok = file.write_all(b"ok").is_ok();
|
||||
if let Err(e) = std::fs::remove_file(&probe) {
|
||||
log::debug!("[memory] vault write-probe cleanup failed: {e}");
|
||||
}
|
||||
write_ok
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tree mode: summary nodes joined to their owning tree for the
|
||||
/// human-readable scope, plus the leaf chunks that hang off them. Edges
|
||||
/// are encoded implicitly via `GraphNode.parent_id` (a chunk's
|
||||
|
||||
@@ -899,3 +899,51 @@ async fn obsidian_status_blank_override_is_treated_as_none() {
|
||||
.unwrap();
|
||||
assert!(!outcome.value.registered);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn vault_health_check_reports_missing_content_root_for_fresh_workspace() {
|
||||
let (_tmp, cfg) = test_config();
|
||||
let outcome = vault_health_check_rpc(&cfg, None).await.unwrap();
|
||||
|
||||
assert!(!outcome.value.exists);
|
||||
assert!(!outcome.value.readable);
|
||||
assert!(!outcome.value.writable);
|
||||
assert!(!outcome.value.obsidian_registered);
|
||||
assert!(outcome.value.pipeline_healthy);
|
||||
assert_eq!(outcome.value.last_sync_ms, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn vault_health_check_reports_writable_and_obsidian_registered_when_ready() {
|
||||
let (_tmp, cfg) = test_config();
|
||||
seed_chat_chunk(
|
||||
&cfg,
|
||||
"slack:#eng",
|
||||
"Vault health seed chunk so content_root exists and last_sync_ms > 0",
|
||||
)
|
||||
.await;
|
||||
|
||||
let content_root = cfg.memory_tree_content_root();
|
||||
let cfg_dir = TempDir::new().unwrap();
|
||||
let body = format!(
|
||||
"{{ \"vaults\": {{ \"id0\": {{ \"path\": {}, \"open\": true }} }} }}",
|
||||
serde_json::to_string(&content_root.to_string_lossy().to_string()).unwrap()
|
||||
);
|
||||
std::fs::write(cfg_dir.path().join("obsidian.json"), body).unwrap();
|
||||
|
||||
let outcome = vault_health_check_rpc(&cfg, Some(cfg_dir.path().to_string_lossy().to_string()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(outcome.value.exists);
|
||||
assert!(outcome.value.readable);
|
||||
assert!(outcome.value.writable);
|
||||
assert!(outcome.value.obsidian_registered);
|
||||
assert!(outcome.value.pipeline_healthy);
|
||||
assert!(outcome.value.last_sync_ms > 0);
|
||||
assert!(
|
||||
!outcome.logs[0].contains(content_root.to_str().unwrap()),
|
||||
"log leaked content root: {}",
|
||||
outcome.logs[0]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
schemas("delete_chunk"),
|
||||
schemas("graph_export"),
|
||||
schemas("obsidian_vault_status"),
|
||||
schemas("vault_health_check"),
|
||||
schemas("flush_now"),
|
||||
schemas("flush_source"),
|
||||
schemas("wipe_all"),
|
||||
@@ -110,6 +111,10 @@ pub fn all_registered_controllers() -> Vec<RegisteredController> {
|
||||
schema: schemas("obsidian_vault_status"),
|
||||
handler: handle_obsidian_vault_status,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("vault_health_check"),
|
||||
handler: handle_vault_health_check,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("flush_now"),
|
||||
handler: handle_flush_now,
|
||||
@@ -693,6 +698,66 @@ pub fn schemas(function: &str) -> ControllerSchema {
|
||||
},
|
||||
],
|
||||
},
|
||||
"vault_health_check" => ControllerSchema {
|
||||
namespace: NAMESPACE,
|
||||
function: "vault_health_check",
|
||||
description: "Consolidated workspace-vault health snapshot for onboarding and \
|
||||
settings. Checks whether <workspace>/memory_tree/content exists, is \
|
||||
readable, and is writable (via temp-file probe), whether Obsidian has \
|
||||
the vault registered, and whether the Memory Tree pipeline is healthy.",
|
||||
inputs: vec![FieldSchema {
|
||||
name: "obsidian_config_dir",
|
||||
ty: TypeSchema::Option(Box::new(TypeSchema::String)),
|
||||
comment: "Optional override for Obsidian's config directory (where \
|
||||
obsidian.json lives). Omitted ⇒ standard per-OS probe.",
|
||||
required: false,
|
||||
}],
|
||||
outputs: vec![
|
||||
FieldSchema {
|
||||
name: "content_root_abs",
|
||||
ty: TypeSchema::String,
|
||||
comment: "Absolute path to <workspace>/memory_tree/content/.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "exists",
|
||||
ty: TypeSchema::Bool,
|
||||
comment: "True when the workspace vault directory exists on disk.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "readable",
|
||||
ty: TypeSchema::Bool,
|
||||
comment: "True when the workspace vault directory can be read.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "writable",
|
||||
ty: TypeSchema::Bool,
|
||||
comment: "True when the vault accepts a create+delete temp-file probe.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "obsidian_registered",
|
||||
ty: TypeSchema::Bool,
|
||||
comment: "True when Obsidian has this folder (or an ancestor) registered \
|
||||
as a vault.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "pipeline_healthy",
|
||||
ty: TypeSchema::Bool,
|
||||
comment: "True when Memory Tree pipeline is not paused and not in error.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "last_sync_ms",
|
||||
ty: TypeSchema::I64,
|
||||
comment: "Epoch ms of the newest chunk timestamp; 0 when empty.",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
"pipeline_status" => ControllerSchema {
|
||||
namespace: NAMESPACE,
|
||||
function: "pipeline_status",
|
||||
@@ -1077,6 +1142,19 @@ fn handle_obsidian_vault_status(params: Map<String, Value>) -> ControllerFuture
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_vault_health_check(params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move {
|
||||
#[derive(serde::Deserialize, Default)]
|
||||
struct Req {
|
||||
#[serde(default)]
|
||||
obsidian_config_dir: Option<String>,
|
||||
}
|
||||
let config = config_rpc::load_config_with_timeout().await?;
|
||||
let req = parse_value::<Req>(Value::Object(params)).unwrap_or_default();
|
||||
to_json(read_rpc::vault_health_check_rpc(&config, req.obsidian_config_dir).await?)
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_flush_source(params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move {
|
||||
#[derive(serde::Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user