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.
)}
);
}