/** * GoalsPanel — Brain > Goals. * * Views and edits the agent's long-term goals list (`openhuman.memory_goals_*`) * and triggers the turn-based enrichment agent ("Reflect"). The same list is * curated automatically by the background goals agent when context is * summarized; this panel is the manual surface. */ import { useCallback, useEffect, useRef, useState } from 'react'; import { LuCheck, LuPencil, LuPlus, LuSparkles, LuTrash2, LuX } from 'react-icons/lu'; import { useT } from '../../lib/i18n/I18nContext'; import { type GoalItem, goalsApi } from '../../services/api/goalsApi'; import Button from '../ui/Button'; import Input from '../ui/Input'; const cardClass = 'rounded-lg border border-line bg-surface p-4'; export default function GoalsPanel() { const { t } = useT(); const [goals, setGoals] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [actionError, setActionError] = useState(null); const [newText, setNewText] = useState(''); const [adding, setAdding] = useState(false); const [reflecting, setReflecting] = useState(false); const [reflectSummary, setReflectSummary] = useState(null); const [editingId, setEditingId] = useState(null); const [editText, setEditText] = useState(''); const [busyId, setBusyId] = useState(null); const mountedRef = useRef(true); const load = useCallback(async () => { setError(null); try { const list = await goalsApi.list(); if (mountedRef.current) setGoals(list); } catch (err) { if (mountedRef.current) setError(err instanceof Error ? err.message : String(err)); } finally { if (mountedRef.current) setLoading(false); } }, []); useEffect(() => { mountedRef.current = true; void load(); return () => { mountedRef.current = false; }; }, [load]); const handleAdd = useCallback(async () => { const text = newText.trim(); if (!text) return; setActionError(null); setAdding(true); try { const list = await goalsApi.add(text); if (mountedRef.current) { setError(null); setGoals(list); setNewText(''); } } catch (err) { if (mountedRef.current) setActionError(err instanceof Error ? err.message : t('brain.goals.actionError')); } finally { if (mountedRef.current) setAdding(false); } }, [newText, t]); const startEdit = useCallback((goal: GoalItem) => { setActionError(null); setEditingId(goal.id); setEditText(goal.text); }, []); const cancelEdit = useCallback(() => { setEditingId(null); setEditText(''); }, []); const saveEdit = useCallback( async (id: string) => { const text = editText.trim(); if (!text) return; setActionError(null); setBusyId(id); try { const list = await goalsApi.edit(id, text); if (mountedRef.current) { setError(null); setGoals(list); setEditingId(null); setEditText(''); } } catch (err) { if (mountedRef.current) setActionError(err instanceof Error ? err.message : t('brain.goals.actionError')); } finally { if (mountedRef.current) setBusyId(null); } }, [editText, t] ); const handleDelete = useCallback( async (id: string) => { setActionError(null); setBusyId(id); try { const list = await goalsApi.remove(id); if (mountedRef.current) { setError(null); setGoals(list); } } catch (err) { if (mountedRef.current) setActionError(err instanceof Error ? err.message : t('brain.goals.actionError')); } finally { if (mountedRef.current) setBusyId(null); } }, [t] ); const handleReflect = useCallback(async () => { setActionError(null); setReflectSummary(null); setReflecting(true); try { const res = await goalsApi.reflect(); if (mountedRef.current) { setError(null); setGoals(res.items); setReflectSummary( res.ran ? res.summary || t('brain.goals.reflectDone') : res.summary || t('brain.goals.actionError') ); } } catch (err) { if (mountedRef.current) setActionError(err instanceof Error ? err.message : t('brain.goals.actionError')); } finally { if (mountedRef.current) setReflecting(false); } }, [t]); return (
{/* Header */}

{t('brain.goals.title')}

{t('brain.goals.description')}

{/* Add row */}
setNewText(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') void handleAdd(); }} />
{/* Errors / reflect summary */} {actionError && (
{actionError}
)} {reflectSummary && (
{reflectSummary}
)} {/* List */}
{loading ? (
{t('common.loading')}
) : error ? (
{error}
) : goals.length === 0 ? (

{t('brain.goals.empty')}

) : (
    {goals.map(goal => (
  • {editingId === goal.id ? (
    setEditText(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') void saveEdit(goal.id); if (e.key === 'Escape') cancelEdit(); }} autoFocus />
    ) : (
    {goal.text}
    )}
  • ))}
)}
); }