Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288import { LoadingState } from "../components/loading-state";import { DiagnosticSettings } from "./DiagnosticSettings";import { ModelSettings } from "./ModelSettings";import { InstallationSettings } from "./InstallationSettings";import { MemorySettings } from "./MemorySettings";import { DomainSettings } from "./DomainSettings";import { SettingsNavigation } from "../components/settings-navigation";import { useEffect, useRef, useState } from "octane";import { useMutation } from "@octanejs/tanstack-query";import { useCustomerQuerySession } from "../runtime/customer-query-provider";import { useSettingsConnection } from "../runtime/settings-connection";import { callOwner } from "../runtime/owner-rpc";import { instructionOptions, useInstructionsQuery,} from "../runtime/queries/use-instructions-query";import { Button } from "octane-kumo/components/button";import { InputArea } from "octane-kumo/components/input";import { MAX_INSTRUCTIONS_LENGTH, type InstructionSettings,} from "../../shared/instructions";
export function Settings() { const cache = useCustomerQuerySession(); return <SettingsEditor key={cache.getSnapshot().epoch} />;}
function SettingsEditor() { const cache = useCustomerQuerySession(); const { connection, connected, connecting, error: connectionError, reconnect, } = useSettingsConnection(); const options = instructionOptions(cache, connection); const query = useInstructionsQuery(connection); const settings = query.data; const [draft, setDraft] = useState(settings?.instructions ?? ""); const [notice, setNotice] = useState(""); const dirtyDraft = useRef(false); const current = useRef(connection); current.current = connection; useEffect( () => () => { current.current = null; }, [], ); useEffect(() => { if (settings && !dirtyDraft.current) setDraft(settings.instructions); }, [settings]); const mutation = useMutation({ mutationFn: async (reset: boolean) => { if (!connection?.isReady() || current.current !== connection) throw new Error("Connection unavailable"); await cache.client.cancelQueries({ queryKey: options.queryKey }); await cache.client.invalidateQueries({ queryKey: options.queryKey, refetchType: "none", }); const value = await callOwner<InstructionSettings>( connection, reset ? "resetInstructions" : "updateInstructions", reset ? [] : [draft], ); if (!connection?.isCurrent() || current.current !== connection) return; // A focus refresh could have started while the command was in flight. void cache.client.cancelQueries({ queryKey: options.queryKey }); cache.client.setQueryData(options.queryKey, value); setDraft(value.instructions); dirtyDraft.current = false; setNotice( reset ? "Default instructions restored. Applies from the next turn." : "Instructions saved. Applies from the next turn.", ); }, }); useEffect(() => { mutation.reset(); setNotice(""); }, [connection, connected]); const saving = mutation.isPending; const loading = connecting || (query.isPending && query.isFetching); const error = connectionError || (mutation.isError ? "Could not save instructions. Your edits are still here. Check your connection and sign-in, then try again." : query.isError ? "Could not load instructions. Try again." : ""); function save(reset: boolean) { if (!connected || !connection?.isReady() || saving || loading) return; setNotice(""); mutation.mutate(reset); } const dirty = !!settings && draft !== settings.instructions; const showPrivate = !cache.getSnapshot().denied || connecting; return ( <main className="content settings-page"> <div className="settings-page-heading"> <h1>Settings</h1> <p> Make Flarebot your own. Manage your agent’s behavior, knowledge and installation. </p> </div> {error && <p role="alert">{error}</p>} {!loading && (!connected || query.isError) && ( <Button onClick={() => (connected ? void query.refetch() : reconnect())} > {connected && query.isError ? "Reload instructions" : settings ? "Reconnect" : "Try again"} </Button> )} <div className="settings-layout"> <SettingsNavigation /> <div className="settings-content"> <div className="settings-group" id="agent-settings" tabIndex={-1}> <div className="settings-group-heading"> <h2>Agent behavior</h2> <p>Choose how your agent thinks and responds.</p> </div> {showPrivate && ( <ModelSettings connection={connection} connected={connected} connecting={connecting} /> )} <section className="settings-section" aria-labelledby="instructions-heading" > <div className="settings-intro"> <h3 id="instructions-heading">Personal agent instructions</h3> <p> Customize how Flarebot responds. Changes apply from the next turn in every conversation. </p> </div> <div className="settings-section-body"> {loading && !settings && ( <LoadingState label="Loading instructions…" rows={5} multiline /> )} {settings && (connecting || query.isFetching) && ( <p role="status"> {connecting ? "Reconnecting…" : "Refreshing instructions…"} </p> )} {settings && ( <form className="instructions-form" aria-busy={saving} onSubmit={(event) => { event.preventDefault(); void save(false); }} > <InputArea label="Instructions" description={`Describe your preferences and how Flarebot should help. Maximum ${MAX_INSTRUCTIONS_LENGTH.toLocaleString("en-US")} characters.`} value={draft} onValueChange={(value) => { dirtyDraft.current = value !== settings.instructions; setDraft(value); setNotice(""); }} rows={12} maxLength={MAX_INSTRUCTIONS_LENGTH} required disabled={saving || loading || !connected} className="instructions-input" /> <p className="muted" role="status"> {dirty ? "Unsaved changes" : settings.customized ? "Using custom instructions" : "Using Flarebot defaults"} </p> <div className="settings-actions"> <Button type="submit" variant="primary" disabled={ !connected || loading || !dirty || !draft.trim() || saving } loading={saving} > Save instructions </Button> <Button disabled={ !connected || loading || saving || (!settings.customized && !dirty) } onClick={() => void save(true)} > Reset to defaults </Button> </div> {notice && <p role="status">{notice}</p>} </form> )} </div> </section> </div> <div className="settings-group" id="memory-settings" tabIndex={-1}> <div className="settings-group-heading"> <h2>Memory</h2> <p> Manage the knowledge your agent carries between conversations. </p> </div> {showPrivate && ( <MemorySettings connection={connection} connected={connected} connecting={connecting} /> )} </div> <div className="settings-group" id="installation-settings" tabIndex={-1} > <div className="settings-group-heading"> <h2>Installation</h2> <p> Manage your address and review the services available to your agent. </p> </div> {showPrivate && ( <> <DomainSettings connection={connection} connected={connected} connecting={connecting} /> <InstallationSettings connection={connection} connected={connected} connecting={connecting} /> </> )} </div> <div className="settings-group" id="support-settings" tabIndex={-1}> <div className="settings-group-heading"> <h2>Diagnostics</h2> <p> Get the information you need to troubleshoot your installation. </p> </div> {showPrivate && ( <DiagnosticSettings connection={connection} connected={connected} connecting={connecting} /> )} </div> </div> </div> </main> );}