| import { useRef, useState } from 'react'; | |
| interface DocumentManagerProps { | |
| documents: Array<{ id: string; title: string; filepath: string }>; | |
| onUpload: (files: FileList) => void; | |
| onPaste: (text: string, filename: string) => void; | |
| } | |
| function PasteModal({ onClose, onConfirm }: { onClose: () => void; onConfirm: (text: string, filename: string) => void }) { | |
| const [text, setText] = useState(''); | |
| const [filename, setFilename] = useState('pasted-document.md'); | |
| function handleConfirm() { | |
| const trimmed = text.trim(); | |
| if (!trimmed) return; | |
| onConfirm(trimmed, filename.trim() || 'pasted-document.md'); | |
| onClose(); | |
| } | |
| return ( | |
| <div style={{ | |
| position: 'fixed', | |
| inset: 0, | |
| background: 'var(--modal-bg)', | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| zIndex: 1000, | |
| }} | |
| onClick={e => { if (e.target === e.currentTarget) onClose(); }} | |
| > | |
| <div style={{ | |
| background: 'var(--bg-card)', | |
| borderRadius: '10px', | |
| padding: '1.5rem', | |
| width: '90%', | |
| maxWidth: '560px', | |
| boxShadow: '0 8px 32px var(--shadow)', | |
| fontFamily: 'system-ui, -apple-system, sans-serif', | |
| border: '1px solid var(--border)', | |
| }}> | |
| <h3 style={{ margin: '0 0 1rem 0', fontSize: '1rem', color: 'var(--text)' }}> | |
| Paste Document | |
| </h3> | |
| <div style={{ marginBottom: '0.75rem' }}> | |
| <label style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', display: 'block', marginBottom: '0.3rem' }}> | |
| Filename | |
| </label> | |
| <input | |
| type="text" | |
| value={filename} | |
| onChange={e => setFilename(e.target.value)} | |
| style={{ | |
| width: '100%', | |
| padding: '0.45rem 0.65rem', | |
| fontSize: '0.85rem', | |
| fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace", | |
| border: '1px solid var(--input-border)', | |
| borderRadius: '5px', | |
| boxSizing: 'border-box', | |
| background: 'var(--bg-input)', | |
| color: 'var(--text)', | |
| }} | |
| /> | |
| </div> | |
| <div style={{ marginBottom: '1rem' }}> | |
| <label style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', display: 'block', marginBottom: '0.3rem' }}> | |
| Content (Markdown or plain text) | |
| </label> | |
| <textarea | |
| value={text} | |
| onChange={e => setText(e.target.value)} | |
| rows={12} | |
| placeholder="Paste your document content here\u2026" | |
| style={{ | |
| width: '100%', | |
| padding: '0.5rem 0.65rem', | |
| fontSize: '0.8rem', | |
| fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace", | |
| border: '1px solid var(--input-border)', | |
| borderRadius: '5px', | |
| resize: 'vertical', | |
| boxSizing: 'border-box', | |
| lineHeight: 1.5, | |
| background: 'var(--bg-input)', | |
| color: 'var(--text)', | |
| }} | |
| /> | |
| </div> | |
| <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.5rem' }}> | |
| <button | |
| onClick={onClose} | |
| style={{ | |
| padding: '0.5rem 1rem', | |
| fontSize: '0.85rem', | |
| fontFamily: 'system-ui, -apple-system, sans-serif', | |
| background: 'var(--bg-section)', | |
| color: 'var(--text-secondary)', | |
| border: '1px solid var(--border)', | |
| borderRadius: '5px', | |
| cursor: 'pointer', | |
| }} | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| onClick={handleConfirm} | |
| disabled={!text.trim()} | |
| style={{ | |
| padding: '0.5rem 1rem', | |
| fontSize: '0.85rem', | |
| fontFamily: 'system-ui, -apple-system, sans-serif', | |
| background: text.trim() ? '#4285F4' : 'var(--border)', | |
| color: '#fff', | |
| border: 'none', | |
| borderRadius: '5px', | |
| cursor: text.trim() ? 'pointer' : 'not-allowed', | |
| fontWeight: 600, | |
| }} | |
| > | |
| Add Document | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default function DocumentManager({ documents, onUpload, onPaste }: DocumentManagerProps) { | |
| const fileInputRef = useRef<HTMLInputElement>(null); | |
| const [pasteOpen, setPasteOpen] = useState(false); | |
| function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) { | |
| const files = e.target.files; | |
| if (files && files.length > 0) { | |
| onUpload(files); | |
| } | |
| e.target.value = ''; | |
| } | |
| return ( | |
| <div style={{ | |
| padding: '1rem', | |
| background: 'var(--bg-section)', | |
| border: '1px solid var(--border)', | |
| borderRadius: '8px', | |
| marginBottom: '1.5rem', | |
| fontFamily: 'system-ui, -apple-system, sans-serif', | |
| }}> | |
| <div style={{ | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'space-between', | |
| marginBottom: '0.6rem', | |
| }}> | |
| <h3 style={{ | |
| margin: 0, | |
| fontSize: '0.85rem', | |
| fontWeight: 600, | |
| color: 'var(--text-secondary)', | |
| textTransform: 'uppercase', | |
| letterSpacing: '0.05em', | |
| }}> | |
| Documents | |
| <span style={{ | |
| marginLeft: '0.5rem', | |
| fontSize: '0.75rem', | |
| fontWeight: 400, | |
| color: 'var(--text-muted)', | |
| }}> | |
| ({documents.length}) | |
| </span> | |
| </h3> | |
| <div style={{ display: 'flex', gap: '0.4rem' }}> | |
| <button | |
| onClick={() => fileInputRef.current?.click()} | |
| style={{ | |
| padding: '0.3rem 0.7rem', | |
| fontSize: '0.78rem', | |
| background: 'var(--bg-card)', | |
| color: '#4285F4', | |
| border: '1px solid #4285F4', | |
| borderRadius: '5px', | |
| cursor: 'pointer', | |
| fontFamily: 'system-ui, -apple-system, sans-serif', | |
| fontWeight: 500, | |
| }} | |
| > | |
| Upload | |
| </button> | |
| <button | |
| onClick={() => setPasteOpen(true)} | |
| style={{ | |
| padding: '0.3rem 0.7rem', | |
| fontSize: '0.78rem', | |
| background: 'var(--bg-card)', | |
| color: '#34a853', | |
| border: '1px solid #34a853', | |
| borderRadius: '5px', | |
| cursor: 'pointer', | |
| fontFamily: 'system-ui, -apple-system, sans-serif', | |
| fontWeight: 500, | |
| }} | |
| > | |
| Paste | |
| </button> | |
| </div> | |
| </div> | |
| <input | |
| ref={fileInputRef} | |
| type="file" | |
| accept=".md,.txt" | |
| multiple | |
| style={{ display: 'none' }} | |
| onChange={handleFileChange} | |
| /> | |
| {documents.length === 0 ? ( | |
| <p style={{ fontSize: '0.82rem', color: 'var(--text-muted)', margin: 0 }}> | |
| No documents loaded. Upload .md or .txt files, or paste text. They stay local to this browser session. | |
| </p> | |
| ) : ( | |
| <div style={{ maxHeight: '180px', overflowY: 'auto' }}> | |
| {documents.map(doc => ( | |
| <div key={doc.id} style={{ | |
| display: 'flex', | |
| alignItems: 'center', | |
| padding: '0.35rem 0.6rem', | |
| background: 'var(--bg-card)', | |
| border: '1px solid var(--border)', | |
| borderRadius: '5px', | |
| marginBottom: '0.3rem', | |
| gap: '0.5rem', | |
| }}> | |
| <span style={{ | |
| fontSize: '0.75rem', | |
| color: 'var(--text-muted)', | |
| flexShrink: 0, | |
| }}> | |
| {'\u25AA'} | |
| </span> | |
| <span style={{ | |
| flex: 1, | |
| fontSize: '0.8rem', | |
| fontWeight: 500, | |
| color: 'var(--text)', | |
| overflow: 'hidden', | |
| textOverflow: 'ellipsis', | |
| whiteSpace: 'nowrap', | |
| }}> | |
| {doc.title} | |
| </span> | |
| <span style={{ | |
| fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace", | |
| fontSize: '0.68rem', | |
| color: 'var(--text-muted)', | |
| flexShrink: 0, | |
| }}> | |
| {doc.filepath} | |
| </span> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| {pasteOpen && ( | |
| <PasteModal | |
| onClose={() => setPasteOpen(false)} | |
| onConfirm={onPaste} | |
| /> | |
| )} | |
| </div> | |
| ); | |
| } | |