Command center: edit brain sections inline from the cards
Add PATCH /api/claws/{id}/brain (edit_brain): writes any of system_prompt / agent_md
/ persona / skills_md into the claw's .brain (best-effort) + persists system_prompt
to Postgres (authoritative) + commits a ClawSync revision.
Frontend: a reusable EditableSection (pencil → textarea → Save/Cancel → PATCH →
re-fetch brain). The SYSTEM PROMPT, HOW I OPERATE (AGENTS.md), and PERSONALITY cards
in the command center are now editable inline; saving writes back to the mapped
brain section. AGENTS.md card now always shows (so it can be authored when empty).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9fca3f6676
commit
89c147b742
@@ -5,10 +5,56 @@
|
||||
// from Dashboard.tsx's in-file helpers.
|
||||
|
||||
import { useState, type CSSProperties } from "react";
|
||||
import { ScrollText } from "lucide-react";
|
||||
import { Check, Pencil, ScrollText, X } from "lucide-react";
|
||||
|
||||
export const mono = "'JetBrains Mono', ui-monospace, monospace";
|
||||
|
||||
/** A brain section: rendered read-only with a pencil; click to edit as raw text
|
||||
* and PATCH it back to `/api/claws/{id}/brain`. `children` is the display;
|
||||
* `value` is the editable source. `onSaved` should re-fetch the brain. */
|
||||
export function EditableSection({ clawId, field, value, onSaved, children, placeholder }: {
|
||||
clawId: string;
|
||||
field: "system_prompt" | "agent_md" | "persona" | "skills_md";
|
||||
value: string;
|
||||
onSaved?: () => void;
|
||||
children: React.ReactNode;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [draft, setDraft] = useState(value);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const save = async () => {
|
||||
setSaving(true);
|
||||
try {
|
||||
await fetch(`/api/claws/${clawId}/brain`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ [field]: draft }) });
|
||||
setEditing(false);
|
||||
onSaved?.();
|
||||
} catch {
|
||||
/* keep the editor open on failure */
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
if (editing) {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
<textarea value={draft} onChange={(e) => setDraft(e.target.value)} placeholder={placeholder} autoFocus
|
||||
style={{ width: "100%", minHeight: 130, resize: "vertical", boxSizing: "border-box", borderRadius: 8, border: "1px solid rgba(255,255,255,.14)", background: "#0b0b0e", color: "#e6e6ea", padding: 10, fontFamily: mono, fontSize: 12.5, lineHeight: 1.55 }} />
|
||||
<div style={{ display: "flex", gap: 8 }}>
|
||||
<button type="button" onClick={save} disabled={saving} style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 12px", borderRadius: 7, border: 0, background: "#5fd08a", color: "#06140c", fontSize: 12, fontWeight: 700, cursor: "pointer" }}><Check size={13} />{saving ? "Saving…" : "Save"}</button>
|
||||
<button type="button" onClick={() => setEditing(false)} style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 12px", borderRadius: 7, border: "1px solid rgba(255,255,255,.12)", background: "transparent", color: "#cfcfd5", fontSize: 12, cursor: "pointer" }}><X size={13} />Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<button type="button" onClick={() => { setDraft(value); setEditing(true); }} aria-label="Edit" title="Edit" style={{ position: "absolute", top: -2, right: -2, zIndex: 2, display: "inline-flex", alignItems: "center", justifyContent: "center", width: 24, height: 24, borderRadius: 6, border: "1px solid rgba(255,255,255,.1)", background: "rgba(8,8,10,.55)", color: "#8a8a92", cursor: "pointer" }}><Pencil size={12} /></button>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// `GET /api/claws/{id}/brain` — the claw's .brain (cm-brain / ClawhDF5).
|
||||
export type RawBrain = {
|
||||
exists: boolean;
|
||||
@@ -66,8 +112,9 @@ export function CollapseTick({ open, color, onClick }: { open: boolean; color: s
|
||||
);
|
||||
}
|
||||
|
||||
export function SystemPromptCard({ prompt }: { prompt: string }) {
|
||||
export function SystemPromptCard({ prompt, clawId, onSaved }: { prompt: string; clawId?: string; onSaved?: () => void }) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const display = prompt?.trim() ? <MarkdownText text={prompt} baseSize={14} /> : <p style={{ fontSize: 13, color: "#6a6a72", margin: 0 }}>No system prompt set.</p>;
|
||||
return (
|
||||
<div style={{ borderRadius: 12, background: "#0f0f13", border: "1px solid rgba(255,111,97,.3)", padding: "16px 18px", boxShadow: "0 8px 22px rgba(0,0,0,.4)" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: open ? 13 : 0 }}>
|
||||
@@ -76,7 +123,11 @@ export function SystemPromptCard({ prompt }: { prompt: string }) {
|
||||
<span style={{ flex: 1 }} />
|
||||
<CollapseTick open={open} color="#ff8a7a" onClick={() => setOpen((o) => !o)} />
|
||||
</div>
|
||||
{open ? (prompt?.trim() ? <MarkdownText text={prompt} baseSize={14} /> : <p style={{ fontSize: 13, color: "#6a6a72", margin: 0 }}>No system prompt set.</p>) : null}
|
||||
{open ? (
|
||||
clawId ? (
|
||||
<EditableSection clawId={clawId} field="system_prompt" value={prompt ?? ""} onSaved={onSaved} placeholder="System prompt / job description…">{display}</EditableSection>
|
||||
) : display
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user