import React, { useEffect, useRef, useState } from "react"; import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Chip, CircularProgress, FormControl, FormControlLabel, FormHelperText, FormLabel, InputLabel, MenuItem, Radio, RadioGroup, Select, TextField, ToggleButton, ToggleButtonGroup, Typography, } from "@mui/material"; import type { SelectChangeEvent } from "@mui/material"; import { Clear, ExpandMore, PlayArrow, Upload } from "@mui/icons-material"; import type { AnalyzeRequest, ModelsResponse } from "../types"; import { getModelDescription, getModelLabel } from "../utils/modelLabels"; import { uploadFile } from "../api/client"; // Fields that ArticleInput owns (text, metadata — no pipeline/model) type ArticleFields = Omit; interface ArticleInputProps { onSubmit?: (request: AnalyzeRequest) => void; isLoading: boolean; showPipelineSelector?: boolean; availableModels?: ModelsResponse; // When true, hide the submit button (CompareTab renders its own button) hideSubmitButton?: boolean; // Fires on every text/metadata change so parent can track current content onRequestChange?: (request: ArticleFields) => void; } function ArticleInput({ onSubmit, isLoading, showPipelineSelector = true, availableModels, hideSubmitButton = false, onRequestChange, }: ArticleInputProps): React.ReactElement { const [inputMode, setInputMode] = useState<"text" | "file">("text"); const [textValue, setTextValue] = useState(""); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const [extractedInfo, setExtractedInfo] = useState<{ filename: string; char_count: number } | null>(null); const [title, setTitle] = useState(""); const [publicationDate, setPublicationDate] = useState(""); const [source, setSource] = useState(""); const [pipeline, setPipeline] = useState<"spacy" | "llm">("spacy"); const [model, setModel] = useState(""); const fileInputRef = useRef(null); // Sync model default when pipeline changes or availableModels first loads useEffect(() => { if (availableModels) { setModel(availableModels[pipeline].default); } }, [availableModels, pipeline]); // Notify parent whenever article content changes (used by CompareTab) const onRequestChangeRef = useRef(onRequestChange); onRequestChangeRef.current = onRequestChange; useEffect(() => { onRequestChangeRef.current?.({ text: textValue, title: title || undefined, publication_date: publicationDate || null, source: source || undefined, }); }, [textValue, title, publicationDate, source]); const handleModeChange = ( _: React.MouseEvent, newMode: string | null, ): void => { if (newMode !== null) { setInputMode(newMode as "text" | "file"); } }; const handleFileChange = async (e: React.ChangeEvent): Promise => { const file = e.target.files?.[0]; if (!file) return; setUploadError(null); setExtractedInfo(null); setIsUploading(true); try { const result = await uploadFile(file); setTextValue(result.text); setExtractedInfo({ filename: result.filename, char_count: result.char_count }); setInputMode("text"); } catch (err: unknown) { const msg = (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail ?? "Upload failed"; setUploadError(msg); } finally { setIsUploading(false); if (fileInputRef.current) fileInputRef.current.value = ""; } }; const handleClearFile = (): void => { setExtractedInfo(null); setUploadError(null); setTextValue(""); if (fileInputRef.current) fileInputRef.current.value = ""; }; const handleSubmit = (): void => { if (!onSubmit) return; const request: AnalyzeRequest = { text: textValue, title: title || undefined, publication_date: publicationDate || null, source: source || undefined, pipeline: showPipelineSelector ? pipeline : undefined, model: showPipelineSelector && model ? model : undefined, }; onSubmit(request); }; return ( Paste Text Upload File {inputMode === "text" && ( setTextValue(e.target.value)} sx={{ "& .MuiInputBase-input": { fontFamily: "IBM Plex Mono, monospace" } }} /> {textValue.length} characters {extractedInfo && ( )} )} {inputMode === "file" && ( {!isUploading && extractedInfo && ( )} {uploadError && ( {uploadError} )} Supported formats: .txt, .pdf, .docx )} }> Article Metadata setTitle(e.target.value)} size="small" /> setPublicationDate(e.target.value)} size="small" slotProps={{ inputLabel: { shrink: true } }} /> setSource(e.target.value)} size="small" /> {showPipelineSelector && ( <> Pipeline setPipeline(e.target.value as "spacy" | "llm")} row > } label="Pipeline A" /> } label="Pipeline B" /> {availableModels && ( Model {model && ( {getModelDescription(model)} )} )} )} {!hideSubmitButton && ( )} ); } export default ArticleInput;