Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288import { LoadingState } from "../components/loading-state";import { useEffect, useRef, useState } from "octane";import { Button } from "octane-kumo/components/button";import { InputArea } from "octane-kumo/components/input";import { MAX_MEMORIES, MAX_MEMORY_LENGTH, type MemoryFact,} from "../../shared/memory";import { useMutation } from "@octanejs/tanstack-query";import { useCustomerQuerySession } from "../runtime/customer-query-provider";import { callOwner, type SettingsConnectionProps } from "../runtime/owner-rpc";import { memoryOptions, useMemoriesQuery,} from "../runtime/queries/use-memories-query";
export function MemorySettings({ connection, connected, connecting,}: SettingsConnectionProps) { const cache = useCustomerQuerySession(); const options = memoryOptions(cache, connection); const query = useMemoriesQuery(connection); const facts = query.data ?? []; const loaded = query.data !== undefined; const loading = query.isPending && !query.isError; const [notice, setNotice] = useState(""); const [draft, setDraft] = useState(""); const [editing, setEditing] = useState<MemoryFact | null>(null); const [adding, setAdding] = useState(false); const generation = useRef(0); const current = useRef({ connection, connected }); current.current = { connection, connected }; const mutation = useMutation({ mutationFn: async ( action: { kind: "save" } | { kind: "remove"; fact: MemoryFact }, ) => { const token = generation.current; const valid = () => token === generation.current && current.current.connected && current.current.connection === connection && !!connection?.isCurrent(); if (!valid()) throw new Error("Connection unavailable"); await cache.client.cancelQueries({ queryKey: options.queryKey }); await cache.client.invalidateQueries({ queryKey: options.queryKey, refetchType: "none", }); if (action.kind === "save") { const fact = await callOwner<MemoryFact>( connection, editing ? "updateMemory" : "addMemory", editing ? [editing.id, draft, editing.version] : [draft], ); if (!valid()) return; void cache.client.cancelQueries({ queryKey: options.queryKey }); cache.client.setQueryData(options.queryKey, (previous) => [ fact, ...(previous ?? []).filter((item) => item.id !== fact.id), ]); setEditing(null); setAdding(false); setDraft(""); setNotice("Memory saved. Relevant facts apply from the next turn."); } else { const { fact } = action; await callOwner(connection, "deleteMemory", [fact.id, fact.version]); if (!valid()) return; void cache.client.cancelQueries({ queryKey: options.queryKey }); cache.client.setQueryData(options.queryKey, (previous) => (previous ?? []).filter((item) => item.id !== fact.id), ); if (editing?.id === fact.id) { setEditing(null); setDraft(""); } setNotice( "Memory deleted. It will no longer be retrieved into future turns.", ); } }, }); useEffect(() => { generation.current++; mutation.reset(); setNotice(""); return () => { generation.current++; }; }, [connection, connected]); const busy = mutation.isPending; const error = mutation.isError ? mutation.variables?.kind === "remove" ? "Could not delete memory. Check your connection, reload memories and try again." : "Could not save memory. Your edits are still here. Check your connection and input. If the fact changed elsewhere, cancel editing and reload memories." : query.isError ? "Could not load memories. Check your connection and try again." : "";
useEffect(() => { if (!loaded || loading) return; let frame = 0; const revealMemory = () => { const match = /^#memory-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/.exec( window.location.hash, ); if (!match) return; cancelAnimationFrame(frame); frame = requestAnimationFrame(() => { const target = document.getElementById(`memory-${match[1]}`); if (target) { target.scrollIntoView({ block: "start" }); target.focus({ preventScroll: true }); } else { setNotice("This memory is no longer available."); } }); }; revealMemory(); window.addEventListener("hashchange", revealMemory); return () => { cancelAnimationFrame(frame); window.removeEventListener("hashchange", revealMemory); }; }, [loaded, loading, facts]);
function save() { if (!connection?.isReady() || !connected || busy || loading) return; setNotice(""); mutation.mutate({ kind: "save" }); } function remove(fact: MemoryFact) { if (!connection?.isReady() || !connected || busy || loading) return; setNotice(""); mutation.mutate({ kind: "remove", fact }); }
return ( <section className="settings-section memory-settings" aria-labelledby="memory-heading" > <div className="settings-intro"> <h3 id="memory-heading">Memories</h3> <p> Facts you ask Flarebot to remember are shared across your conversations. You can also add a fact here. Up to {MAX_MEMORIES}{" "} facts, {MAX_MEMORY_LENGTH.toLocaleString("en-US")} characters each. </p> <p> Deleting a memory stops future retrieval. Existing messages and platform backups may still contain it. </p> </div> <div className="settings-section-body"> {loading && (connected || connecting) && !loaded && !error && ( <LoadingState label="Loading memories…" /> )} {query.isFetching && connected && loaded && ( <p role="status">Refreshing memories…</p> )} {error && <p role="alert">{error}</p>} {notice && <p role="status">{notice}</p>} <div className="settings-actions"> <Button disabled={ !connected || busy || loading || adding || editing !== null || facts.length >= MAX_MEMORIES } onClick={() => { setAdding(true); setDraft(""); setNotice(""); }} > Add memory </Button> <Button disabled={ !connected || busy || loading || adding || editing !== null } onClick={() => { mutation.reset(); void query.refetch(); }} > Reload memories </Button> </div> {(adding || editing !== null) && ( <form className="memory-editor" aria-busy={busy} onSubmit={(event) => { event.preventDefault(); void save(); }} > <InputArea label={editing ? "Edit memory" : "New memory"} value={draft} onValueChange={setDraft} rows={4} maxLength={MAX_MEMORY_LENGTH} required disabled={!connected || busy} className="instructions-input" /> <div className="settings-actions"> <Button type="submit" variant="primary" disabled={ !connected || busy || loading || !draft.trim() || draft === editing?.content } loading={busy} > Save memory </Button> <Button disabled={!connected || busy} onClick={() => { setEditing(null); setAdding(false); setDraft(""); }} > Cancel editing </Button> </div> </form> )} {loaded && !loading && !error && !facts.length && ( <p> No memories yet. Ask Flarebot to remember a fact, or add one here. </p> )} <ul className="memory-list" aria-label="Saved memories"> {facts.map((fact) => ( <li key={fact.id} id={`memory-${fact.id}`} tabIndex={-1} className="memory-fact" > <p>{fact.content}</p> <div className="settings-actions"> <Button disabled={ !connected || busy || loading || adding || editing !== null } onClick={() => { setEditing(fact); setDraft(fact.content); setNotice(""); }} > Edit memory </Button> <Button disabled={ !connected || busy || loading || adding || editing !== null } onClick={() => void remove(fact)} > Delete memory </Button> </div> </li> ))} </ul> </div> </section> );}