import { Trans, useLingui } from "@lingui/react/macro"; import type { SpaceMemoryConfig } from "@rakazo/contracts"; import { Button, Field, FieldLabel, NativeSelect, NativeSelectOption, Switch, } from "@rakazo/ui-web"; import { useEffect, useId, useState } from "react"; import { rpc } from "../lib/rpc"; import { SpaceMemorySection } from "./KnowledgeSection"; import { defaultMemoryProviderSettings, MEMORY_PROVIDER_SETTINGS, type MemoryProviderConnectionDraft, memoryProviderSettings, } from "./memory-providers/registry"; type MemoryScope = "isolated" | "shared"; function SharedScopeSwitch({ value, disabled, onChange, }: { value: MemoryScope; disabled: boolean; onChange: (scope: MemoryScope) => void; }) { const labelId = useId(); return (
Share between bots onChange(checked ? "shared" : "isolated")} />
); } /** * What the agents remember first; the optional semantic-recall provider stays * behind Advanced because most people never need it. */ export function MemorySettingsOverlay({ config, onConfigChange, onBusyChange, }: { config: SpaceMemoryConfig | null | undefined; onConfigChange: (config: SpaceMemoryConfig | null) => void; onBusyChange?: (busy: boolean) => void; }) { const { t } = useLingui(); const providerSelectId = useId(); const defaultRegistration = defaultMemoryProviderSettings(); const [selectedProvider, setSelectedProvider] = useState( config?.provider ?? defaultRegistration.id, ); const [defaultScope, setDefaultScope] = useState( config?.defaultMemoryScope ?? "isolated", ); const [pending, setPending] = useState<"connect" | "disconnect" | "scope" | null>(null); const [error, setError] = useState(null); useEffect(() => { if (config) { setSelectedProvider(config.provider); setDefaultScope(config.defaultMemoryScope); return; } if (config === null && !memoryProviderSettings(selectedProvider)) { setSelectedProvider(defaultRegistration.id); } }, [config, defaultRegistration.id, selectedProvider]); const registration = memoryProviderSettings(config?.provider ?? selectedProvider); const busy = pending !== null; useEffect(() => { return () => onBusyChange?.(false); }, [onBusyChange]); function markPending(next: "connect" | "disconnect" | "scope" | null) { setPending(next); onBusyChange?.(next !== null); } async function connect(draft: MemoryProviderConnectionDraft) { if (!registration) return false; setError(null); markPending("connect"); try { const next = await rpc.memory.connectProvider({ provider: registration.id, ...draft, defaultMemoryScope: defaultScope, }); onConfigChange(next); return true; } catch (err) { setError(err instanceof Error ? err.message : t`Could not connect ${registration.name}`); return false; } finally { markPending(null); } } async function disconnect() { setError(null); markPending("disconnect"); try { await rpc.memory.disconnectProvider(); onConfigChange(null); } catch (err) { setError(err instanceof Error ? err.message : t`Could not disconnect memory provider`); } finally { markPending(null); } } async function updateDefaultScope(scope: MemoryScope) { if (scope === defaultScope) return; setError(null); markPending("scope"); try { const next = await rpc.memory.setDefaultScope({ defaultMemoryScope: scope }); onConfigChange(next); } catch (err) { setError(err instanceof Error ? err.message : t`Could not update the default memory scope`); } finally { markPending(null); } } return (
Advanced
{error ?

{error}

: null} {config === undefined ? (

Loading memory settings…

) : config ? (
{registration?.connectedLabel(config) ?? config.provider}
void updateDefaultScope(scope)} />
) : registration ? ( <> {MEMORY_PROVIDER_SETTINGS.length > 1 ? ( Provider setSelectedProvider(event.target.value)} > {MEMORY_PROVIDER_SETTINGS.map((entry) => ( {entry.name} ))} ) : null}
) : (

The selected memory provider is not available in this build.

)}
); }