NeonClary's picture
Upload folder using huggingface_hub
060fd46 verified
Raw History Blame Contribute Delete
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>
);
}