| import { useState } from 'react' |
|
|
| interface TeachPanelProps { |
| onTeach: (question: string, answer: string) => Promise<any> |
| loading: boolean |
| } |
|
|
| interface TeachResult { |
| question: string |
| answer: string |
| tokensWritten: number |
| message: string |
| timestamp: number |
| } |
|
|
| export function TeachPanel({ onTeach, loading }: TeachPanelProps) { |
| const [question, setQuestion] = useState('') |
| const [answer, setAnswer] = useState('') |
| const [history, setHistory] = useState<TeachResult[]>([]) |
|
|
| const handleTeach = async () => { |
| if (!question.trim() || !answer.trim() || loading) return |
| try { |
| const resp = await onTeach(question.trim(), answer.trim()) |
| setHistory(prev => [{ |
| question: question.trim(), |
| answer: answer.trim(), |
| tokensWritten: resp.tokens_written, |
| message: resp.message, |
| timestamp: Date.now(), |
| }, ...prev]) |
| setQuestion('') |
| setAnswer('') |
| } catch { |
| setHistory(prev => [{ |
| question: question.trim(), |
| answer: answer.trim(), |
| tokensWritten: 0, |
| message: 'Error: API not reachable', |
| timestamp: Date.now(), |
| }, ...prev]) |
| } |
| } |
|
|
| return ( |
| <div className="flex flex-col h-full glass-card overflow-hidden"> |
| <div className="px-5 py-3 border-b border-pal-border"> |
| <div className="flex items-center gap-2"> |
| <span className="text-base">📚</span> |
| <h2 className="text-sm font-semibold text-pal-text">Teach</h2> |
| <span className="text-xs text-pal-muted">O(1) learning</span> |
| </div> |
| </div> |
| |
| <div className="flex-1 overflow-y-auto p-4 space-y-3"> |
| {/* Input form */} |
| <div className="space-y-2"> |
| <div> |
| <label className="text-[11px] text-pal-muted font-medium uppercase tracking-wide block mb-1"> |
| Question |
| </label> |
| <input |
| type="text" |
| value={question} |
| onChange={e => setQuestion(e.target.value)} |
| onKeyDown={e => e.key === 'Enter' && !e.shiftKey && handleTeach()} |
| placeholder="what is the capital of france" |
| className="w-full bg-pal-surface border border-pal-border rounded-lg px-3 py-2 text-sm text-pal-text placeholder:text-pal-muted focus:outline-none focus:border-pal-green/50 transition-all" |
| /> |
| </div> |
| <div> |
| <label className="text-[11px] text-pal-muted font-medium uppercase tracking-wide block mb-1"> |
| Answer |
| </label> |
| <input |
| type="text" |
| value={answer} |
| onChange={e => setAnswer(e.target.value)} |
| onKeyDown={e => e.key === 'Enter' && !e.shiftKey && handleTeach()} |
| placeholder="paris" |
| className="w-full bg-pal-surface border border-pal-border rounded-lg px-3 py-2 text-sm text-pal-text placeholder:text-pal-muted focus:outline-none focus:border-pal-green/50 transition-all" |
| /> |
| </div> |
| <button |
| onClick={handleTeach} |
| disabled={!question.trim() || !answer.trim() || loading} |
| className="w-full py-2 rounded-lg bg-gradient-to-r from-pal-green to-pal-accent2 text-white font-semibold text-sm transition-all hover:scale-[1.02] hover:shadow-lg hover:shadow-pal-green/20 disabled:opacity-30 disabled:cursor-not-allowed disabled:hover:scale-100" |
| > |
| {loading ? 'Teaching...' : '⚡ Teach Instantly'} |
| </button> |
| </div> |
| |
| {/* History */} |
| {history.length > 0 && ( |
| <div className="space-y-2 pt-2 border-t border-pal-border"> |
| <span className="text-[11px] text-pal-muted font-medium uppercase tracking-wide"> |
| Taught ({history.length}) |
| </span> |
| {history.map((h, i) => ( |
| <div |
| key={i} |
| className="p-2.5 rounded-lg bg-pal-surface/60 border border-pal-border/50 animate-slide-up" |
| > |
| <div className="flex items-start justify-between gap-2 mb-1"> |
| <div className="flex-1 min-w-0"> |
| <div className="text-xs text-pal-text font-medium truncate"> |
| {h.question} |
| </div> |
| <div className="text-xs text-pal-green font-medium"> |
| → {h.answer} |
| </div> |
| </div> |
| {h.tokensWritten > 0 && ( |
| <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-pal-green/10 text-pal-green font-mono whitespace-nowrap"> |
| +{h.tokensWritten} tok |
| </span> |
| )} |
| </div> |
| {h.message && ( |
| <p className="text-[10px] text-pal-muted mt-1">{h.message}</p> |
| )} |
| </div> |
| ))} |
| </div> |
| )} |
| |
| {history.length === 0 && ( |
| <div className="text-center py-8"> |
| <div className="text-3xl mb-2 opacity-50">⚡</div> |
| <p className="text-xs text-pal-muted"> |
| Teach PALIMPSESTE new facts.<br/> |
| Each fact is written to memory in O(1) —<br/> |
| instantly retrievable, never forgotten. |
| </p> |
| </div> |
| )} |
| </div> |
| </div> |
| ) |
| } |
|
|