mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
Generated
+12
@@ -5141,6 +5141,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-big-array",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"serde_yaml",
|
||||
"sha1",
|
||||
"sha2 0.10.9",
|
||||
@@ -7176,6 +7177,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
|
||||
@@ -36,6 +36,7 @@ crate-type = ["rlib"]
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_repr = "0.1"
|
||||
serde_yaml = "0.9"
|
||||
# (Removed `html2md` dep. dhat-rs profiling on real Gmail inboxes
|
||||
# showed `html2md::walk` and `html2md::tables::handle` allocating
|
||||
|
||||
Generated
+1
@@ -5394,6 +5394,7 @@ dependencies = [
|
||||
"sentry",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"serde_yaml",
|
||||
"sha1",
|
||||
"sha2 0.10.9",
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useT } from '../../lib/i18n/I18nContext';
|
||||
import { updateMemorySource } from '../../services/memorySourcesService';
|
||||
|
||||
interface SyncBudgetDialogProps {
|
||||
source: {
|
||||
id: string;
|
||||
label: string;
|
||||
max_tokens_per_sync?: number | null;
|
||||
max_cost_per_sync_usd?: number | null;
|
||||
sync_depth_days?: number | null;
|
||||
};
|
||||
onClose: () => void;
|
||||
onSaved: () => void;
|
||||
}
|
||||
|
||||
export default function SyncBudgetDialog({ source, onClose, onSaved }: SyncBudgetDialogProps) {
|
||||
const { t } = useT();
|
||||
const [maxTokens, setMaxTokens] = useState(source.max_tokens_per_sync?.toString() ?? '');
|
||||
const [maxCost, setMaxCost] = useState(source.max_cost_per_sync_usd?.toString() ?? '');
|
||||
const [depthDays, setDepthDays] = useState<string>(source.sync_depth_days?.toString() ?? '');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
await updateMemorySource(source.id, {
|
||||
max_tokens_per_sync: maxTokens ? Number(maxTokens) : undefined,
|
||||
max_cost_per_sync_usd: maxCost ? Number(maxCost) : undefined,
|
||||
sync_depth_days: depthDays ? Number(depthDays) : undefined,
|
||||
} as Parameters<typeof updateMemorySource>[1]);
|
||||
onSaved();
|
||||
onClose();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [source.id, maxTokens, maxCost, depthDays, onSaved, onClose]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
|
||||
onClick={onClose}>
|
||||
<div
|
||||
className="bg-white dark:bg-neutral-900 rounded-xl shadow-xl border border-stone-200 dark:border-neutral-800 w-full max-w-md mx-4 p-5"
|
||||
onClick={e => e.stopPropagation()}>
|
||||
<h3 className="text-base font-semibold text-stone-900 dark:text-neutral-100 mb-1">
|
||||
{t('syncBudget.title')}
|
||||
</h3>
|
||||
<p className="text-xs text-stone-500 dark:text-neutral-400 mb-4">{source.label}</p>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="budget-tokens"
|
||||
className="block text-sm font-medium text-stone-700 dark:text-neutral-300">
|
||||
{t('syncBudget.maxTokens')}
|
||||
</label>
|
||||
<p className="text-xs text-stone-500 dark:text-neutral-400 mt-0.5 mb-1">
|
||||
{t('syncBudget.maxTokensHelp')}
|
||||
</p>
|
||||
<input
|
||||
id="budget-tokens"
|
||||
type="number"
|
||||
min={0}
|
||||
step={10000}
|
||||
value={maxTokens}
|
||||
onChange={e => setMaxTokens(e.target.value)}
|
||||
placeholder={t('syncBudget.unlimited')}
|
||||
className="w-full px-3 py-1.5 rounded-md border border-stone-200 dark:border-neutral-700 bg-white dark:bg-neutral-800 text-sm font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="budget-cost"
|
||||
className="block text-sm font-medium text-stone-700 dark:text-neutral-300">
|
||||
{t('syncBudget.maxCost')}
|
||||
</label>
|
||||
<p className="text-xs text-stone-500 dark:text-neutral-400 mt-0.5 mb-1">
|
||||
{t('syncBudget.maxCostHelp')}
|
||||
</p>
|
||||
<input
|
||||
id="budget-cost"
|
||||
type="number"
|
||||
min={0}
|
||||
step={0.01}
|
||||
value={maxCost}
|
||||
onChange={e => setMaxCost(e.target.value)}
|
||||
placeholder={t('syncBudget.unlimited')}
|
||||
className="w-full px-3 py-1.5 rounded-md border border-stone-200 dark:border-neutral-700 bg-white dark:bg-neutral-800 text-sm font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="budget-depth"
|
||||
className="block text-sm font-medium text-stone-700 dark:text-neutral-300">
|
||||
{t('syncBudget.syncDepth')}
|
||||
</label>
|
||||
<p className="text-xs text-stone-500 dark:text-neutral-400 mt-0.5 mb-1">
|
||||
{t('syncBudget.syncDepthHelp')}
|
||||
</p>
|
||||
<select
|
||||
id="budget-depth"
|
||||
value={depthDays}
|
||||
onChange={e => setDepthDays(e.target.value)}
|
||||
className="w-full px-3 py-1.5 rounded-md border border-stone-200 dark:border-neutral-700 bg-white dark:bg-neutral-800 text-sm">
|
||||
<option value="">{t('syncBudget.allTime')}</option>
|
||||
<option value="7">{t('syncBudget.days7')}</option>
|
||||
<option value="30">{t('syncBudget.days30')}</option>
|
||||
<option value="90">{t('syncBudget.days90')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className="mt-3 text-xs text-coral-600">{error}</p>}
|
||||
|
||||
<div className="flex justify-end gap-2 mt-5">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-3 py-1.5 rounded-md text-xs font-medium text-stone-600 dark:text-neutral-400 hover:bg-stone-100 dark:hover:bg-neutral-800">
|
||||
{t('syncConfirm.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="px-3 py-1.5 rounded-md bg-primary-600 hover:bg-primary-500 disabled:opacity-50 text-white text-xs font-medium">
|
||||
{saving ? t('autonomy.statusSaving') : t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useT } from '../../lib/i18n/I18nContext';
|
||||
import { callCoreRpc } from '../../services/coreRpcClient';
|
||||
|
||||
interface SyncEstimate {
|
||||
item_count: number;
|
||||
estimated_tokens: number;
|
||||
estimated_cost_usd: number;
|
||||
budget_max_cost_usd: number | null;
|
||||
budget_max_tokens: number | null;
|
||||
}
|
||||
|
||||
interface SyncConfirmDialogProps {
|
||||
sourceId: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function SyncConfirmDialog({
|
||||
sourceId,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: SyncConfirmDialogProps) {
|
||||
const { t } = useT();
|
||||
const [estimate, setEstimate] = useState<SyncEstimate | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setEstimate(null);
|
||||
setError(null);
|
||||
(async () => {
|
||||
try {
|
||||
const resp = await callCoreRpc<{ result: SyncEstimate }>({
|
||||
method: 'openhuman.memory_sources_estimate_sync_cost',
|
||||
params: { source_id: sourceId },
|
||||
});
|
||||
if (!cancelled) setEstimate(resp.result);
|
||||
} catch (e) {
|
||||
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [sourceId]);
|
||||
|
||||
const tokenStr = estimate
|
||||
? estimate.estimated_tokens > 1000
|
||||
? `${Math.round(estimate.estimated_tokens / 1000)}k`
|
||||
: String(estimate.estimated_tokens)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40"
|
||||
onClick={onCancel}>
|
||||
<div
|
||||
className="bg-white dark:bg-neutral-900 rounded-xl shadow-xl border border-stone-200 dark:border-neutral-800 w-full max-w-sm mx-4 p-5"
|
||||
onClick={e => e.stopPropagation()}>
|
||||
<h3 className="text-base font-semibold text-stone-900 dark:text-neutral-100 mb-3">
|
||||
{t('syncConfirm.title')}
|
||||
</h3>
|
||||
|
||||
{!estimate && !error && (
|
||||
<p className="text-sm text-stone-500 dark:text-neutral-400">
|
||||
{t('syncConfirm.estimating')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && <p className="text-sm text-coral-600">{error}</p>}
|
||||
|
||||
{estimate && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-sm text-stone-700 dark:text-neutral-300">
|
||||
{t('syncConfirm.message')
|
||||
.replace('{items}', String(estimate.item_count))
|
||||
.replace('{tokens}', tokenStr)
|
||||
.replace('{cost}', estimate.estimated_cost_usd.toFixed(4))}
|
||||
</p>
|
||||
{estimate.budget_max_cost_usd != null && (
|
||||
<p className="text-xs text-stone-500 dark:text-neutral-400">
|
||||
{t('syncConfirm.budgetNote').replace(
|
||||
'{max}',
|
||||
estimate.budget_max_cost_usd.toFixed(2)
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2 mt-5">
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="px-3 py-1.5 rounded-md text-xs font-medium text-stone-600 dark:text-neutral-400 hover:bg-stone-100 dark:hover:bg-neutral-800">
|
||||
{t('syncConfirm.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
disabled={!estimate}
|
||||
className="px-3 py-1.5 rounded-md bg-primary-600 hover:bg-primary-500 disabled:opacity-50 text-white text-xs font-medium">
|
||||
{t('syncConfirm.proceed')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { useT } from '../../../lib/i18n/I18nContext';
|
||||
import { callCoreRpc } from '../../../services/coreRpcClient';
|
||||
|
||||
interface ActivityLevelSettings {
|
||||
level: number;
|
||||
level_label: string;
|
||||
sync_interval_secs: number | null;
|
||||
heartbeat_enabled: boolean;
|
||||
subconscious_enabled: boolean;
|
||||
token_budget_per_cycle: number | null;
|
||||
estimated_monthly_cost_min_usd: number;
|
||||
estimated_monthly_cost_max_usd: number;
|
||||
}
|
||||
|
||||
interface MonthlyCostSummary {
|
||||
month: string;
|
||||
total_cost_usd: number;
|
||||
total_syncs: number;
|
||||
}
|
||||
|
||||
const LEVELS = [
|
||||
{ key: 'off', value: 0 },
|
||||
{ key: 'minimal', value: 1 },
|
||||
{ key: 'moderate', value: 2 },
|
||||
{ key: 'active', value: 3 },
|
||||
{ key: 'alwaysOn', value: 4 },
|
||||
] as const;
|
||||
|
||||
type LevelKey = (typeof LEVELS)[number]['key'];
|
||||
|
||||
type Status = 'idle' | 'loading' | 'saving' | 'saved' | 'error';
|
||||
|
||||
// These tables intentionally duplicate the backend constants in
|
||||
// AgentActivityLevel::estimated_monthly_cost_range (config/schema/activity_level.rs).
|
||||
// The backend only returns cost ranges for the *current* level, so we need a
|
||||
// static lookup to render cost estimates for all levels simultaneously.
|
||||
// A future RPC that returns ranges for all levels would allow removing these.
|
||||
function getCostMin(level: number): number {
|
||||
return [0, 0.1, 1, 5, 20][level] ?? 0;
|
||||
}
|
||||
|
||||
function getCostMax(level: number): number {
|
||||
return [0, 0.5, 5, 20, 100][level] ?? 0;
|
||||
}
|
||||
|
||||
export default function AgentActivityPanel() {
|
||||
const { t } = useT();
|
||||
const [settings, setSettings] = useState<ActivityLevelSettings | null>(null);
|
||||
const [monthlyCost, setMonthlyCost] = useState<MonthlyCostSummary | null>(null);
|
||||
const [status, setStatus] = useState<Status>('loading');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadSettings = useCallback(async () => {
|
||||
try {
|
||||
setStatus('loading');
|
||||
const [settingsResp, costResp] = await Promise.all([
|
||||
callCoreRpc<{ result: ActivityLevelSettings }>({
|
||||
method: 'openhuman.config_get_activity_level_settings',
|
||||
params: {},
|
||||
}),
|
||||
callCoreRpc<{ result: MonthlyCostSummary }>({
|
||||
method: 'openhuman.memory_sources_monthly_cost_summary',
|
||||
params: {},
|
||||
}),
|
||||
]);
|
||||
setSettings(settingsResp.result);
|
||||
setMonthlyCost(costResp.result);
|
||||
setStatus('idle');
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
setStatus('error');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
}, [loadSettings]);
|
||||
|
||||
const handleLevelChange = useCallback(async (levelKey: string) => {
|
||||
try {
|
||||
setStatus('saving');
|
||||
setError(null);
|
||||
const resp = await callCoreRpc<{ result: ActivityLevelSettings }>({
|
||||
method: 'openhuman.config_update_activity_level_settings',
|
||||
params: { level: levelKey },
|
||||
});
|
||||
setSettings(resp.result);
|
||||
setStatus('saved');
|
||||
setTimeout(() => setStatus('idle'), 2000);
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : String(e));
|
||||
setStatus('error');
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (status === 'loading' && !settings) {
|
||||
return (
|
||||
<div className="p-4 text-sm text-stone-500 dark:text-neutral-400">{t('common.loading')}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{t('activityLevel.title')}
|
||||
</h2>
|
||||
<p className="text-xs text-stone-600 dark:text-neutral-400 mt-1">
|
||||
{t('activityLevel.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{monthlyCost && monthlyCost.total_cost_usd > 0 && (
|
||||
<div className="px-3 py-2 rounded-md bg-stone-100 dark:bg-neutral-800 text-sm">
|
||||
<span className="font-medium text-stone-700 dark:text-neutral-300">
|
||||
{t('activityLevel.currentMonth').replace(
|
||||
'{amount}',
|
||||
monthlyCost.total_cost_usd.toFixed(2)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{LEVELS.map(({ key, value }) => {
|
||||
const isSelected = settings?.level === value;
|
||||
const apiKey = key === 'alwaysOn' ? 'always_on' : (key as string);
|
||||
const costMin = getCostMin(value);
|
||||
const costMax = getCostMax(value);
|
||||
return (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => handleLevelChange(apiKey)}
|
||||
disabled={status === 'saving'}
|
||||
className={`w-full text-left px-4 py-3 rounded-lg border transition-colors ${
|
||||
isSelected
|
||||
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
|
||||
: 'border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 hover:border-stone-300 dark:hover:border-neutral-700'
|
||||
} ${status === 'saving' ? 'opacity-50' : ''}`}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-stone-900 dark:text-neutral-100">
|
||||
{t(`activityLevel.${key as LevelKey}`)}
|
||||
</span>
|
||||
{value === 2 && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-stone-200 dark:bg-neutral-700 text-stone-600 dark:text-neutral-400">
|
||||
{t('activityLevel.default')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-stone-500 dark:text-neutral-400 mt-0.5">
|
||||
{t(`activityLevel.${key as LevelKey}Desc`)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-xs font-mono text-stone-500 dark:text-neutral-400 shrink-0 ml-4">
|
||||
{costMin === 0 && costMax === 0
|
||||
? t('activityLevel.costFree')
|
||||
: t('activityLevel.costRange')
|
||||
.replace('{min}', String(costMin))
|
||||
.replace('{max}', String(costMax))}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div role="status" aria-live="polite" aria-atomic="true" className="text-xs min-h-[1rem]">
|
||||
{status === 'saving' && (
|
||||
<span className="text-stone-500">{t('autonomy.statusSaving')}</span>
|
||||
)}
|
||||
{status === 'saved' && (
|
||||
<span className="text-sage-700 dark:text-sage-400">{t('activityLevel.saved')}</span>
|
||||
)}
|
||||
{error && <span className="text-coral-600">{error}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4372,6 +4372,61 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'رفض التخزين المحلي',
|
||||
'pages.settings.account.security': 'الأمان',
|
||||
'pages.settings.account.securityDesc': 'وضع تخزين الأسرار وحالة سلسلة المفاتيح',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'مستوى نشاط الوكيل',
|
||||
'activityLevel.description':
|
||||
'تحكم في مدى استباقية وكيلك. المستويات الأعلى تستخدم المزيد من الرموز.',
|
||||
'activityLevel.off': 'إيقاف',
|
||||
'activityLevel.offDesc': 'لا معالجة في الخلفية. يزامن فقط عند الضغط على الزر.',
|
||||
'activityLevel.minimal': 'أدنى حد',
|
||||
'activityLevel.minimalDesc': 'مزامنة المصادر مرة يوميًا. لا رسائل استباقية.',
|
||||
'activityLevel.moderate': 'متوسط',
|
||||
'activityLevel.moderateDesc': 'مزامنة كل ساعة. ملخص يومي. يقترح إجراءات.',
|
||||
'activityLevel.active': 'نشط',
|
||||
'activityLevel.activeDesc': 'مزامنة كل 10 دقائق. يراقب القنوات ويصنف ويصيغ الردود.',
|
||||
'activityLevel.alwaysOn': 'دائم التشغيل',
|
||||
'activityLevel.alwaysOnDesc': 'مزامنة فورية. استقلالية كاملة ضمن الحدود المحددة.',
|
||||
'activityLevel.currentMonth': 'هذا الشهر: ${amount}',
|
||||
'activityLevel.saved': 'تم تحديث مستوى النشاط.',
|
||||
'activityLevel.default': 'افتراضي',
|
||||
'activityLevel.costFree': '0$',
|
||||
'activityLevel.costRange': '~${min}–${max}/شهر',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'ميزانية المزامنة',
|
||||
'syncBudget.maxTokens': 'الحد الأقصى للرموز لكل مزامنة',
|
||||
'syncBudget.maxTokensHelp': 'توقف المزامنة عند استهلاك هذا العدد من الرموز.',
|
||||
'syncBudget.maxCost': 'الحد الأقصى للتكلفة لكل مزامنة (USD)',
|
||||
'syncBudget.maxCostHelp': 'حد التكلفة المطلق بالدولار لكل تشغيل مزامنة.',
|
||||
'syncBudget.syncDepth': 'عمق المزامنة',
|
||||
'syncBudget.syncDepthHelp': 'جلب العناصر من هذه الفترة الزمنية فقط.',
|
||||
'syncBudget.days7': 'آخر 7 أيام',
|
||||
'syncBudget.days30': 'آخر 30 يومًا',
|
||||
'syncBudget.days90': 'آخر 90 يومًا',
|
||||
'syncBudget.allTime': 'كل الأوقات',
|
||||
'syncBudget.unlimited': 'غير محدود',
|
||||
'syncBudget.saved': 'تم حفظ الميزانية.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'تأكيد المزامنة',
|
||||
'syncConfirm.message': 'ستعالج هذه المزامنة ~{items} عناصر (~{tokens} رمز، تقديري ${cost}).',
|
||||
'syncConfirm.budgetNote': 'حد الميزانية: ${max}',
|
||||
'syncConfirm.proceed': 'المتابعة',
|
||||
'syncConfirm.cancel': 'إلغاء',
|
||||
'syncConfirm.estimating': 'جارٍ تقدير التكلفة...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} هذا الشهر',
|
||||
'monthlyCost.noData': 'لا مزامنات هذا الشهر',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'النشاط',
|
||||
'onboarding.custom.activity.title': 'نشاط الوكيل',
|
||||
'onboarding.custom.activity.subtitle': 'مدى استباقية وكيلك في المراقبة والتصرف في الخلفية.',
|
||||
'onboarding.custom.activity.defaultDesc': 'نشاط متوسط — مزامنة كل ساعة، ملخص يومي.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'اختر مستوى نشاطك الخاص. الإعداد في الإعدادات › مستوى نشاط الوكيل.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4450,6 +4450,66 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'স্থানীয় সঞ্চয়স্থান প্রত্যাখ্যান করুন',
|
||||
'pages.settings.account.security': 'নিরাপত্তা',
|
||||
'pages.settings.account.securityDesc': 'গোপনীয়তা সঞ্চয়স্থান মোড এবং কিচেন অবস্থা',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'এজেন্ট কার্যকলাপের স্তর',
|
||||
'activityLevel.description':
|
||||
'আপনার এজেন্ট কতটা সক্রিয় তা নিয়ন্ত্রণ করুন। উচ্চ স্তরে বেশি টোকেন ব্যবহার হয়।',
|
||||
'activityLevel.off': 'বন্ধ',
|
||||
'activityLevel.offDesc': 'পটভূমিতে কোনো প্রক্রিয়াকরণ নেই। শুধুমাত্র বোতাম চাপলে সিঙ্ক করে।',
|
||||
'activityLevel.minimal': 'ন্যূনতম',
|
||||
'activityLevel.minimalDesc': 'প্রতিদিন একবার উৎস সিঙ্ক করে। কোনো সক্রিয় বার্তা নেই।',
|
||||
'activityLevel.moderate': 'মাঝারি',
|
||||
'activityLevel.moderateDesc':
|
||||
'প্রতি ঘণ্টায় সিঙ্ক করে। দৈনিক সারসংক্ষেপ। কর্মপন্থা প্রস্তাব করে।',
|
||||
'activityLevel.active': 'সক্রিয়',
|
||||
'activityLevel.activeDesc':
|
||||
'প্রতি ১০ মিনিটে সিঙ্ক করে। চ্যানেল পর্যবেক্ষণ, বাছাই ও উত্তর খসড়া করে।',
|
||||
'activityLevel.alwaysOn': 'সর্বদা চালু',
|
||||
'activityLevel.alwaysOnDesc': 'রিয়েল-টাইম সিঙ্ক। নির্ধারিত সীমার মধ্যে পূর্ণ স্বায়ত্তশাসন।',
|
||||
'activityLevel.currentMonth': 'এই মাস: ${amount}',
|
||||
'activityLevel.saved': 'কার্যকলাপের স্তর আপডেট হয়েছে।',
|
||||
'activityLevel.default': 'ডিফল্ট',
|
||||
'activityLevel.costFree': '$০',
|
||||
'activityLevel.costRange': '~${min}–${max}/মাস',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'সিঙ্ক বাজেট',
|
||||
'syncBudget.maxTokens': 'প্রতি সিঙ্কে সর্বোচ্চ টোকেন',
|
||||
'syncBudget.maxTokensHelp': 'এতটা টোকেন ব্যবহার হলে সিঙ্ক বন্ধ করুন।',
|
||||
'syncBudget.maxCost': 'প্রতি সিঙ্কে সর্বোচ্চ খরচ (USD)',
|
||||
'syncBudget.maxCostHelp': 'প্রতিটি সিঙ্ক রানের জন্য নির্দিষ্ট ডলার সীমা।',
|
||||
'syncBudget.syncDepth': 'সিঙ্ক গভীরতা',
|
||||
'syncBudget.syncDepthHelp': 'কেবল এই সময়সীমার আইটেম আনুন।',
|
||||
'syncBudget.days7': 'গত ৭ দিন',
|
||||
'syncBudget.days30': 'গত ৩০ দিন',
|
||||
'syncBudget.days90': 'গত ৯০ দিন',
|
||||
'syncBudget.allTime': 'সব সময়',
|
||||
'syncBudget.unlimited': 'সীমাহীন',
|
||||
'syncBudget.saved': 'বাজেট সংরক্ষিত হয়েছে।',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'সিঙ্ক নিশ্চিত করুন',
|
||||
'syncConfirm.message':
|
||||
'এই সিঙ্ক ~{items}টি আইটেম (~{tokens} টোকেন, আনুমানিক ${cost}) প্রক্রিয়া করবে।',
|
||||
'syncConfirm.budgetNote': 'বাজেট সীমা: ${max}',
|
||||
'syncConfirm.proceed': 'চালিয়ে যান',
|
||||
'syncConfirm.cancel': 'বাতিল',
|
||||
'syncConfirm.estimating': 'খরচ অনুমান করা হচ্ছে...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': 'এই মাসে ${amount}',
|
||||
'monthlyCost.noData': 'এই মাসে কোনো সিঙ্ক নেই',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'কার্যকলাপ',
|
||||
'onboarding.custom.activity.title': 'এজেন্টের কার্যকলাপ',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'আপনার এজেন্ট পটভূমিতে কতটা সক্রিয়ভাবে পর্যবেক্ষণ ও কাজ করে।',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'মাঝারি কার্যকলাপ — প্রতি ঘণ্টায় সিঙ্ক, দৈনিক সারসংক্ষেপ।',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'নিজের কার্যকলাপের স্তর বেছে নিন। সেটিংস › এজেন্ট কার্যকলাপ স্তরে কনফিগার করুন।',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4567,6 +4567,69 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Lokalen Speicher ablehnen',
|
||||
'pages.settings.account.security': 'Sicherheit',
|
||||
'pages.settings.account.securityDesc': 'Geheimnisspeicher-Modus und Schlüsselbund-Status',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'Agent-Aktivitätsstufe',
|
||||
'activityLevel.description':
|
||||
'Steuern Sie, wie proaktiv Ihr Agent ist. Höhere Stufen verbrauchen mehr Tokens.',
|
||||
'activityLevel.off': 'Aus',
|
||||
'activityLevel.offDesc': 'Keine Hintergrundverarbeitung. Synchronisiert nur auf Knopfdruck.',
|
||||
'activityLevel.minimal': 'Minimal',
|
||||
'activityLevel.minimalDesc':
|
||||
'Quellen einmal täglich synchronisieren. Keine proaktiven Nachrichten.',
|
||||
'activityLevel.moderate': 'Moderat',
|
||||
'activityLevel.moderateDesc':
|
||||
'Stündliche Synchronisierung. Tägliche Zusammenfassung. Schlägt Aktionen vor.',
|
||||
'activityLevel.active': 'Aktiv',
|
||||
'activityLevel.activeDesc':
|
||||
'Alle 10 Minuten synchronisieren. Überwacht Kanäle, priorisiert und entwirft Antworten.',
|
||||
'activityLevel.alwaysOn': 'Immer aktiv',
|
||||
'activityLevel.alwaysOnDesc':
|
||||
'Echtzeit-Synchronisierung. Volle Autonomie innerhalb von Leitplanken.',
|
||||
'activityLevel.currentMonth': 'Dieser Monat: ${amount}',
|
||||
'activityLevel.saved': 'Aktivitätsstufe aktualisiert.',
|
||||
'activityLevel.default': 'Standard',
|
||||
'activityLevel.costFree': '0 $',
|
||||
'activityLevel.costRange': '~${min}–${max}/Monat',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Synchronisierungsbudget',
|
||||
'syncBudget.maxTokens': 'Max. Tokens pro Synchronisierung',
|
||||
'syncBudget.maxTokensHelp':
|
||||
'Synchronisierung stoppen, sobald diese Anzahl von Tokens verbraucht wurde.',
|
||||
'syncBudget.maxCost': 'Max. Kosten pro Synchronisierung (USD)',
|
||||
'syncBudget.maxCostHelp': 'Absolutes Kostenlimit pro Synchronisierungslauf.',
|
||||
'syncBudget.syncDepth': 'Synchronisierungstiefe',
|
||||
'syncBudget.syncDepthHelp': 'Nur Elemente aus diesem Zeitfenster abrufen.',
|
||||
'syncBudget.days7': 'Letzte 7 Tage',
|
||||
'syncBudget.days30': 'Letzte 30 Tage',
|
||||
'syncBudget.days90': 'Letzte 90 Tage',
|
||||
'syncBudget.allTime': 'Gesamte Zeit',
|
||||
'syncBudget.unlimited': 'Unbegrenzt',
|
||||
'syncBudget.saved': 'Budget gespeichert.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Synchronisierung bestätigen',
|
||||
'syncConfirm.message':
|
||||
'Diese Synchronisierung verarbeitet ~{items} Elemente (~{tokens} Tokens, ca. ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Budgetlimit: ${max}',
|
||||
'syncConfirm.proceed': 'Fortfahren',
|
||||
'syncConfirm.cancel': 'Abbrechen',
|
||||
'syncConfirm.estimating': 'Kosten werden geschätzt...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} diesen Monat',
|
||||
'monthlyCost.noData': 'Keine Synchronisierungen diesen Monat',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Aktivität',
|
||||
'onboarding.custom.activity.title': 'Agent-Aktivität',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Wie proaktiv Ihr Agent im Hintergrund überwacht und handelt.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'Moderate Aktivität – stündliche Synchronisierung, tägliche Zusammenfassung.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Eigene Aktivitätsstufe wählen. Konfigurieren in Einstellungen › Agent-Aktivitätsstufe.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
+58
-1
@@ -608,12 +608,13 @@ const en: TranslationMap = {
|
||||
'onboarding.apiKeys.continue': 'Save and continue',
|
||||
'onboarding.apiKeys.saving': 'Saving…',
|
||||
|
||||
// Onboarding: Custom wizard (Inference / Voice / OAuth / Search / Memory)
|
||||
// Onboarding: Custom wizard (Inference / Voice / OAuth / Search / Activity / 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.stepperMemory': 'Memory',
|
||||
'onboarding.custom.stepCounter': 'Step {n} of {total}',
|
||||
'onboarding.custom.defaultTitle': 'Default',
|
||||
@@ -671,6 +672,15 @@ const en: TranslationMap = {
|
||||
'onboarding.custom.embeddings.configureDesc':
|
||||
'Bring your own embedding provider (OpenAI, Voyage, Ollama, etc.).',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.activity.title': 'Agent Activity',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'How proactively your agent monitors and acts in the background.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'Moderate activity — syncs every hour, sends a daily digest. Balanced cost and responsiveness.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Pick your own activity level. Configure in Settings › Agent activity level.',
|
||||
|
||||
// Onboarding: Custom > Memory
|
||||
'onboarding.custom.memory.title': 'Memory',
|
||||
'onboarding.custom.memory.subtitle':
|
||||
@@ -680,6 +690,26 @@ const en: TranslationMap = {
|
||||
'onboarding.custom.memory.configureDesc':
|
||||
'Inspect, export, or wipe memory yourself. Configure in Settings › Memory.',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'Agent activity level',
|
||||
'activityLevel.description':
|
||||
'Control how proactive your agent is. Higher levels use more tokens.',
|
||||
'activityLevel.off': 'Off',
|
||||
'activityLevel.offDesc': 'No background processing. Syncs only when you press the button.',
|
||||
'activityLevel.minimal': 'Minimal',
|
||||
'activityLevel.minimalDesc': 'Sync sources once per day. No proactive messages.',
|
||||
'activityLevel.moderate': 'Moderate',
|
||||
'activityLevel.moderateDesc': 'Sync every hour. Daily digest. Suggests actions.',
|
||||
'activityLevel.active': 'Active',
|
||||
'activityLevel.activeDesc': 'Sync every 10 min. Monitors channels, triages, drafts replies.',
|
||||
'activityLevel.alwaysOn': 'Always-on',
|
||||
'activityLevel.alwaysOnDesc': 'Real-time sync. Full autonomy within guardrails.',
|
||||
'activityLevel.currentMonth': 'This month: ${amount}',
|
||||
'activityLevel.saved': 'Activity level updated.',
|
||||
'activityLevel.default': 'default',
|
||||
'activityLevel.costFree': '$0',
|
||||
'activityLevel.costRange': '~${min}–${max}/mo',
|
||||
|
||||
// Accounts
|
||||
'accounts.addAccount': 'Add Account',
|
||||
'accounts.manageAccounts': 'Manage Accounts',
|
||||
@@ -4679,6 +4709,33 @@ const en: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Decline local storage',
|
||||
'pages.settings.account.security': 'Security',
|
||||
'pages.settings.account.securityDesc': 'Secret storage mode and keychain status',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Sync budget',
|
||||
'syncBudget.maxTokens': 'Max tokens per sync',
|
||||
'syncBudget.maxTokensHelp': 'Stop syncing once this many tokens are consumed.',
|
||||
'syncBudget.maxCost': 'Max cost per sync (USD)',
|
||||
'syncBudget.maxCostHelp': 'Hard dollar cap per sync run.',
|
||||
'syncBudget.syncDepth': 'Sync depth',
|
||||
'syncBudget.syncDepthHelp': 'Only fetch items from this time window.',
|
||||
'syncBudget.days7': 'Last 7 days',
|
||||
'syncBudget.days30': 'Last 30 days',
|
||||
'syncBudget.days90': 'Last 90 days',
|
||||
'syncBudget.allTime': 'All time',
|
||||
'syncBudget.unlimited': 'Unlimited',
|
||||
'syncBudget.saved': 'Budget saved.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Confirm sync',
|
||||
'syncConfirm.message': 'This sync will process ~{items} items (~{tokens} tokens, est. ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Budget cap: ${max}',
|
||||
'syncConfirm.proceed': 'Proceed',
|
||||
'syncConfirm.cancel': 'Cancel',
|
||||
'syncConfirm.estimating': 'Estimating cost...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} this month',
|
||||
'monthlyCost.noData': 'No syncs this month',
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
||||
@@ -4533,6 +4533,67 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Rechazar almacenamiento local',
|
||||
'pages.settings.account.security': 'Seguridad',
|
||||
'pages.settings.account.securityDesc': 'Modo de almacenamiento de secretos y estado del llavero',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'Nivel de actividad del agente',
|
||||
'activityLevel.description':
|
||||
'Controla qué tan proactivo es tu agente. Los niveles más altos usan más tokens.',
|
||||
'activityLevel.off': 'Desactivado',
|
||||
'activityLevel.offDesc':
|
||||
'Sin procesamiento en segundo plano. Sincroniza solo al presionar el botón.',
|
||||
'activityLevel.minimal': 'Mínimo',
|
||||
'activityLevel.minimalDesc': 'Sincroniza fuentes una vez al día. Sin mensajes proactivos.',
|
||||
'activityLevel.moderate': 'Moderado',
|
||||
'activityLevel.moderateDesc': 'Sincroniza cada hora. Resumen diario. Sugiere acciones.',
|
||||
'activityLevel.active': 'Activo',
|
||||
'activityLevel.activeDesc':
|
||||
'Sincroniza cada 10 min. Monitorea canales, prioriza y redacta respuestas.',
|
||||
'activityLevel.alwaysOn': 'Siempre activo',
|
||||
'activityLevel.alwaysOnDesc':
|
||||
'Sincronización en tiempo real. Autonomía total dentro de los límites.',
|
||||
'activityLevel.currentMonth': 'Este mes: ${amount}',
|
||||
'activityLevel.saved': 'Nivel de actividad actualizado.',
|
||||
'activityLevel.default': 'predeterminado',
|
||||
'activityLevel.costFree': '$0',
|
||||
'activityLevel.costRange': '~${min}–${max}/mes',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Presupuesto de sincronización',
|
||||
'syncBudget.maxTokens': 'Tokens máximos por sincronización',
|
||||
'syncBudget.maxTokensHelp': 'Detener la sincronización al consumir este número de tokens.',
|
||||
'syncBudget.maxCost': 'Costo máximo por sincronización (USD)',
|
||||
'syncBudget.maxCostHelp': 'Límite absoluto en dólares por ejecución de sincronización.',
|
||||
'syncBudget.syncDepth': 'Profundidad de sincronización',
|
||||
'syncBudget.syncDepthHelp': 'Solo obtener elementos de esta ventana de tiempo.',
|
||||
'syncBudget.days7': 'Últimos 7 días',
|
||||
'syncBudget.days30': 'Últimos 30 días',
|
||||
'syncBudget.days90': 'Últimos 90 días',
|
||||
'syncBudget.allTime': 'Todo el tiempo',
|
||||
'syncBudget.unlimited': 'Ilimitado',
|
||||
'syncBudget.saved': 'Presupuesto guardado.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Confirmar sincronización',
|
||||
'syncConfirm.message':
|
||||
'Esta sincronización procesará ~{items} elementos (~{tokens} tokens, est. ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Límite de presupuesto: ${max}',
|
||||
'syncConfirm.proceed': 'Proceder',
|
||||
'syncConfirm.cancel': 'Cancelar',
|
||||
'syncConfirm.estimating': 'Calculando costo...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} este mes',
|
||||
'monthlyCost.noData': 'Sin sincronizaciones este mes',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Actividad',
|
||||
'onboarding.custom.activity.title': 'Actividad del agente',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Qué tan proactivamente monitorea y actúa tu agente en segundo plano.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'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.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4548,6 +4548,68 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Refuser le stockage local',
|
||||
'pages.settings.account.security': 'Sécurité',
|
||||
'pages.settings.account.securityDesc': 'Mode de stockage des secrets et état du trousseau',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': "Niveau d'activité de l'agent",
|
||||
'activityLevel.description':
|
||||
'Contrôlez le niveau de proactivité de votre agent. Les niveaux élevés consomment plus de tokens.',
|
||||
'activityLevel.off': 'Désactivé',
|
||||
'activityLevel.offDesc':
|
||||
'Aucun traitement en arrière-plan. Synchronise uniquement sur pression du bouton.',
|
||||
'activityLevel.minimal': 'Minimal',
|
||||
'activityLevel.minimalDesc': 'Synchronise les sources une fois par jour. Aucun message proactif.',
|
||||
'activityLevel.moderate': 'Modéré',
|
||||
'activityLevel.moderateDesc':
|
||||
'Synchronise toutes les heures. Résumé quotidien. Suggère des actions.',
|
||||
'activityLevel.active': 'Actif',
|
||||
'activityLevel.activeDesc':
|
||||
'Synchronise toutes les 10 min. Surveille les canaux, trie et rédige des réponses.',
|
||||
'activityLevel.alwaysOn': 'Toujours actif',
|
||||
'activityLevel.alwaysOnDesc':
|
||||
'Synchronisation en temps réel. Pleine autonomie dans les limites définies.',
|
||||
'activityLevel.currentMonth': 'Ce mois-ci : ${amount}',
|
||||
'activityLevel.saved': "Niveau d'activité mis à jour.",
|
||||
'activityLevel.default': 'par défaut',
|
||||
'activityLevel.costFree': '0 $',
|
||||
'activityLevel.costRange': '~${min}–${max}/mois',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Budget de synchronisation',
|
||||
'syncBudget.maxTokens': 'Tokens max par synchronisation',
|
||||
'syncBudget.maxTokensHelp': 'Arrêter la synchronisation une fois ce nombre de tokens consommé.',
|
||||
'syncBudget.maxCost': 'Coût max par synchronisation (USD)',
|
||||
'syncBudget.maxCostHelp': 'Plafond absolu en dollars par exécution de synchronisation.',
|
||||
'syncBudget.syncDepth': 'Profondeur de synchronisation',
|
||||
'syncBudget.syncDepthHelp': 'Récupérer uniquement les éléments de cette fenêtre temporelle.',
|
||||
'syncBudget.days7': '7 derniers jours',
|
||||
'syncBudget.days30': '30 derniers jours',
|
||||
'syncBudget.days90': '90 derniers jours',
|
||||
'syncBudget.allTime': 'Tout le temps',
|
||||
'syncBudget.unlimited': 'Illimité',
|
||||
'syncBudget.saved': 'Budget enregistré.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Confirmer la synchronisation',
|
||||
'syncConfirm.message':
|
||||
'Cette synchronisation traitera ~{items} éléments (~{tokens} tokens, est. ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Plafond budgétaire : ${max}',
|
||||
'syncConfirm.proceed': 'Procéder',
|
||||
'syncConfirm.cancel': 'Annuler',
|
||||
'syncConfirm.estimating': 'Estimation du coût...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} ce mois-ci',
|
||||
'monthlyCost.noData': 'Aucune synchronisation ce mois-ci',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Activité',
|
||||
'onboarding.custom.activity.title': "Activité de l'agent",
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'À quel point votre agent surveille et agit en arrière-plan.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'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.",
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4457,6 +4457,63 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'स्थानीय भंडारण अस्वीकार करें',
|
||||
'pages.settings.account.security': 'सुरक्षा',
|
||||
'pages.settings.account.securityDesc': 'रहस्य भंडारण मोड और कीचेन स्थिति',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'एजेंट गतिविधि स्तर',
|
||||
'activityLevel.description':
|
||||
'नियंत्रित करें कि आपका एजेंट कितना सक्रिय है। उच्च स्तर पर अधिक टोकन का उपयोग होता है।',
|
||||
'activityLevel.off': 'बंद',
|
||||
'activityLevel.offDesc': 'पृष्ठभूमि में कोई प्रसंस्करण नहीं। केवल बटन दबाने पर सिंक होता है।',
|
||||
'activityLevel.minimal': 'न्यूनतम',
|
||||
'activityLevel.minimalDesc': 'दिन में एक बार स्रोत सिंक करता है। कोई सक्रिय संदेश नहीं।',
|
||||
'activityLevel.moderate': 'मध्यम',
|
||||
'activityLevel.moderateDesc': 'हर घंटे सिंक करता है। दैनिक सारांश। क्रियाएं सुझाता है।',
|
||||
'activityLevel.active': 'सक्रिय',
|
||||
'activityLevel.activeDesc':
|
||||
'हर 10 मिनट में सिंक करता है। चैनलों की निगरानी करता है, प्राथमिकता देता है और उत्तर तैयार करता है।',
|
||||
'activityLevel.alwaysOn': 'हमेशा चालू',
|
||||
'activityLevel.alwaysOnDesc': 'रीयल-टाइम सिंक। निर्धारित सीमाओं के भीतर पूर्ण स्वायत्तता।',
|
||||
'activityLevel.currentMonth': 'इस महीने: ${amount}',
|
||||
'activityLevel.saved': 'गतिविधि स्तर अपडेट किया गया।',
|
||||
'activityLevel.default': 'डिफ़ॉल्ट',
|
||||
'activityLevel.costFree': '$0',
|
||||
'activityLevel.costRange': '~${min}–${max}/माह',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'सिंक बजट',
|
||||
'syncBudget.maxTokens': 'प्रति सिंक अधिकतम टोकन',
|
||||
'syncBudget.maxTokensHelp': 'इतने टोकन उपयोग होने पर सिंक बंद करें।',
|
||||
'syncBudget.maxCost': 'प्रति सिंक अधिकतम लागत (USD)',
|
||||
'syncBudget.maxCostHelp': 'प्रति सिंक रन के लिए डॉलर की सीमा।',
|
||||
'syncBudget.syncDepth': 'सिंक गहराई',
|
||||
'syncBudget.syncDepthHelp': 'केवल इस समय सीमा के आइटम लाएं।',
|
||||
'syncBudget.days7': 'पिछले 7 दिन',
|
||||
'syncBudget.days30': 'पिछले 30 दिन',
|
||||
'syncBudget.days90': 'पिछले 90 दिन',
|
||||
'syncBudget.allTime': 'सब समय',
|
||||
'syncBudget.unlimited': 'असीमित',
|
||||
'syncBudget.saved': 'बजट सहेजा गया।',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'सिंक की पुष्टि करें',
|
||||
'syncConfirm.message': 'यह सिंक ~{items} आइटम (~{tokens} टोकन, अनुमानित ${cost}) प्रोसेस करेगा।',
|
||||
'syncConfirm.budgetNote': 'बजट सीमा: ${max}',
|
||||
'syncConfirm.proceed': 'आगे बढ़ें',
|
||||
'syncConfirm.cancel': 'रद्द करें',
|
||||
'syncConfirm.estimating': 'लागत का अनुमान लगाया जा रहा है...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': 'इस महीने ${amount}',
|
||||
'monthlyCost.noData': 'इस महीने कोई सिंक नहीं',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'गतिविधि',
|
||||
'onboarding.custom.activity.title': 'एजेंट गतिविधि',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'आपका एजेंट पृष्ठभूमि में कितनी सक्रियता से निगरानी और कार्य करता है।',
|
||||
'onboarding.custom.activity.defaultDesc': 'मध्यम गतिविधि — प्रति घंटे सिंक, दैनिक सारांश।',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'अपना गतिविधि स्तर चुनें। सेटिंग्स › एजेंट गतिविधि स्तर में कॉन्फ़िगर करें।',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4467,6 +4467,67 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Tolak penyimpanan lokal',
|
||||
'pages.settings.account.security': 'Keamanan',
|
||||
'pages.settings.account.securityDesc': 'Mode penyimpanan rahasia dan status keychain',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'Tingkat aktivitas agen',
|
||||
'activityLevel.description':
|
||||
'Kendalikan seberapa proaktif agen Anda. Tingkat lebih tinggi menggunakan lebih banyak token.',
|
||||
'activityLevel.off': 'Mati',
|
||||
'activityLevel.offDesc':
|
||||
'Tidak ada pemrosesan latar belakang. Hanya sinkronisasi saat tombol ditekan.',
|
||||
'activityLevel.minimal': 'Minimal',
|
||||
'activityLevel.minimalDesc': 'Sinkronisasi sumber sekali sehari. Tidak ada pesan proaktif.',
|
||||
'activityLevel.moderate': 'Sedang',
|
||||
'activityLevel.moderateDesc': 'Sinkronisasi setiap jam. Ringkasan harian. Menyarankan tindakan.',
|
||||
'activityLevel.active': 'Aktif',
|
||||
'activityLevel.activeDesc':
|
||||
'Sinkronisasi setiap 10 menit. Memantau saluran, mengurutkan, dan menyusun balasan.',
|
||||
'activityLevel.alwaysOn': 'Selalu aktif',
|
||||
'activityLevel.alwaysOnDesc':
|
||||
'Sinkronisasi real-time. Otonomi penuh dalam batas yang ditentukan.',
|
||||
'activityLevel.currentMonth': 'Bulan ini: ${amount}',
|
||||
'activityLevel.saved': 'Tingkat aktivitas diperbarui.',
|
||||
'activityLevel.default': 'bawaan',
|
||||
'activityLevel.costFree': '$0',
|
||||
'activityLevel.costRange': '~${min}–${max}/bln',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Anggaran sinkronisasi',
|
||||
'syncBudget.maxTokens': 'Token maksimum per sinkronisasi',
|
||||
'syncBudget.maxTokensHelp': 'Hentikan sinkronisasi setelah sejumlah token ini dikonsumsi.',
|
||||
'syncBudget.maxCost': 'Biaya maksimum per sinkronisasi (USD)',
|
||||
'syncBudget.maxCostHelp': 'Batas dolar mutlak per jalannya sinkronisasi.',
|
||||
'syncBudget.syncDepth': 'Kedalaman sinkronisasi',
|
||||
'syncBudget.syncDepthHelp': 'Hanya ambil item dari jendela waktu ini.',
|
||||
'syncBudget.days7': '7 hari terakhir',
|
||||
'syncBudget.days30': '30 hari terakhir',
|
||||
'syncBudget.days90': '90 hari terakhir',
|
||||
'syncBudget.allTime': 'Sepanjang waktu',
|
||||
'syncBudget.unlimited': 'Tidak terbatas',
|
||||
'syncBudget.saved': 'Anggaran disimpan.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Konfirmasi sinkronisasi',
|
||||
'syncConfirm.message':
|
||||
'Sinkronisasi ini akan memproses ~{items} item (~{tokens} token, est. ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Batas anggaran: ${max}',
|
||||
'syncConfirm.proceed': 'Lanjutkan',
|
||||
'syncConfirm.cancel': 'Batal',
|
||||
'syncConfirm.estimating': 'Memperkirakan biaya...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} bulan ini',
|
||||
'monthlyCost.noData': 'Tidak ada sinkronisasi bulan ini',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Aktivitas',
|
||||
'onboarding.custom.activity.title': 'Aktivitas agen',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Seberapa proaktif agen Anda memantau dan bertindak di latar belakang.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'Aktivitas sedang — sinkronisasi per jam, ringkasan harian.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Pilih tingkat aktivitas Anda sendiri. Konfigurasi di Pengaturan › Tingkat aktivitas agen.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4525,6 +4525,69 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Rifiuta archiviazione locale',
|
||||
'pages.settings.account.security': 'Sicurezza',
|
||||
'pages.settings.account.securityDesc': 'Modalità archiviazione segreti e stato del portachiavi',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': "Livello di attività dell'agente",
|
||||
'activityLevel.description':
|
||||
'Controlla quanto è proattivo il tuo agente. I livelli più alti usano più token.',
|
||||
'activityLevel.off': 'Disattivato',
|
||||
'activityLevel.offDesc':
|
||||
'Nessuna elaborazione in background. Sincronizza solo alla pressione del pulsante.',
|
||||
'activityLevel.minimal': 'Minimo',
|
||||
'activityLevel.minimalDesc':
|
||||
'Sincronizza le sorgenti una volta al giorno. Nessun messaggio proattivo.',
|
||||
'activityLevel.moderate': 'Moderato',
|
||||
'activityLevel.moderateDesc': 'Sincronizza ogni ora. Riepilogo giornaliero. Suggerisce azioni.',
|
||||
'activityLevel.active': 'Attivo',
|
||||
'activityLevel.activeDesc':
|
||||
'Sincronizza ogni 10 min. Monitora i canali, ordina e redige risposte.',
|
||||
'activityLevel.alwaysOn': 'Sempre attivo',
|
||||
'activityLevel.alwaysOnDesc':
|
||||
'Sincronizzazione in tempo reale. Piena autonomia entro i limiti definiti.',
|
||||
'activityLevel.currentMonth': 'Questo mese: ${amount}',
|
||||
'activityLevel.saved': 'Livello di attività aggiornato.',
|
||||
'activityLevel.default': 'predefinito',
|
||||
'activityLevel.costFree': '0 $',
|
||||
'activityLevel.costRange': '~${min}–${max}/mese',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Budget di sincronizzazione',
|
||||
'syncBudget.maxTokens': 'Token massimi per sincronizzazione',
|
||||
'syncBudget.maxTokensHelp':
|
||||
'Ferma la sincronizzazione dopo aver consumato questo numero di token.',
|
||||
'syncBudget.maxCost': 'Costo massimo per sincronizzazione (USD)',
|
||||
'syncBudget.maxCostHelp': 'Limite assoluto in dollari per ogni esecuzione di sincronizzazione.',
|
||||
'syncBudget.syncDepth': 'Profondità di sincronizzazione',
|
||||
'syncBudget.syncDepthHelp': 'Recupera solo gli elementi di questa finestra temporale.',
|
||||
'syncBudget.days7': 'Ultimi 7 giorni',
|
||||
'syncBudget.days30': 'Ultimi 30 giorni',
|
||||
'syncBudget.days90': 'Ultimi 90 giorni',
|
||||
'syncBudget.allTime': 'Sempre',
|
||||
'syncBudget.unlimited': 'Illimitato',
|
||||
'syncBudget.saved': 'Budget salvato.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Conferma sincronizzazione',
|
||||
'syncConfirm.message':
|
||||
'Questa sincronizzazione elaborerà ~{items} elementi (~{tokens} token, stima ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Limite budget: ${max}',
|
||||
'syncConfirm.proceed': 'Procedi',
|
||||
'syncConfirm.cancel': 'Annulla',
|
||||
'syncConfirm.estimating': 'Stima del costo in corso...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} questo mese',
|
||||
'monthlyCost.noData': 'Nessuna sincronizzazione questo mese',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Attività',
|
||||
'onboarding.custom.activity.title': "Attività dell'agente",
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Quanto proattivamente il tuo agente monitora e agisce in background.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'Attività moderata — sincronizzazione oraria, riepilogo giornaliero.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
"Scegli il tuo livello di attività. Configura in Impostazioni › Livello di attività dell'agente.",
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4415,6 +4415,62 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': '로컬 저장소 거부',
|
||||
'pages.settings.account.security': '보안',
|
||||
'pages.settings.account.securityDesc': '비밀 저장 모드 및 키체인 상태',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': '에이전트 활동 수준',
|
||||
'activityLevel.description':
|
||||
'에이전트의 능동성을 조절하세요. 높은 수준은 더 많은 토큰을 사용합니다.',
|
||||
'activityLevel.off': '끄기',
|
||||
'activityLevel.offDesc': '백그라운드 처리 없음. 버튼을 눌러야만 동기화됩니다.',
|
||||
'activityLevel.minimal': '최소',
|
||||
'activityLevel.minimalDesc': '소스를 하루에 한 번 동기화합니다. 능동적 메시지 없음.',
|
||||
'activityLevel.moderate': '보통',
|
||||
'activityLevel.moderateDesc': '매 시간 동기화. 일일 요약. 작업을 제안합니다.',
|
||||
'activityLevel.active': '활성',
|
||||
'activityLevel.activeDesc': '10분마다 동기화. 채널 모니터링, 분류, 답변 초안 작성.',
|
||||
'activityLevel.alwaysOn': '항상 켜짐',
|
||||
'activityLevel.alwaysOnDesc': '실시간 동기화. 가이드라인 내 완전한 자율성.',
|
||||
'activityLevel.currentMonth': '이번 달: ${amount}',
|
||||
'activityLevel.saved': '활동 수준이 업데이트되었습니다.',
|
||||
'activityLevel.default': '기본값',
|
||||
'activityLevel.costFree': '$0',
|
||||
'activityLevel.costRange': '~${min}–${max}/월',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': '동기화 예산',
|
||||
'syncBudget.maxTokens': '동기화당 최대 토큰',
|
||||
'syncBudget.maxTokensHelp': '이 토큰 수를 소비하면 동기화를 중지합니다.',
|
||||
'syncBudget.maxCost': '동기화당 최대 비용 (USD)',
|
||||
'syncBudget.maxCostHelp': '각 동기화 실행당 달러 한도.',
|
||||
'syncBudget.syncDepth': '동기화 깊이',
|
||||
'syncBudget.syncDepthHelp': '이 기간의 항목만 가져옵니다.',
|
||||
'syncBudget.days7': '최근 7일',
|
||||
'syncBudget.days30': '최근 30일',
|
||||
'syncBudget.days90': '최근 90일',
|
||||
'syncBudget.allTime': '전체 기간',
|
||||
'syncBudget.unlimited': '무제한',
|
||||
'syncBudget.saved': '예산이 저장되었습니다.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': '동기화 확인',
|
||||
'syncConfirm.message': '이 동기화는 ~{items}개 항목 (~{tokens} 토큰, 예상 ${cost})을 처리합니다.',
|
||||
'syncConfirm.budgetNote': '예산 한도: ${max}',
|
||||
'syncConfirm.proceed': '진행',
|
||||
'syncConfirm.cancel': '취소',
|
||||
'syncConfirm.estimating': '비용 계산 중...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '이번 달 ${amount}',
|
||||
'monthlyCost.noData': '이번 달 동기화 없음',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': '활동',
|
||||
'onboarding.custom.activity.title': '에이전트 활동',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'에이전트가 백그라운드에서 얼마나 능동적으로 모니터링하고 행동하는지.',
|
||||
'onboarding.custom.activity.defaultDesc': '보통 활동 — 매시간 동기화, 일일 요약.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'자신만의 활동 수준을 선택하세요. 설정 › 에이전트 활동 수준에서 구성하세요.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4524,6 +4524,66 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Odmów lokalnego przechowywania',
|
||||
'pages.settings.account.security': 'Bezpieczeństwo',
|
||||
'pages.settings.account.securityDesc': 'Tryb przechowywania sekretów i stan pęku kluczy',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'Poziom aktywności agenta',
|
||||
'activityLevel.description':
|
||||
'Kontroluj, jak proaktywny jest Twój agent. Wyższe poziomy zużywają więcej tokenów.',
|
||||
'activityLevel.off': 'Wyłączony',
|
||||
'activityLevel.offDesc':
|
||||
'Brak przetwarzania w tle. Synchronizuje tylko po naciśnięciu przycisku.',
|
||||
'activityLevel.minimal': 'Minimalny',
|
||||
'activityLevel.minimalDesc': 'Synchronizuje źródła raz dziennie. Brak proaktywnych wiadomości.',
|
||||
'activityLevel.moderate': 'Umiarkowany',
|
||||
'activityLevel.moderateDesc': 'Synchronizuje co godzinę. Codzienny skrót. Sugeruje działania.',
|
||||
'activityLevel.active': 'Aktywny',
|
||||
'activityLevel.activeDesc':
|
||||
'Synchronizuje co 10 min. Monitoruje kanały, porządkuje i redaguje odpowiedzi.',
|
||||
'activityLevel.alwaysOn': 'Zawsze włączony',
|
||||
'activityLevel.alwaysOnDesc':
|
||||
'Synchronizacja w czasie rzeczywistym. Pełna autonomia w granicach.',
|
||||
'activityLevel.currentMonth': 'W tym miesiącu: ${amount}',
|
||||
'activityLevel.saved': 'Poziom aktywności zaktualizowany.',
|
||||
'activityLevel.default': 'domyślny',
|
||||
'activityLevel.costFree': '0 $',
|
||||
'activityLevel.costRange': '~${min}–${max}/mies.',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Budżet synchronizacji',
|
||||
'syncBudget.maxTokens': 'Maks. tokenów na synchronizację',
|
||||
'syncBudget.maxTokensHelp': 'Zatrzymaj synchronizację po zużyciu tej liczby tokenów.',
|
||||
'syncBudget.maxCost': 'Maks. koszt na synchronizację (USD)',
|
||||
'syncBudget.maxCostHelp': 'Bezwzględny limit w dolarach na jedno uruchomienie synchronizacji.',
|
||||
'syncBudget.syncDepth': 'Głębokość synchronizacji',
|
||||
'syncBudget.syncDepthHelp': 'Pobieraj tylko elementy z tego okna czasowego.',
|
||||
'syncBudget.days7': 'Ostatnie 7 dni',
|
||||
'syncBudget.days30': 'Ostatnie 30 dni',
|
||||
'syncBudget.days90': 'Ostatnie 90 dni',
|
||||
'syncBudget.allTime': 'Cały czas',
|
||||
'syncBudget.unlimited': 'Bez limitu',
|
||||
'syncBudget.saved': 'Budżet zapisany.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Potwierdź synchronizację',
|
||||
'syncConfirm.message':
|
||||
'Ta synchronizacja przetworzy ~{items} elementów (~{tokens} tokenów, szacunkowo ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Limit budżetu: ${max}',
|
||||
'syncConfirm.proceed': 'Kontynuuj',
|
||||
'syncConfirm.cancel': 'Anuluj',
|
||||
'syncConfirm.estimating': 'Szacowanie kosztów...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} w tym miesiącu',
|
||||
'monthlyCost.noData': 'Brak synchronizacji w tym miesiącu',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Aktywność',
|
||||
'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.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4522,6 +4522,66 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Recusar armazenamento local',
|
||||
'pages.settings.account.security': 'Segurança',
|
||||
'pages.settings.account.securityDesc': 'Modo de armazenamento de segredos e status do chaveiro',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'Nível de atividade do agente',
|
||||
'activityLevel.description':
|
||||
'Controle o quão proativo é o seu agente. Níveis mais altos usam mais tokens.',
|
||||
'activityLevel.off': 'Desligado',
|
||||
'activityLevel.offDesc':
|
||||
'Nenhum processamento em segundo plano. Sincroniza apenas ao pressionar o botão.',
|
||||
'activityLevel.minimal': 'Mínimo',
|
||||
'activityLevel.minimalDesc': 'Sincroniza fontes uma vez por dia. Sem mensagens proativas.',
|
||||
'activityLevel.moderate': 'Moderado',
|
||||
'activityLevel.moderateDesc': 'Sincroniza a cada hora. Resumo diário. Sugere ações.',
|
||||
'activityLevel.active': 'Ativo',
|
||||
'activityLevel.activeDesc':
|
||||
'Sincroniza a cada 10 min. Monitora canais, prioriza e rascunha respostas.',
|
||||
'activityLevel.alwaysOn': 'Sempre ativo',
|
||||
'activityLevel.alwaysOnDesc': 'Sincronização em tempo real. Autonomia total dentro dos limites.',
|
||||
'activityLevel.currentMonth': 'Este mês: ${amount}',
|
||||
'activityLevel.saved': 'Nível de atividade atualizado.',
|
||||
'activityLevel.default': 'padrão',
|
||||
'activityLevel.costFree': '$0',
|
||||
'activityLevel.costRange': '~${min}–${max}/mês',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Orçamento de sincronização',
|
||||
'syncBudget.maxTokens': 'Tokens máximos por sincronização',
|
||||
'syncBudget.maxTokensHelp': 'Parar a sincronização ao consumir este número de tokens.',
|
||||
'syncBudget.maxCost': 'Custo máximo por sincronização (USD)',
|
||||
'syncBudget.maxCostHelp': 'Limite absoluto em dólares por execução de sincronização.',
|
||||
'syncBudget.syncDepth': 'Profundidade de sincronização',
|
||||
'syncBudget.syncDepthHelp': 'Buscar apenas itens desta janela de tempo.',
|
||||
'syncBudget.days7': 'Últimos 7 dias',
|
||||
'syncBudget.days30': 'Últimos 30 dias',
|
||||
'syncBudget.days90': 'Últimos 90 dias',
|
||||
'syncBudget.allTime': 'Todo o tempo',
|
||||
'syncBudget.unlimited': 'Ilimitado',
|
||||
'syncBudget.saved': 'Orçamento salvo.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Confirmar sincronização',
|
||||
'syncConfirm.message':
|
||||
'Esta sincronização processará ~{items} itens (~{tokens} tokens, est. ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Limite de orçamento: ${max}',
|
||||
'syncConfirm.proceed': 'Prosseguir',
|
||||
'syncConfirm.cancel': 'Cancelar',
|
||||
'syncConfirm.estimating': 'Estimando custo...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} este mês',
|
||||
'monthlyCost.noData': 'Sem sincronizações este mês',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Atividade',
|
||||
'onboarding.custom.activity.title': 'Atividade do agente',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'O quão proativamente seu agente monitora e age em segundo plano.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'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.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4493,6 +4493,67 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': 'Отклонить локальное хранилище',
|
||||
'pages.settings.account.security': 'Безопасность',
|
||||
'pages.settings.account.securityDesc': 'Режим хранения секретов и статус связки ключей',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': 'Уровень активности агента',
|
||||
'activityLevel.description':
|
||||
'Управляйте проактивностью вашего агента. Более высокие уровни потребляют больше токенов.',
|
||||
'activityLevel.off': 'Выкл.',
|
||||
'activityLevel.offDesc': 'Нет фоновой обработки. Синхронизация только по нажатию кнопки.',
|
||||
'activityLevel.minimal': 'Минимальный',
|
||||
'activityLevel.minimalDesc': 'Синхронизация источников раз в день. Нет проактивных сообщений.',
|
||||
'activityLevel.moderate': 'Умеренный',
|
||||
'activityLevel.moderateDesc': 'Синхронизация каждый час. Ежедневная сводка. Предлагает действия.',
|
||||
'activityLevel.active': 'Активный',
|
||||
'activityLevel.activeDesc':
|
||||
'Синхронизация каждые 10 мин. Мониторинг каналов, сортировка и составление ответов.',
|
||||
'activityLevel.alwaysOn': 'Всегда включён',
|
||||
'activityLevel.alwaysOnDesc':
|
||||
'Синхронизация в реальном времени. Полная автономия в рамках ограничений.',
|
||||
'activityLevel.currentMonth': 'В этом месяце: ${amount}',
|
||||
'activityLevel.saved': 'Уровень активности обновлён.',
|
||||
'activityLevel.default': 'по умолчанию',
|
||||
'activityLevel.costFree': '0 $',
|
||||
'activityLevel.costRange': '~${min}–${max}/мес.',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': 'Бюджет синхронизации',
|
||||
'syncBudget.maxTokens': 'Макс. токенов на синхронизацию',
|
||||
'syncBudget.maxTokensHelp':
|
||||
'Остановить синхронизацию после потребления указанного количества токенов.',
|
||||
'syncBudget.maxCost': 'Макс. стоимость на синхронизацию (USD)',
|
||||
'syncBudget.maxCostHelp': 'Жёсткий лимит в долларах за один запуск синхронизации.',
|
||||
'syncBudget.syncDepth': 'Глубина синхронизации',
|
||||
'syncBudget.syncDepthHelp': 'Загружать только элементы из этого временного окна.',
|
||||
'syncBudget.days7': 'Последние 7 дней',
|
||||
'syncBudget.days30': 'Последние 30 дней',
|
||||
'syncBudget.days90': 'Последние 90 дней',
|
||||
'syncBudget.allTime': 'За всё время',
|
||||
'syncBudget.unlimited': 'Без ограничений',
|
||||
'syncBudget.saved': 'Бюджет сохранён.',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': 'Подтвердить синхронизацию',
|
||||
'syncConfirm.message':
|
||||
'Эта синхронизация обработает ~{items} элементов (~{tokens} токенов, ест. ${cost}).',
|
||||
'syncConfirm.budgetNote': 'Лимит бюджета: ${max}',
|
||||
'syncConfirm.proceed': 'Продолжить',
|
||||
'syncConfirm.cancel': 'Отмена',
|
||||
'syncConfirm.estimating': 'Оценка стоимости...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '${amount} в этом месяце',
|
||||
'monthlyCost.noData': 'Синхронизаций в этом месяце нет',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': 'Активность',
|
||||
'onboarding.custom.activity.title': 'Активность агента',
|
||||
'onboarding.custom.activity.subtitle':
|
||||
'Насколько проактивно агент отслеживает события и действует в фоне.',
|
||||
'onboarding.custom.activity.defaultDesc':
|
||||
'Умеренная активность — синхронизация каждый час, ежедневная сводка.',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'Выберите свой уровень активности. Настройка в Параметры › Уровень активности агента.',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4236,6 +4236,60 @@ const messages: TranslationMap = {
|
||||
'keyring.settings.revokeConsent': '拒绝本地存储',
|
||||
'pages.settings.account.security': '安全',
|
||||
'pages.settings.account.securityDesc': '密钥存储模式和密钥链状态',
|
||||
|
||||
// Agent activity level
|
||||
'activityLevel.title': '智能体活动级别',
|
||||
'activityLevel.description': '控制您的智能体的主动程度。级别越高,消耗的令牌越多。',
|
||||
'activityLevel.off': '关闭',
|
||||
'activityLevel.offDesc': '无后台处理。仅在按下按钮时同步。',
|
||||
'activityLevel.minimal': '最低',
|
||||
'activityLevel.minimalDesc': '每天同步一次来源。无主动消息。',
|
||||
'activityLevel.moderate': '适中',
|
||||
'activityLevel.moderateDesc': '每小时同步。每日摘要。建议操作。',
|
||||
'activityLevel.active': '活跃',
|
||||
'activityLevel.activeDesc': '每10分钟同步。监控频道、分类并起草回复。',
|
||||
'activityLevel.alwaysOn': '始终开启',
|
||||
'activityLevel.alwaysOnDesc': '实时同步。在规定范围内完全自主。',
|
||||
'activityLevel.currentMonth': '本月:${amount}',
|
||||
'activityLevel.saved': '活动级别已更新。',
|
||||
'activityLevel.default': '默认',
|
||||
'activityLevel.costFree': '$0',
|
||||
'activityLevel.costRange': '~${min}–${max}/月',
|
||||
|
||||
// Sync budget dialog
|
||||
'syncBudget.title': '同步预算',
|
||||
'syncBudget.maxTokens': '每次同步最大令牌数',
|
||||
'syncBudget.maxTokensHelp': '消耗此数量的令牌后停止同步。',
|
||||
'syncBudget.maxCost': '每次同步最大费用(USD)',
|
||||
'syncBudget.maxCostHelp': '每次同步运行的绝对美元上限。',
|
||||
'syncBudget.syncDepth': '同步深度',
|
||||
'syncBudget.syncDepthHelp': '仅获取此时间窗口内的项目。',
|
||||
'syncBudget.days7': '最近7天',
|
||||
'syncBudget.days30': '最近30天',
|
||||
'syncBudget.days90': '最近90天',
|
||||
'syncBudget.allTime': '所有时间',
|
||||
'syncBudget.unlimited': '无限制',
|
||||
'syncBudget.saved': '预算已保存。',
|
||||
|
||||
// Sync confirm dialog
|
||||
'syncConfirm.title': '确认同步',
|
||||
'syncConfirm.message': '此同步将处理约{items}个项目(约{tokens}个令牌,预计${cost})。',
|
||||
'syncConfirm.budgetNote': '预算上限:${max}',
|
||||
'syncConfirm.proceed': '继续',
|
||||
'syncConfirm.cancel': '取消',
|
||||
'syncConfirm.estimating': '正在估算费用...',
|
||||
|
||||
// Monthly cost badge
|
||||
'monthlyCost.badge': '本月${amount}',
|
||||
'monthlyCost.noData': '本月无同步',
|
||||
|
||||
// Onboarding: Custom > Activity
|
||||
'onboarding.custom.stepperActivity': '活动',
|
||||
'onboarding.custom.activity.title': '智能体活动',
|
||||
'onboarding.custom.activity.subtitle': '您的智能体在后台监控和行动的主动程度。',
|
||||
'onboarding.custom.activity.defaultDesc': '适中活动——每小时同步,每日摘要。',
|
||||
'onboarding.custom.activity.configureDesc':
|
||||
'选择您自己的活动级别。在设置 › 智能体活动级别中配置。',
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -5,6 +5,7 @@ import CostDashboardPanel from '../components/dashboard/CostDashboardPanel';
|
||||
import LogoutAndClearActions from '../components/settings/LogoutAndClearActions';
|
||||
import AboutPanel from '../components/settings/panels/AboutPanel';
|
||||
import AgentAccessPanel from '../components/settings/panels/AgentAccessPanel';
|
||||
import AgentActivityPanel from '../components/settings/panels/AgentActivityPanel';
|
||||
import AgentChatPanel from '../components/settings/panels/AgentChatPanel';
|
||||
import AgentEditorPage from '../components/settings/panels/AgentEditorPage';
|
||||
import AgentsPanel from '../components/settings/panels/AgentsPanel';
|
||||
@@ -457,6 +458,13 @@ const Settings = () => {
|
||||
route: 'agent-access',
|
||||
icon: AgentAccessIcon,
|
||||
},
|
||||
{
|
||||
id: 'activity-level',
|
||||
title: t('activityLevel.title'),
|
||||
description: t('activityLevel.description'),
|
||||
route: 'activity-level',
|
||||
icon: LlmIcon,
|
||||
},
|
||||
];
|
||||
|
||||
const composioSettingsItems = [
|
||||
@@ -587,6 +595,7 @@ const Settings = () => {
|
||||
<Route path="persona" element={wrapSettingsPage(<PersonaPanel />)} />
|
||||
<Route path="appearance" element={wrapSettingsPage(<AppearancePanel />)} />
|
||||
<Route path="agent-access" element={wrapSettingsPage(<AgentAccessPanel />)} />
|
||||
<Route path="activity-level" element={wrapSettingsPage(<AgentActivityPanel />)} />
|
||||
<Route path="approval-history" element={wrapSettingsPage(<ApprovalHistoryPanel />)} />
|
||||
<Route path="agents" element={wrapSettingsPage(<AgentsPanel />)} />
|
||||
<Route path="agents/new" element={wrapSettingsPage(<AgentEditorPage />)} />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
|
||||
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;
|
||||
@@ -39,6 +40,7 @@ const Onboarding = () => {
|
||||
<Route path="custom/oauth" element={<CustomOAuthPage />} />
|
||||
<Route path="custom/search" element={<CustomSearchPage />} />
|
||||
<Route path="custom/embeddings" element={<CustomEmbeddingsPage />} />
|
||||
<Route path="custom/activity" element={<CustomActivityPage />} />
|
||||
{/* <Route path="custom/memory" element={<CustomMemoryPage />} /> */}
|
||||
<Route path="*" element={<Navigate to="welcome" replace />} />
|
||||
</Route>
|
||||
|
||||
@@ -2,7 +2,14 @@ import { createContext, useContext } from 'react';
|
||||
|
||||
export type AiMode = 'cloud' | 'custom';
|
||||
|
||||
export type CustomStepKey = 'inference' | 'voice' | 'oauth' | 'search' | 'embeddings' | 'memory';
|
||||
export type CustomStepKey =
|
||||
| 'inference'
|
||||
| 'voice'
|
||||
| 'oauth'
|
||||
| 'search'
|
||||
| 'embeddings'
|
||||
| 'memory'
|
||||
| 'activity';
|
||||
export type CustomStepChoice = 'default' | 'configure';
|
||||
|
||||
export interface OnboardingDraft {
|
||||
|
||||
@@ -9,6 +9,7 @@ export const CUSTOM_WIZARD_STEPS: CustomStepKey[] = [
|
||||
'oauth',
|
||||
'search',
|
||||
'embeddings',
|
||||
'activity',
|
||||
// 'memory',
|
||||
];
|
||||
|
||||
@@ -18,6 +19,7 @@ export const CUSTOM_WIZARD_ROUTES: Record<CustomStepKey, string> = {
|
||||
oauth: '/onboarding/custom/oauth',
|
||||
search: '/onboarding/custom/search',
|
||||
embeddings: '/onboarding/custom/embeddings',
|
||||
activity: '/onboarding/custom/activity',
|
||||
memory: '/onboarding/custom/memory',
|
||||
};
|
||||
|
||||
@@ -29,5 +31,6 @@ export const CUSTOM_WIZARD_SETTINGS_ROUTES: Record<CustomStepKey, string> = {
|
||||
oauth: '/settings/composio-routing',
|
||||
search: '/settings/tools',
|
||||
embeddings: '/settings/embeddings',
|
||||
activity: '/settings/activity-level',
|
||||
memory: '/settings/memory-data',
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import AgentActivityPanel from '../../../components/settings/panels/AgentActivityPanel';
|
||||
import CustomWizardConfigPage from './CustomWizardConfigPage';
|
||||
|
||||
const CustomActivityPage = () => (
|
||||
<CustomWizardConfigPage stepKey="activity" configureContent={<AgentActivityPanel />} />
|
||||
);
|
||||
|
||||
export default CustomActivityPage;
|
||||
@@ -114,6 +114,7 @@ const CustomWizardStep = ({
|
||||
t('onboarding.custom.stepperOAuth'),
|
||||
t('onboarding.custom.stepperSearch'),
|
||||
t('onboarding.custom.stepperEmbeddings'),
|
||||
t('onboarding.custom.stepperActivity'),
|
||||
t('onboarding.custom.stepperMemory'),
|
||||
].slice(0, stepCount);
|
||||
|
||||
|
||||
@@ -913,6 +913,105 @@ pub async fn load_and_apply_autonomy_settings(
|
||||
apply_autonomy_settings(&mut config, update).await
|
||||
}
|
||||
|
||||
// ── Agent Activity Level ───────────────────────────────────────────────
|
||||
|
||||
/// Partial update for the agent activity level (0–4).
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ActivityLevelSettingsPatch {
|
||||
/// "off" | "minimal" | "moderate" | "active" | "always_on" (or "0"-"4").
|
||||
pub level: Option<String>,
|
||||
}
|
||||
|
||||
/// Returns the current activity level and its derived settings.
|
||||
pub async fn get_activity_level_settings() -> Result<RpcOutcome<serde_json::Value>, String> {
|
||||
let config = load_config_with_timeout().await?;
|
||||
let level = config.agent_activity_level;
|
||||
let (cost_min, cost_max) = level.estimated_monthly_cost_range();
|
||||
let value = serde_json::json!({
|
||||
"level": level as u8,
|
||||
"level_label": level.as_str(),
|
||||
"sync_interval_secs": level.sync_interval_secs(),
|
||||
"heartbeat_enabled": level.heartbeat_enabled(),
|
||||
"subconscious_enabled": level.subconscious_enabled(),
|
||||
"token_budget_per_cycle": level.token_budget_per_cycle(),
|
||||
"estimated_monthly_cost_min_usd": cost_min,
|
||||
"estimated_monthly_cost_max_usd": cost_max,
|
||||
});
|
||||
Ok(RpcOutcome::single_log(
|
||||
value,
|
||||
"activity level settings read",
|
||||
))
|
||||
}
|
||||
|
||||
/// Updates the agent activity level and pushes it into the scheduler gate.
|
||||
pub async fn apply_activity_level_settings(
|
||||
config: &mut Config,
|
||||
update: ActivityLevelSettingsPatch,
|
||||
) -> Result<RpcOutcome<serde_json::Value>, String> {
|
||||
use crate::openhuman::config::schema::activity_level::AgentActivityLevel;
|
||||
use crate::openhuman::config::SchedulerGateMode;
|
||||
|
||||
if let Some(level_str) = update.level {
|
||||
let level = AgentActivityLevel::from_str_opt(&level_str).ok_or_else(|| {
|
||||
format!(
|
||||
"invalid activity level '{}' \
|
||||
(expected off|minimal|moderate|active|always_on or 0-4)",
|
||||
level_str
|
||||
)
|
||||
})?;
|
||||
config.agent_activity_level = level;
|
||||
}
|
||||
|
||||
// Derive the gate mode from the (possibly updated) activity level and
|
||||
// persist it alongside the level so the saved config is self-consistent.
|
||||
let level = config.agent_activity_level;
|
||||
let gate_mode = match level {
|
||||
AgentActivityLevel::Off => SchedulerGateMode::Off,
|
||||
AgentActivityLevel::Minimal | AgentActivityLevel::Moderate => SchedulerGateMode::Auto,
|
||||
AgentActivityLevel::Active | AgentActivityLevel::AlwaysOn => SchedulerGateMode::AlwaysOn,
|
||||
};
|
||||
config.scheduler_gate.mode = gate_mode;
|
||||
|
||||
config.save().await.map_err(|e| e.to_string())?;
|
||||
|
||||
let gate_cfg = config.scheduler_gate.clone();
|
||||
crate::openhuman::scheduler_gate::gate::update_config(gate_cfg);
|
||||
|
||||
tracing::info!(
|
||||
level = %level.as_str(),
|
||||
gate_mode = %gate_mode.as_str(),
|
||||
"[config:activity_level] activity level updated"
|
||||
);
|
||||
|
||||
let (cost_min, cost_max) = level.estimated_monthly_cost_range();
|
||||
let value = serde_json::json!({
|
||||
"level": level as u8,
|
||||
"level_label": level.as_str(),
|
||||
"sync_interval_secs": level.sync_interval_secs(),
|
||||
"heartbeat_enabled": level.heartbeat_enabled(),
|
||||
"subconscious_enabled": level.subconscious_enabled(),
|
||||
"token_budget_per_cycle": level.token_budget_per_cycle(),
|
||||
"estimated_monthly_cost_min_usd": cost_min,
|
||||
"estimated_monthly_cost_max_usd": cost_max,
|
||||
});
|
||||
Ok(RpcOutcome::new(
|
||||
value,
|
||||
vec![format!(
|
||||
"activity level set to '{}' — saved to {}",
|
||||
level.as_str(),
|
||||
config.config_path.display()
|
||||
)],
|
||||
))
|
||||
}
|
||||
|
||||
/// Loads the configuration, applies activity level settings, and saves it.
|
||||
pub async fn load_and_apply_activity_level_settings(
|
||||
update: ActivityLevelSettingsPatch,
|
||||
) -> Result<RpcOutcome<serde_json::Value>, String> {
|
||||
let mut config = load_config_with_timeout().await?;
|
||||
apply_activity_level_settings(&mut config, update).await
|
||||
}
|
||||
|
||||
/// Serializes the load-modify-save in [`add_auto_approve_tool`] so two
|
||||
/// concurrent "Always allow" appends (different tools) can't read the same
|
||||
/// `auto_approve`, each push their own, and clobber the other on save
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
//! Agent activity level — controls how proactive background AI is.
|
||||
//!
|
||||
//! Maps a single 0–4 knob into scheduler-gate mode, periodic sync
|
||||
//! cadence, heartbeat/subconscious toggles, and token budgets.
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
/// User-facing activity level for background AI work.
|
||||
///
|
||||
/// Each level is an opinionated preset that controls multiple subsystems:
|
||||
/// - Scheduler-gate mode (off / auto / always_on)
|
||||
/// - Periodic sync cadence (never / daily / hourly / 10min / realtime)
|
||||
/// - Heartbeat & subconscious inference (disabled / enabled)
|
||||
/// - Token budget per background cycle
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr, JsonSchema)]
|
||||
#[repr(u8)]
|
||||
pub enum AgentActivityLevel {
|
||||
/// No background processing. Syncs only on manual press.
|
||||
Off = 0,
|
||||
/// Sync sources once per day. No proactive messages.
|
||||
Minimal = 1,
|
||||
/// Sync every hour. Daily digest. Suggests actions. (default)
|
||||
Moderate = 2,
|
||||
/// Sync every 10 min. Monitors channels, triages, drafts replies.
|
||||
Active = 3,
|
||||
/// Real-time sync. Full autonomy within guardrails.
|
||||
AlwaysOn = 4,
|
||||
}
|
||||
|
||||
impl AgentActivityLevel {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Off => "off",
|
||||
Self::Minimal => "minimal",
|
||||
Self::Moderate => "moderate",
|
||||
Self::Active => "active",
|
||||
Self::AlwaysOn => "always_on",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str_opt(s: &str) -> Option<Self> {
|
||||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"off" | "0" => Some(Self::Off),
|
||||
"minimal" | "1" => Some(Self::Minimal),
|
||||
"moderate" | "2" => Some(Self::Moderate),
|
||||
"active" | "3" => Some(Self::Active),
|
||||
"always_on" | "alwayson" | "4" => Some(Self::AlwaysOn),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Periodic sync interval in seconds for this level.
|
||||
/// Returns None for Off (manual-only).
|
||||
pub fn sync_interval_secs(self) -> Option<u64> {
|
||||
match self {
|
||||
Self::Off => None,
|
||||
Self::Minimal => Some(86_400), // 24h
|
||||
Self::Moderate => Some(3_600), // 1h
|
||||
Self::Active => Some(600), // 10min
|
||||
Self::AlwaysOn => Some(60), // 1min
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether heartbeat inference should run at this level.
|
||||
pub fn heartbeat_enabled(self) -> bool {
|
||||
matches!(self, Self::Moderate | Self::Active | Self::AlwaysOn)
|
||||
}
|
||||
|
||||
/// Whether subconscious background reasoning should run.
|
||||
pub fn subconscious_enabled(self) -> bool {
|
||||
matches!(self, Self::Active | Self::AlwaysOn)
|
||||
}
|
||||
|
||||
/// Per-background-cycle token budget. None = unlimited.
|
||||
pub fn token_budget_per_cycle(self) -> Option<u64> {
|
||||
match self {
|
||||
Self::Off => Some(0),
|
||||
Self::Minimal => Some(100_000),
|
||||
Self::Moderate => Some(500_000),
|
||||
Self::Active => Some(2_000_000),
|
||||
Self::AlwaysOn => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Estimated monthly cost range (min, max) in USD for display.
|
||||
pub fn estimated_monthly_cost_range(self) -> (f64, f64) {
|
||||
match self {
|
||||
Self::Off => (0.0, 0.0),
|
||||
Self::Minimal => (0.10, 0.50),
|
||||
Self::Moderate => (1.0, 5.0),
|
||||
Self::Active => (5.0, 20.0),
|
||||
Self::AlwaysOn => (20.0, 100.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AgentActivityLevel {
|
||||
fn default() -> Self {
|
||||
Self::Moderate
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_is_moderate() {
|
||||
assert_eq!(AgentActivityLevel::default(), AgentActivityLevel::Moderate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str_round_trips() {
|
||||
for level in [
|
||||
AgentActivityLevel::Off,
|
||||
AgentActivityLevel::Minimal,
|
||||
AgentActivityLevel::Moderate,
|
||||
AgentActivityLevel::Active,
|
||||
AgentActivityLevel::AlwaysOn,
|
||||
] {
|
||||
let parsed = AgentActivityLevel::from_str_opt(level.as_str()).unwrap();
|
||||
assert_eq!(parsed, level);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_repr_round_trips() {
|
||||
for level in [
|
||||
AgentActivityLevel::Off,
|
||||
AgentActivityLevel::Minimal,
|
||||
AgentActivityLevel::Moderate,
|
||||
AgentActivityLevel::Active,
|
||||
AgentActivityLevel::AlwaysOn,
|
||||
] {
|
||||
let json = serde_json::to_string(&level).unwrap();
|
||||
let parsed: AgentActivityLevel = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed, level);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_interval_none_for_off() {
|
||||
assert_eq!(AgentActivityLevel::Off.sync_interval_secs(), None);
|
||||
assert_eq!(
|
||||
AgentActivityLevel::Minimal.sync_interval_secs(),
|
||||
Some(86_400)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heartbeat_disabled_for_low_levels() {
|
||||
assert!(!AgentActivityLevel::Off.heartbeat_enabled());
|
||||
assert!(!AgentActivityLevel::Minimal.heartbeat_enabled());
|
||||
assert!(AgentActivityLevel::Moderate.heartbeat_enabled());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! Split into submodules; this module re-exports the main `Config` and all public types.
|
||||
|
||||
pub mod activity_level;
|
||||
pub use activity_level::AgentActivityLevel;
|
||||
pub mod cloud_providers;
|
||||
pub use cloud_providers::{
|
||||
generate_provider_id, is_slug_reserved, migrate_legacy_fields, AuthStyle, CloudProviderCreds,
|
||||
|
||||
@@ -123,6 +123,12 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub scheduler_gate: SchedulerGateConfig,
|
||||
|
||||
/// User-facing activity-level knob (0–4) controlling how proactive
|
||||
/// background AI work is. Maps into scheduler_gate mode, periodic sync
|
||||
/// cadence, heartbeat/subconscious toggles. See issue #3117.
|
||||
#[serde(default)]
|
||||
pub agent_activity_level: AgentActivityLevel,
|
||||
|
||||
#[serde(default)]
|
||||
pub agent: AgentConfig,
|
||||
|
||||
@@ -653,6 +659,7 @@ impl Default for Config {
|
||||
reliability: ReliabilityConfig::default(),
|
||||
scheduler: SchedulerConfig::default(),
|
||||
scheduler_gate: SchedulerGateConfig::default(),
|
||||
agent_activity_level: AgentActivityLevel::default(),
|
||||
agent: AgentConfig::default(),
|
||||
orchestrator: OrchestratorModelConfig::default(),
|
||||
teams: HashMap::new(),
|
||||
|
||||
@@ -228,6 +228,12 @@ struct AgentSettingsUpdate {
|
||||
agent_timeout_secs: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ActivityLevelSettingsUpdate {
|
||||
/// "off" | "minimal" | "moderate" | "active" | "always_on" (or "0"-"4").
|
||||
level: Option<String>,
|
||||
}
|
||||
|
||||
pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
vec![
|
||||
schemas("get_config"),
|
||||
@@ -265,6 +271,8 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
schemas("update_agent_settings"),
|
||||
schemas("update_search_settings"),
|
||||
schemas("get_search_settings"),
|
||||
schemas("get_activity_level_settings"),
|
||||
schemas("update_activity_level_settings"),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -410,6 +418,14 @@ pub fn all_registered_controllers() -> Vec<RegisteredController> {
|
||||
schema: schemas("get_search_settings"),
|
||||
handler: handle_get_search_settings,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("get_activity_level_settings"),
|
||||
handler: handle_get_activity_level_settings,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("update_activity_level_settings"),
|
||||
handler: handle_update_activity_level_settings,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -912,6 +928,20 @@ pub fn schemas(function: &str) -> ControllerSchema {
|
||||
"Engine, effective engine, limits, and per-provider configuration flags.",
|
||||
)],
|
||||
},
|
||||
"get_activity_level_settings" => ControllerSchema {
|
||||
namespace: "config",
|
||||
function: "get_activity_level_settings",
|
||||
description: "Get the agent activity level (0–4) and its derived settings: sync cadence, heartbeat/subconscious toggles, token budget, estimated monthly cost.",
|
||||
inputs: vec![],
|
||||
outputs: vec![json_output("settings", "Activity level settings with cost estimates.")],
|
||||
},
|
||||
"update_activity_level_settings" => ControllerSchema {
|
||||
namespace: "config",
|
||||
function: "update_activity_level_settings",
|
||||
description: "Set the agent activity level. Immediately updates the scheduler gate mode and persists the change.",
|
||||
inputs: vec![optional_string("level", "Activity level: off | minimal | moderate | active | always_on (or 0–4).")],
|
||||
outputs: vec![json_output("settings", "Updated activity level settings with cost estimates.")],
|
||||
},
|
||||
"agent_server_status" => ControllerSchema {
|
||||
namespace: "config",
|
||||
function: "agent_server_status",
|
||||
@@ -1657,6 +1687,20 @@ fn handle_get_search_settings(_params: Map<String, Value>) -> ControllerFuture {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_get_activity_level_settings(_params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move { to_json(config_rpc::get_activity_level_settings().await?) })
|
||||
}
|
||||
|
||||
fn handle_update_activity_level_settings(params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move {
|
||||
let update = deserialize_params::<ActivityLevelSettingsUpdate>(params)?;
|
||||
let patch = config_rpc::ActivityLevelSettingsPatch {
|
||||
level: update.level,
|
||||
};
|
||||
to_json(config_rpc::load_and_apply_activity_level_settings(patch).await?)
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize_params<T: DeserializeOwned>(params: Map<String, Value>) -> Result<T, String> {
|
||||
serde_json::from_value(Value::Object(params)).map_err(|e| format!("invalid params: {e}"))
|
||||
}
|
||||
|
||||
@@ -90,6 +90,9 @@ mod tests {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,9 @@ mod tests {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,9 @@ mod tests {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,15 @@ pub async fn update_source(
|
||||
if let Some(selector) = patch.selector {
|
||||
entry.selector = Some(selector);
|
||||
}
|
||||
if let Some(v) = patch.max_tokens_per_sync {
|
||||
entry.max_tokens_per_sync = Some(v);
|
||||
}
|
||||
if let Some(v) = patch.max_cost_per_sync_usd {
|
||||
entry.max_cost_per_sync_usd = Some(v);
|
||||
}
|
||||
if let Some(v) = patch.sync_depth_days {
|
||||
entry.sync_depth_days = Some(v);
|
||||
}
|
||||
|
||||
entry.validate()?;
|
||||
let updated = entry.clone();
|
||||
@@ -212,6 +221,9 @@ pub async fn upsert_composio_source(
|
||||
since_days: None,
|
||||
max_items: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
};
|
||||
config.memory_sources.push(entry.clone());
|
||||
config
|
||||
@@ -257,6 +269,12 @@ pub struct MemorySourcePatch {
|
||||
pub max_items: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub selector: Option<String>,
|
||||
#[serde(default)]
|
||||
pub max_tokens_per_sync: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub max_cost_per_sync_usd: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub sync_depth_days: Option<u32>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -81,6 +81,12 @@ pub struct AddRequest {
|
||||
pub max_items: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub selector: Option<String>,
|
||||
#[serde(default)]
|
||||
pub max_tokens_per_sync: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub max_cost_per_sync_usd: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub sync_depth_days: Option<u32>,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
@@ -121,6 +127,9 @@ pub async fn add_rpc(req: AddRequest) -> Result<RpcOutcome<AddResponse>, String>
|
||||
since_days: req.since_days,
|
||||
max_items: req.max_items,
|
||||
selector: req.selector,
|
||||
max_tokens_per_sync: req.max_tokens_per_sync,
|
||||
max_cost_per_sync_usd: req.max_cost_per_sync_usd,
|
||||
sync_depth_days: req.sync_depth_days,
|
||||
};
|
||||
|
||||
let source = registry::add_source(entry).await?;
|
||||
@@ -282,3 +291,106 @@ pub async fn sync_audit_log_rpc() -> Result<RpcOutcome<SyncAuditLogResponse>, St
|
||||
let entries = crate::openhuman::memory_sync::sources::audit::read_audit_log(&config);
|
||||
Ok(RpcOutcome::new(SyncAuditLogResponse { entries }, vec![]))
|
||||
}
|
||||
|
||||
// ── Estimate Sync Cost ──
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct EstimateSyncCostRequest {
|
||||
pub source_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct EstimateSyncCostResponse {
|
||||
pub source_id: String,
|
||||
pub item_count: u32,
|
||||
pub estimated_tokens: u64,
|
||||
pub estimated_cost_usd: f64,
|
||||
pub budget_max_cost_usd: Option<f64>,
|
||||
pub budget_max_tokens: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn estimate_sync_cost_rpc(
|
||||
req: EstimateSyncCostRequest,
|
||||
) -> Result<RpcOutcome<EstimateSyncCostResponse>, String> {
|
||||
tracing::debug!(source_id = %req.source_id, "[memory_sources] estimate_sync_cost_rpc: entry");
|
||||
|
||||
let source = registry::get_source(&req.source_id)
|
||||
.await?
|
||||
.ok_or_else(|| format!("source '{}' not found", req.source_id))?;
|
||||
|
||||
let config = config_rpc::load_config_with_timeout().await?;
|
||||
let reader = readers::reader_for(&source.kind);
|
||||
let items = reader.list_items(&source, &config).await?;
|
||||
|
||||
let item_count = items.len() as u32;
|
||||
// estimated_tokens includes both input (500/item) and output (100/item)
|
||||
// to be consistent with the cost calculation below.
|
||||
let estimated_input_tokens = item_count as u64 * 500;
|
||||
let estimated_output_tokens = item_count as u64 * 100;
|
||||
let estimated_tokens = estimated_input_tokens + estimated_output_tokens;
|
||||
let estimated_cost_usd = crate::openhuman::memory_sync::sources::audit::estimate_cost_usd(
|
||||
estimated_input_tokens,
|
||||
estimated_output_tokens,
|
||||
);
|
||||
|
||||
Ok(RpcOutcome::new(
|
||||
EstimateSyncCostResponse {
|
||||
source_id: req.source_id,
|
||||
item_count,
|
||||
estimated_tokens,
|
||||
estimated_cost_usd,
|
||||
budget_max_cost_usd: source.max_cost_per_sync_usd,
|
||||
budget_max_tokens: source.max_tokens_per_sync,
|
||||
},
|
||||
vec![],
|
||||
))
|
||||
}
|
||||
|
||||
// ── Monthly Cost Summary ──
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct MonthlyCostSummaryResponse {
|
||||
pub month: String,
|
||||
pub total_cost_usd: f64,
|
||||
pub total_syncs: u32,
|
||||
pub total_items: u32,
|
||||
pub total_input_tokens: u64,
|
||||
pub total_output_tokens: u64,
|
||||
}
|
||||
|
||||
pub async fn monthly_cost_summary_rpc() -> Result<RpcOutcome<MonthlyCostSummaryResponse>, String> {
|
||||
tracing::debug!("[memory_sources] monthly_cost_summary_rpc: entry");
|
||||
let config = config_rpc::load_config_with_timeout().await?;
|
||||
let entries = crate::openhuman::memory_sync::sources::audit::read_audit_log(&config);
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let month_str = now.format("%Y-%m").to_string();
|
||||
|
||||
let mut total_cost_usd = 0.0f64;
|
||||
let mut total_syncs = 0u32;
|
||||
let mut total_items = 0u32;
|
||||
let mut total_input_tokens = 0u64;
|
||||
let mut total_output_tokens = 0u64;
|
||||
|
||||
for entry in &entries {
|
||||
if entry.timestamp.format("%Y-%m").to_string() == month_str {
|
||||
total_cost_usd += entry.effective_cost_usd();
|
||||
total_syncs += 1;
|
||||
total_items += entry.items_fetched;
|
||||
total_input_tokens += entry.input_tokens;
|
||||
total_output_tokens += entry.output_tokens;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RpcOutcome::new(
|
||||
MonthlyCostSummaryResponse {
|
||||
month: month_str,
|
||||
total_cost_usd,
|
||||
total_syncs,
|
||||
total_items,
|
||||
total_input_tokens,
|
||||
total_output_tokens,
|
||||
},
|
||||
vec![],
|
||||
))
|
||||
}
|
||||
|
||||
@@ -79,6 +79,24 @@ fn kind_specific_fields() -> Vec<FieldSchema> {
|
||||
comment: "CSS selector for web_page sources.",
|
||||
required: false,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "max_tokens_per_sync",
|
||||
ty: TypeSchema::Option(Box::new(TypeSchema::U64)),
|
||||
comment: "Max tokens per sync run.",
|
||||
required: false,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "max_cost_per_sync_usd",
|
||||
ty: TypeSchema::Option(Box::new(TypeSchema::F64)),
|
||||
comment: "Max cost per sync run in USD.",
|
||||
required: false,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "sync_depth_days",
|
||||
ty: TypeSchema::Option(Box::new(TypeSchema::U64)),
|
||||
comment: "Only sync items from the last N days.",
|
||||
required: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -94,6 +112,8 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
schemas("sync"),
|
||||
schemas("status_list"),
|
||||
schemas("sync_audit_log"),
|
||||
schemas("estimate_sync_cost"),
|
||||
schemas("monthly_cost_summary"),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -139,6 +159,14 @@ pub fn all_registered_controllers() -> Vec<RegisteredController> {
|
||||
schema: schemas("sync_audit_log"),
|
||||
handler: handle_sync_audit_log,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("estimate_sync_cost"),
|
||||
handler: handle_estimate_sync_cost,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("monthly_cost_summary"),
|
||||
handler: handle_monthly_cost_summary,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -364,6 +392,101 @@ pub fn schemas(function: &str) -> ControllerSchema {
|
||||
required: true,
|
||||
}],
|
||||
},
|
||||
"estimate_sync_cost" => ControllerSchema {
|
||||
namespace: NAMESPACE,
|
||||
function: "estimate_sync_cost",
|
||||
description:
|
||||
"Estimate the cost of syncing a source before starting. Returns item count, \
|
||||
estimated tokens, and estimated cost in USD.",
|
||||
inputs: vec![FieldSchema {
|
||||
name: "source_id",
|
||||
ty: TypeSchema::String,
|
||||
comment: "Source id to estimate.",
|
||||
required: true,
|
||||
}],
|
||||
outputs: vec![
|
||||
FieldSchema {
|
||||
name: "source_id",
|
||||
ty: TypeSchema::String,
|
||||
comment: "Echo of source id.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "item_count",
|
||||
ty: TypeSchema::U64,
|
||||
comment: "Number of items to sync.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "estimated_tokens",
|
||||
ty: TypeSchema::U64,
|
||||
comment: "Estimated input tokens.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "estimated_cost_usd",
|
||||
ty: TypeSchema::F64,
|
||||
comment: "Estimated cost in USD.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "budget_max_cost_usd",
|
||||
ty: TypeSchema::Option(Box::new(TypeSchema::F64)),
|
||||
comment: "Configured cost cap if set.",
|
||||
required: false,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "budget_max_tokens",
|
||||
ty: TypeSchema::Option(Box::new(TypeSchema::U64)),
|
||||
comment: "Configured token cap if set.",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"monthly_cost_summary" => ControllerSchema {
|
||||
namespace: NAMESPACE,
|
||||
function: "monthly_cost_summary",
|
||||
description: "Aggregate sync costs for the current calendar month.",
|
||||
inputs: vec![],
|
||||
outputs: vec![
|
||||
FieldSchema {
|
||||
name: "month",
|
||||
ty: TypeSchema::String,
|
||||
comment: "YYYY-MM.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "total_cost_usd",
|
||||
ty: TypeSchema::F64,
|
||||
comment: "Total spend in USD.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "total_syncs",
|
||||
ty: TypeSchema::U64,
|
||||
comment: "Number of sync runs.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "total_items",
|
||||
ty: TypeSchema::U64,
|
||||
comment: "Total items fetched.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "total_input_tokens",
|
||||
ty: TypeSchema::U64,
|
||||
comment: "Total input tokens.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "total_output_tokens",
|
||||
ty: TypeSchema::U64,
|
||||
comment: "Total output tokens.",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
other => panic!("unknown memory_sources schema function: {other}"),
|
||||
}
|
||||
}
|
||||
@@ -429,6 +552,17 @@ fn handle_sync_audit_log(_params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move { to_json(rpc::sync_audit_log_rpc().await?) })
|
||||
}
|
||||
|
||||
fn handle_estimate_sync_cost(params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move {
|
||||
let req = parse_value::<rpc::EstimateSyncCostRequest>(Value::Object(params))?;
|
||||
to_json(rpc::estimate_sync_cost_rpc(req).await?)
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_monthly_cost_summary(_params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move { to_json(rpc::monthly_cost_summary_rpc().await?) })
|
||||
}
|
||||
|
||||
fn parse_value<T: DeserializeOwned>(v: Value) -> Result<T, String> {
|
||||
serde_json::from_value(v).map_err(|e| format!("invalid params: {e}"))
|
||||
}
|
||||
|
||||
@@ -179,6 +179,9 @@ mod tests {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
};
|
||||
assert_eq!(source_id_prefix(&entry), "mem_src:src_abc:%");
|
||||
|
||||
|
||||
@@ -88,6 +88,17 @@ pub struct MemorySourceEntry {
|
||||
// ── WebPage ──
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub selector: Option<String>,
|
||||
|
||||
// ── Sync Budget (all source kinds) ──
|
||||
/// Maximum tokens to consume per sync run. Sync stops once this budget is hit.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_tokens_per_sync: Option<u64>,
|
||||
/// Maximum cost in USD per sync run. Refuses LLM calls once reached.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub max_cost_per_sync_usd: Option<f64>,
|
||||
/// Sync depth in days — only fetch items from the last N days.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sync_depth_days: Option<u32>,
|
||||
}
|
||||
|
||||
impl MemorySourceEntry {
|
||||
@@ -262,6 +273,9 @@ mod tests {
|
||||
since_days: None,
|
||||
max_items: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,9 @@ fn source_entry(id: &str, kind: SourceKind, label: &str) -> MemorySourceEntry {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2783,6 +2783,7 @@ async fn worker_a_controller_schemas_are_fully_exposed() {
|
||||
vec![
|
||||
"openhuman.config_agent_server_status",
|
||||
"openhuman.config_get",
|
||||
"openhuman.config_get_activity_level_settings",
|
||||
"openhuman.config_get_agent_settings",
|
||||
"openhuman.config_get_analytics_settings",
|
||||
"openhuman.config_get_autonomy_settings",
|
||||
@@ -2800,6 +2801,7 @@ async fn worker_a_controller_schemas_are_fully_exposed() {
|
||||
"openhuman.config_resolve_api_url",
|
||||
"openhuman.config_set_browser_allow_all",
|
||||
"openhuman.config_set_onboarding_completed",
|
||||
"openhuman.config_update_activity_level_settings",
|
||||
"openhuman.config_update_agent_settings",
|
||||
"openhuman.config_update_analytics_settings",
|
||||
"openhuman.config_update_autonomy_settings",
|
||||
@@ -2846,6 +2848,23 @@ async fn worker_a_controller_schemas_are_fully_exposed() {
|
||||
],
|
||||
),
|
||||
("connectivity", vec!["openhuman.connectivity_diag"]),
|
||||
(
|
||||
"memory_sources",
|
||||
vec![
|
||||
"openhuman.memory_sources_add",
|
||||
"openhuman.memory_sources_estimate_sync_cost",
|
||||
"openhuman.memory_sources_get",
|
||||
"openhuman.memory_sources_list",
|
||||
"openhuman.memory_sources_list_items",
|
||||
"openhuman.memory_sources_monthly_cost_summary",
|
||||
"openhuman.memory_sources_read_item",
|
||||
"openhuman.memory_sources_remove",
|
||||
"openhuman.memory_sources_status_list",
|
||||
"openhuman.memory_sources_sync",
|
||||
"openhuman.memory_sources_sync_audit_log",
|
||||
"openhuman.memory_sources_update",
|
||||
],
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
schema_method_names(&schema, namespace),
|
||||
|
||||
@@ -71,6 +71,9 @@ fn source_entry(kind: SourceKind, id: &str) -> MemorySourceEntry {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,9 @@ fn source_entry(id: &str, kind: SourceKind) -> MemorySourceEntry {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,9 @@ fn source_entry(id: &str, kind: SourceKind) -> MemorySourceEntry {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,9 @@ fn source(kind: SourceKind, id: &str) -> MemorySourceEntry {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,9 @@ fn source(kind: SourceKind, id: &str) -> MemorySourceEntry {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3818,6 +3821,9 @@ async fn memory_sources_registry_rpc_and_schema_handlers_cover_crud_edges() {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
@@ -3841,6 +3847,9 @@ async fn memory_sources_registry_rpc_and_schema_handlers_cover_crud_edges() {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
})
|
||||
.await
|
||||
.expect("add folder")
|
||||
@@ -3865,6 +3874,9 @@ async fn memory_sources_registry_rpc_and_schema_handlers_cover_crud_edges() {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
@@ -197,6 +197,9 @@ fn source_entry(id: &str, kind: SourceKind) -> MemorySourceEntry {
|
||||
max_issues: None,
|
||||
max_prs: None,
|
||||
selector: None,
|
||||
max_tokens_per_sync: None,
|
||||
max_cost_per_sync_usd: None,
|
||||
sync_depth_days: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user