Spaces:
Sleeping
Sleeping
Download frontend/src/components/QueryInput.js from neongeckocom/LLM_Comparison_Tool: direct link, hf CLI and curl.
- Browser
- Download file 9.85 kB
-
https://huggingface.co/spaces/neongeckocom/LLM_Comparison_Tool/resolve/main/frontend/src/components/QueryInput.js
- Command line
-
hf download hf://spaces/neongeckocom/LLM_Comparison_Tool/frontend/src/components/QueryInput.js
-
curl -L -o QueryInput.js https://huggingface.co/spaces/neongeckocom/LLM_Comparison_Tool/resolve/main/frontend/src/components/QueryInput.js
9.85 kB
| import { useState, useRef } from 'react'; | |
| import { Send, Upload, FileText, X, Loader2 } from 'lucide-react'; | |
| import { neonModelDisplayName, neonPersonaDisplayName } from '../utils/displayName'; | |
| export default function QueryInput({ onQuery, onCsvUpload, loading, csvMode = false, selectedNeon = [], selectedComparison = [], comparisonProviders = [] }) { | |
| const [query, setQuery] = useState(''); | |
| const [selectedFile, setSelectedFile] = useState(null); | |
| const fileInputRef = useRef(null); | |
| const handleSubmit = (e) => { | |
| e.preventDefault(); | |
| if (csvMode && selectedFile) { | |
| onCsvUpload(selectedFile); | |
| } else if (!csvMode && query.trim()) { | |
| onQuery(query.trim()); | |
| setQuery(''); | |
| } | |
| }; | |
| const handleFileDrop = (e) => { | |
| e.preventDefault(); | |
| const file = e.dataTransfer?.files?.[0]; | |
| if (file && file.name.endsWith('.csv')) { | |
| setSelectedFile(file); | |
| } | |
| }; | |
| const handleFileSelect = (e) => { | |
| const file = e.target.files?.[0]; | |
| if (file) setSelectedFile(file); | |
| }; | |
| const handleKeyDown = (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey && !csvMode) { | |
| e.preventDefault(); | |
| handleSubmit(e); | |
| } | |
| }; | |
| const neonDisplayName = (sel) => | |
| `${neonModelDisplayName(sel.model_id)} - ${neonPersonaDisplayName(sel.persona_name)}`; | |
| const comparisonDisplayName = (modelId) => { | |
| for (const provider of comparisonProviders) { | |
| const model = provider.models.find(m => m.id === modelId); | |
| if (model) return model.name; | |
| } | |
| return modelId.split('/').pop(); | |
| }; | |
| return ( | |
| <div className="query-input-container"> | |
| <form onSubmit={handleSubmit} className="query-form"> | |
| {csvMode ? ( | |
| <div | |
| className={`csv-drop-zone ${selectedFile ? 'has-file' : ''}`} | |
| onDragOver={e => e.preventDefault()} | |
| onDrop={handleFileDrop} | |
| onClick={() => fileInputRef.current?.click()} | |
| > | |
| <input | |
| ref={fileInputRef} | |
| type="file" | |
| accept=".csv" | |
| onChange={handleFileSelect} | |
| style={{ display: 'none' }} | |
| /> | |
| {selectedFile ? ( | |
| <div className="csv-file-info"> | |
| <FileText size={20} /> | |
| <span>{selectedFile.name}</span> | |
| <button | |
| type="button" | |
| className="csv-clear" | |
| onClick={(e) => { e.stopPropagation(); setSelectedFile(null); }} | |
| > | |
| <X size={14} /> | |
| </button> | |
| </div> | |
| ) : ( | |
| <div className="csv-placeholder"> | |
| <Upload size={24} /> | |
| <span>Drop a CSV file here or click to browse</span> | |
| <span className="csv-hint">Single column of questions</span> | |
| </div> | |
| )} | |
| </div> | |
| ) : ( | |
| <textarea | |
| className="query-textarea" | |
| value={query} | |
| onChange={e => setQuery(e.target.value)} | |
| onKeyDown={handleKeyDown} | |
| placeholder="Ask a question to compare across models..." | |
| rows={2} | |
| disabled={loading} | |
| /> | |
| )} | |
| <div className="query-submit-row"> | |
| <button | |
| type="submit" | |
| className="query-submit" | |
| disabled={loading || (csvMode ? !selectedFile : !query.trim())} | |
| > | |
| {loading ? <Loader2 size={18} className="spin" /> : <Send size={18} />} | |
| {csvMode ? 'Run Batch' : 'Compare'} | |
| </button> | |
| {(selectedNeon.length > 0 || selectedComparison.length > 0) && ( | |
| <div className="selection-summary"> | |
| {selectedNeon.length > 0 && ( | |
| <div className="selection-line"> | |
| <span className="selection-label">Neon.ai Models Selected:</span>{' '} | |
| {selectedNeon.map(s => neonDisplayName(s)).join(', ')} <span className="selection-count">({selectedNeon.length})</span> | |
| </div> | |
| )} | |
| {selectedComparison.length > 0 && ( | |
| <div className="selection-line"> | |
| <span className="selection-label">Comparison Models Selected:</span>{' '} | |
| {selectedComparison.map(id => comparisonDisplayName(id)).join(', ')} <span className="selection-count">({selectedComparison.length})</span> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </form> | |
| <style>{` | |
| .query-input-container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 16px; | |
| } | |
| .query-form { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 16px; | |
| } | |
| .query-textarea { | |
| flex: 1; | |
| padding: 12px 14px; | |
| border: 1px solid var(--border-primary); | |
| border-radius: 10px; | |
| background: var(--card-bg); | |
| color: var(--text-primary); | |
| font-family: inherit; | |
| font-size: 15px; | |
| line-height: 1.5; | |
| font-weight: 400; | |
| resize: vertical; | |
| min-height: 52px; | |
| outline: none; | |
| box-shadow: var(--shadow-sm); | |
| transition: border-color 0.15s, box-shadow 0.15s; | |
| } | |
| .query-textarea:focus { | |
| border-color: var(--accent-primary); | |
| } | |
| .query-textarea:focus-visible { | |
| box-shadow: var(--focus-ring); | |
| } | |
| .query-textarea::placeholder { | |
| color: var(--text-muted); | |
| } | |
| .csv-drop-zone { | |
| flex: 1; | |
| border: 1px dashed var(--border-primary); | |
| border-radius: 10px; | |
| padding: 24px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: border-color 0.15s, background 0.15s; | |
| background: var(--card-bg); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .csv-drop-zone:hover { | |
| border-color: var(--accent-primary); | |
| background: var(--card-hover); | |
| } | |
| .csv-drop-zone:focus-visible { | |
| box-shadow: var(--focus-ring); | |
| } | |
| .csv-drop-zone.has-file { | |
| border-style: solid; | |
| border-color: var(--accent-primary); | |
| } | |
| .csv-placeholder { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 8px; | |
| color: var(--text-secondary); | |
| font-size: 14px; | |
| font-weight: 400; | |
| } | |
| .csv-hint { | |
| font-size: 12px; | |
| color: var(--text-muted); | |
| } | |
| .csv-file-info { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| color: var(--text-primary); | |
| font-size: 14px; | |
| font-weight: 500; | |
| justify-content: center; | |
| } | |
| .csv-clear { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 28px; | |
| height: 28px; | |
| border: 1px solid var(--border-primary); | |
| border-radius: 8px; | |
| background: var(--card-bg); | |
| color: var(--text-muted); | |
| } | |
| .csv-clear:hover { | |
| background: var(--card-hover); | |
| color: var(--text-primary); | |
| } | |
| .query-submit-row { | |
| display: flex; | |
| align-items: flex-end; | |
| gap: 16px; | |
| } | |
| .query-submit { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 12px 24px; | |
| border: none; | |
| border-radius: 10px; | |
| background: var(--accent-gradient); | |
| color: #FFFFFF; | |
| font-size: 15px; | |
| font-weight: 600; | |
| white-space: nowrap; | |
| transition: opacity 0.15s, transform 0.1s; | |
| flex-shrink: 0; | |
| box-shadow: var(--shadow-md); | |
| } | |
| .selection-summary { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2px; | |
| padding-top: 2px; | |
| min-width: 0; | |
| } | |
| .selection-line { | |
| font-size: 13px; | |
| font-family: inherit; | |
| font-weight: 400; | |
| color: var(--text-muted); | |
| line-height: 1.5; | |
| word-break: break-word; | |
| } | |
| .selection-label { | |
| font-weight: 500; | |
| color: var(--text-secondary); | |
| } | |
| .selection-count { | |
| font-weight: 500; | |
| color: var(--text-secondary); | |
| } | |
| .query-submit:hover:not(:disabled) { | |
| opacity: 0.92; | |
| transform: translateY(-1px); | |
| } | |
| .query-submit:focus-visible { | |
| box-shadow: var(--focus-ring); | |
| } | |
| .query-submit:disabled { | |
| opacity: 0.45; | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| @media (max-width: 480px) { | |
| .query-textarea { | |
| min-height: 56px; | |
| font-size: 16px; | |
| } | |
| .query-submit-row { | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .query-submit { | |
| align-self: stretch; | |
| justify-content: center; | |
| padding: 12px 20px; | |
| } | |
| .selection-line { | |
| font-size: 13px; | |
| } | |
| .csv-drop-zone { | |
| padding: 16px; | |
| } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .query-submit:hover:not(:disabled) { | |
| transform: none; | |
| } | |
| } | |
| `}</style> | |
| </div> | |
| ); | |
| } | |