File size: 3,589 Bytes
37938b3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | import { useEffect, useRef, useState } from "react";
const STORAGE_KEY = "reachy-vibe-knowledge";
const MAX_LEN = 20_000;
interface KnowledgeDialogProps {
open: boolean;
onClose: () => void;
}
export function loadKnowledge(): string {
try {
return localStorage.getItem(STORAGE_KEY) ?? "";
} catch {
return "";
}
}
export function KnowledgeDialog({ open, onClose }: KnowledgeDialogProps) {
const [text, setText] = useState("");
const [saved, setSaved] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
useEffect(() => {
if (!open) return;
setText(loadKnowledge());
setSaved(false);
const t = window.setTimeout(() => textareaRef.current?.focus(), 40);
return () => window.clearTimeout(t);
}, [open]);
const handleSave = () => {
try {
const trimmed = text.slice(0, MAX_LEN);
localStorage.setItem(STORAGE_KEY, trimmed);
setText(trimmed);
setSaved(true);
window.setTimeout(() => setSaved(false), 1500);
} catch (err) {
console.error("[knowledge] save failed", err);
}
};
const handleClear = () => {
try {
localStorage.removeItem(STORAGE_KEY);
} catch {}
setText("");
setSaved(true);
window.setTimeout(() => setSaved(false), 1500);
};
if (!open) return null;
const charCount = text.length;
const approxKb = (charCount / 1024).toFixed(1);
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal knowledge-modal" onClick={(e) => e.stopPropagation()}>
<header className="modal-header">
<div>
<h2>App Knowledge</h2>
<p className="modal-sub">
Long-lived context the agent sees on every message. Use it for
the project goal, constraints, tone, preferred libraries,
API keys already in place, etc.
</p>
</div>
<button
type="button"
className="btn btn-ghost"
onClick={onClose}
aria-label="Close"
>
✕
</button>
</header>
<div className="modal-body">
<textarea
ref={textareaRef}
className="knowledge-textarea"
value={text}
onChange={(e) => setText(e.target.value.slice(0, MAX_LEN))}
placeholder={[
"Example:",
"- I'm building an accessibility demo for Reachy Mini.",
"- Keep the UI high-contrast and screen-reader friendly.",
"- All robot commands must be undoable from the UI.",
"- Prefer plain DOM over frameworks.",
].join("\n")}
rows={14}
/>
<div className="knowledge-meta">
<span>
{charCount.toLocaleString()} chars · ~{approxKb} KB
</span>
<span className="text-faint">
Stored locally in your browser, sent on every prompt.
</span>
</div>
</div>
<footer className="modal-footer">
<button type="button" className="btn btn-ghost" onClick={handleClear}>
Clear
</button>
<div className="spacer" />
{saved && <span className="saved-pill">Saved</span>}
<button type="button" className="btn btn-ghost" onClick={onClose}>
Close
</button>
<button type="button" className="btn btn-primary" onClick={handleSave}>
Save
</button>
</footer>
</div>
</div>
);
}
|