Spaces:
Running
Running
Rajeev Ranjan Pandey commited on
Commit ·
e58b2bd
1
Parent(s): 6aa61f3
Refine UI alignment, fix dark mode toggle, and improve model selection logic
Browse files
frontend/src/components/SummarizerWidget.jsx
CHANGED
|
@@ -121,7 +121,7 @@ export default function SummarizerWidget({
|
|
| 121 |
{/* Input and Output Split Area */}
|
| 122 |
<div className="grid gap-0 lg:grid-cols-2 flex-1">
|
| 123 |
{/* LEFT PANE: INPUT */}
|
| 124 |
-
<div className="p-4 md:p-5 flex flex-col relative">
|
| 125 |
<div className="flex items-center justify-between border-b border-slate-100 dark:border-white/[0.05] pb-2.5 mb-3">
|
| 126 |
<h3 className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-500">Original Incident</h3>
|
| 127 |
<span className="rounded-full bg-slate-100 dark:bg-white/[0.05] px-2.5 py-0.5 text-[10px] font-bold text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-white/[0.06]">
|
|
@@ -129,8 +129,7 @@ export default function SummarizerWidget({
|
|
| 129 |
</span>
|
| 130 |
</div>
|
| 131 |
<textarea
|
| 132 |
-
className="w-full resize-none bg-transparent p-0 text-lg leading-[1.85] text-slate-800 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-600 focus:outline-none focus:ring-0"
|
| 133 |
-
rows={5}
|
| 134 |
placeholder="Paste a traffic incident report here, or click a sample on the right..."
|
| 135 |
value={text}
|
| 136 |
onChange={(e) => setText(e.target.value)}
|
|
@@ -146,7 +145,8 @@ export default function SummarizerWidget({
|
|
| 146 |
</div>
|
| 147 |
|
| 148 |
{/* RIGHT PANE: OUTPUT */}
|
| 149 |
-
<div className="p-4 md:p-5 flex flex-col relative border-t lg:border-t-0 lg:border-l border-slate-100 dark:border-white/[0.05] bg-slate-50/50 dark:bg-white/[0.01] rounded-b-2xl lg:rounded-bl-none lg:rounded-tr-2xl">
|
|
|
|
| 150 |
<div className="flex items-center justify-between border-b border-orange-300/40 dark:border-orange-400/20 pb-2.5 mb-3">
|
| 151 |
<h3 className="text-xs font-bold uppercase tracking-[0.2em] text-orange-500 dark:text-orange-400">Generated Output · {modelChoice.replace(/_/g, ' ').toUpperCase()}</h3>
|
| 152 |
{summary ? (
|
|
@@ -214,8 +214,9 @@ export default function SummarizerWidget({
|
|
| 214 |
return (
|
| 215 |
<button
|
| 216 |
key={model.id}
|
| 217 |
-
onClick={() => setModelChoice(model.id)}
|
| 218 |
-
|
|
|
|
| 219 |
>
|
| 220 |
{isSelected && <div className="absolute top-4 right-4 h-2 w-2 rounded-full bg-orange-500 shadow-[0_0_8px_rgba(249,115,22,0.6)]" />}
|
| 221 |
<div className="flex w-full items-start justify-between mb-4">
|
|
|
|
| 121 |
{/* Input and Output Split Area */}
|
| 122 |
<div className="grid gap-0 lg:grid-cols-2 flex-1">
|
| 123 |
{/* LEFT PANE: INPUT */}
|
| 124 |
+
<div className="p-4 md:p-5 flex flex-col relative min-h-[320px]">
|
| 125 |
<div className="flex items-center justify-between border-b border-slate-100 dark:border-white/[0.05] pb-2.5 mb-3">
|
| 126 |
<h3 className="text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-500">Original Incident</h3>
|
| 127 |
<span className="rounded-full bg-slate-100 dark:bg-white/[0.05] px-2.5 py-0.5 text-[10px] font-bold text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-white/[0.06]">
|
|
|
|
| 129 |
</span>
|
| 130 |
</div>
|
| 131 |
<textarea
|
| 132 |
+
className="w-full resize-none bg-transparent p-0 text-lg leading-[1.85] text-slate-800 dark:text-slate-100 placeholder:text-slate-400 dark:placeholder:text-slate-600 focus:outline-none focus:ring-0 flex-1"
|
|
|
|
| 133 |
placeholder="Paste a traffic incident report here, or click a sample on the right..."
|
| 134 |
value={text}
|
| 135 |
onChange={(e) => setText(e.target.value)}
|
|
|
|
| 145 |
</div>
|
| 146 |
|
| 147 |
{/* RIGHT PANE: OUTPUT */}
|
| 148 |
+
<div className="p-4 md:p-5 flex flex-col relative border-t lg:border-t-0 lg:border-l border-slate-100 dark:border-white/[0.05] bg-slate-50/50 dark:bg-white/[0.01] rounded-b-2xl lg:rounded-bl-none lg:rounded-tr-2xl min-h-[320px]">
|
| 149 |
+
|
| 150 |
<div className="flex items-center justify-between border-b border-orange-300/40 dark:border-orange-400/20 pb-2.5 mb-3">
|
| 151 |
<h3 className="text-xs font-bold uppercase tracking-[0.2em] text-orange-500 dark:text-orange-400">Generated Output · {modelChoice.replace(/_/g, ' ').toUpperCase()}</h3>
|
| 152 |
{summary ? (
|
|
|
|
| 214 |
return (
|
| 215 |
<button
|
| 216 |
key={model.id}
|
| 217 |
+
onClick={() => !loading && setModelChoice(model.id)}
|
| 218 |
+
disabled={loading}
|
| 219 |
+
className={`relative flex flex-col items-start rounded-xl border p-4 text-left transition-all ${cardCls} ${loading ? 'opacity-50 cursor-not-allowed' : ''}`}
|
| 220 |
>
|
| 221 |
{isSelected && <div className="absolute top-4 right-4 h-2 w-2 rounded-full bg-orange-500 shadow-[0_0_8px_rgba(249,115,22,0.6)]" />}
|
| 222 |
<div className="flex w-full items-start justify-between mb-4">
|
frontend/src/pages/Home.jsx
CHANGED
|
@@ -13,11 +13,7 @@ const FALLBACK_TEXT = {
|
|
| 13 |
};
|
| 14 |
|
| 15 |
export default function Home() {
|
| 16 |
-
const [isDark, setIsDark] = useState(
|
| 17 |
-
// Apply immediately — before first paint — so dark: variants work on load
|
| 18 |
-
document.documentElement.classList.add("dark");
|
| 19 |
-
return true;
|
| 20 |
-
});
|
| 21 |
const [datasetTrack, setDatasetTrack] = useState("gcc");
|
| 22 |
const [text, setText] = useState(FALLBACK_TEXT.gcc);
|
| 23 |
const [modelChoice, setModelChoice] = useState("bart_large_cnn");
|
|
@@ -29,51 +25,76 @@ export default function Home() {
|
|
| 29 |
const datasetTrackRef = useRef(datasetTrack);
|
| 30 |
const modelChoiceRef = useRef(modelChoice);
|
| 31 |
|
|
|
|
| 32 |
useEffect(() => { textRef.current = text; }, [text]);
|
| 33 |
useEffect(() => { datasetTrackRef.current = datasetTrack; }, [datasetTrack]);
|
| 34 |
useEffect(() => { modelChoiceRef.current = modelChoice; }, [modelChoice]);
|
| 35 |
|
| 36 |
-
//
|
| 37 |
useEffect(() => {
|
| 38 |
document.documentElement.classList.toggle("dark", isDark);
|
| 39 |
}, [isDark]);
|
| 40 |
|
|
|
|
| 41 |
useEffect(() => {
|
|
|
|
| 42 |
let active = true;
|
|
|
|
| 43 |
fetchSamples(datasetTrack)
|
| 44 |
.then((items) => {
|
| 45 |
if (!active) return;
|
| 46 |
setSamples(items);
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
})
|
| 49 |
-
.catch(() => {
|
| 50 |
-
if (!active) return;
|
| 51 |
setSamples([]);
|
| 52 |
setText(FALLBACK_TEXT[datasetTrack]);
|
| 53 |
});
|
|
|
|
| 54 |
setSummary("");
|
| 55 |
-
return () => {
|
|
|
|
|
|
|
|
|
|
| 56 |
}, [datasetTrack]);
|
| 57 |
|
| 58 |
-
const runSummarize = async (targetModelId) => {
|
| 59 |
const modelToUse = targetModelId || modelChoiceRef.current;
|
| 60 |
const currentText = textRef.current;
|
| 61 |
const currentTrack = datasetTrackRef.current;
|
|
|
|
| 62 |
if (!currentText || currentText.trim().length < 10) return;
|
|
|
|
| 63 |
setLoading(true);
|
| 64 |
setSummary("");
|
|
|
|
| 65 |
try {
|
| 66 |
-
const data = await summarizeText({
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
setSummary(data.summary);
|
| 68 |
} catch (error) {
|
| 69 |
setSummary(`Error: ${error?.response?.data?.detail || error.message}`);
|
| 70 |
} finally {
|
| 71 |
setLoading(false);
|
| 72 |
}
|
| 73 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
const handleSummarize = () => runSummarize();
|
| 76 |
-
const handleModelSelect = (modelId) => { setModelChoice(modelId); runSummarize(modelId); };
|
| 77 |
|
| 78 |
return (
|
| 79 |
<div className="min-h-screen bg-slate-50 dark:bg-[#060d1f] text-slate-900 dark:text-slate-200 transition-colors duration-300 pb-16 dark:bg-grid">
|
|
|
|
| 13 |
};
|
| 14 |
|
| 15 |
export default function Home() {
|
| 16 |
+
const [isDark, setIsDark] = useState(true);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
const [datasetTrack, setDatasetTrack] = useState("gcc");
|
| 18 |
const [text, setText] = useState(FALLBACK_TEXT.gcc);
|
| 19 |
const [modelChoice, setModelChoice] = useState("bart_large_cnn");
|
|
|
|
| 25 |
const datasetTrackRef = useRef(datasetTrack);
|
| 26 |
const modelChoiceRef = useRef(modelChoice);
|
| 27 |
|
| 28 |
+
// Sync refs safely
|
| 29 |
useEffect(() => { textRef.current = text; }, [text]);
|
| 30 |
useEffect(() => { datasetTrackRef.current = datasetTrack; }, [datasetTrack]);
|
| 31 |
useEffect(() => { modelChoiceRef.current = modelChoice; }, [modelChoice]);
|
| 32 |
|
| 33 |
+
// Initial dark mode setup (flicker-free)
|
| 34 |
useEffect(() => {
|
| 35 |
document.documentElement.classList.toggle("dark", isDark);
|
| 36 |
}, [isDark]);
|
| 37 |
|
| 38 |
+
|
| 39 |
useEffect(() => {
|
| 40 |
+
const controller = new AbortController();
|
| 41 |
let active = true;
|
| 42 |
+
|
| 43 |
fetchSamples(datasetTrack)
|
| 44 |
.then((items) => {
|
| 45 |
if (!active) return;
|
| 46 |
setSamples(items);
|
| 47 |
+
// Only override if default or empty
|
| 48 |
+
setText((prev) => {
|
| 49 |
+
if (!prev || prev === FALLBACK_TEXT.gcc || prev === FALLBACK_TEXT.us || samples.some(s => s.text === prev)) {
|
| 50 |
+
return items.length ? items[0].text : FALLBACK_TEXT[datasetTrack];
|
| 51 |
+
}
|
| 52 |
+
return prev;
|
| 53 |
+
});
|
| 54 |
})
|
| 55 |
+
.catch((err) => {
|
| 56 |
+
if (!active || err.name === 'AbortError') return;
|
| 57 |
setSamples([]);
|
| 58 |
setText(FALLBACK_TEXT[datasetTrack]);
|
| 59 |
});
|
| 60 |
+
|
| 61 |
setSummary("");
|
| 62 |
+
return () => {
|
| 63 |
+
active = false;
|
| 64 |
+
controller.abort();
|
| 65 |
+
};
|
| 66 |
}, [datasetTrack]);
|
| 67 |
|
| 68 |
+
const runSummarize = useCallback(async (targetModelId) => {
|
| 69 |
const modelToUse = targetModelId || modelChoiceRef.current;
|
| 70 |
const currentText = textRef.current;
|
| 71 |
const currentTrack = datasetTrackRef.current;
|
| 72 |
+
|
| 73 |
if (!currentText || currentText.trim().length < 10) return;
|
| 74 |
+
|
| 75 |
setLoading(true);
|
| 76 |
setSummary("");
|
| 77 |
+
|
| 78 |
try {
|
| 79 |
+
const data = await summarizeText({
|
| 80 |
+
text: currentText,
|
| 81 |
+
model_choice: modelToUse,
|
| 82 |
+
dataset_track: currentTrack
|
| 83 |
+
});
|
| 84 |
setSummary(data.summary);
|
| 85 |
} catch (error) {
|
| 86 |
setSummary(`Error: ${error?.response?.data?.detail || error.message}`);
|
| 87 |
} finally {
|
| 88 |
setLoading(false);
|
| 89 |
}
|
| 90 |
+
}, []);
|
| 91 |
+
|
| 92 |
+
const handleSummarize = useCallback(() => runSummarize(), [runSummarize]);
|
| 93 |
+
const handleModelSelect = useCallback((modelId) => {
|
| 94 |
+
setModelChoice(modelId);
|
| 95 |
+
runSummarize(modelId);
|
| 96 |
+
}, [runSummarize]);
|
| 97 |
|
|
|
|
|
|
|
| 98 |
|
| 99 |
return (
|
| 100 |
<div className="min-h-screen bg-slate-50 dark:bg-[#060d1f] text-slate-900 dark:text-slate-200 transition-colors duration-300 pb-16 dark:bg-grid">
|