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
- className={`relative flex flex-col items-start rounded-xl border p-4 text-left transition-all ${cardCls}`}
 
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
- // Keep dark class in sync when user toggles
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
- setText(items.length ? items[0].text : FALLBACK_TEXT[datasetTrack]);
 
 
 
 
 
 
48
  })
49
- .catch(() => {
50
- if (!active) return;
51
  setSamples([]);
52
  setText(FALLBACK_TEXT[datasetTrack]);
53
  });
 
54
  setSummary("");
55
- return () => { active = false; };
 
 
 
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({ text: currentText, model_choice: modelToUse, dataset_track: currentTrack });
 
 
 
 
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">