Spaces:
Runtime error
Runtime error
| 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> | |
| ); | |
| } | |