heymenn commited on
Commit
36fa73c
·
1 Parent(s): c1442ee

add features stop and filter class

Browse files
Files changed (3) hide show
  1. App.tsx +100 -54
  2. backend/main.py +9 -1
  3. components/StatsDashboard.tsx +40 -6
App.tsx CHANGED
@@ -31,6 +31,8 @@ const App: React.FC = () => {
31
  const [selectedPatternId, setSelectedPatternId] = useState<number | null>(null);
32
  const [isAnalyzing, setIsAnalyzing] = useState(false);
33
 
 
 
34
  // Metadata state for Backend
35
  const [selectedWG, setSelectedWG] = useState<string>("");
36
  const [selectedMeeting, setSelectedMeeting] = useState<string>("");
@@ -96,15 +98,33 @@ const App: React.FC = () => {
96
  setInnovations([]);
97
  }, []);
98
 
 
 
 
 
99
  // Logic: Backend Processing Engine (Phase 2 - SQLite)
100
  const handleExtractInnovations = async () => {
101
  if (files.length === 0) return;
102
  setIsProcessing(true);
103
- setProcessedCount(0);
104
- setInnovations([]);
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  // Sequential Processing Logic
107
- for (let i = 0; i < files.length; i++) {
 
 
108
  const file = files[i];
109
  setCurrentProcessingFile(file.filename);
110
 
@@ -120,6 +140,7 @@ const App: React.FC = () => {
120
 
121
  setIsProcessing(false);
122
  setCurrentProcessingFile("");
 
123
  };
124
 
125
  const handleClassify = async (id: string, classification: Classification) => {
@@ -172,51 +193,62 @@ const App: React.FC = () => {
172
  }
173
 
174
  setIsAnalyzing(true);
 
175
 
176
  try {
177
- const updatedInnovations = [...innovations];
 
 
 
 
178
 
179
- for (let i = 0; i < updatedInnovations.length; i++) {
180
- const inv = updatedInnovations[i];
181
- let result: string | null = null;
182
- let resultId: number | undefined;
 
 
 
 
 
 
 
183
 
184
  // If it's an uploaded file (id starts with uploaded-), pass text content
185
  if (inv.id.startsWith('uploaded-')) {
186
  const res = await backendService.analyzeContent(selectedPatternId, undefined, inv.answer);
187
- if (res) {
188
- updatedInnovations[i] = {
189
- ...inv,
190
- analysis_result: res.content,
191
- result_id: res.result_id,
192
- methodology: res.methodology,
193
- context: res.context,
194
- problem: res.problem,
195
- pattern_name: res.pattern_name
196
- };
197
- }
198
  } else {
199
  // It's a processed doc, pass the ID
200
  const res = await backendService.analyzeContent(selectedPatternId, inv.id);
201
- if (res) {
202
- updatedInnovations[i] = {
203
- ...inv,
204
- analysis_result: res.content,
205
- result_id: res.result_id,
206
- methodology: res.methodology,
207
- context: res.context,
208
- problem: res.problem,
209
- pattern_name: res.pattern_name
210
- };
211
- }
 
 
 
 
 
 
 
 
 
212
  }
213
  }
214
 
215
- setInnovations(updatedInnovations);
216
  } catch (error) {
217
  console.error("Error during analysis:", error);
218
  } finally {
219
  setIsAnalyzing(false);
 
220
  }
221
  };
222
 
@@ -302,18 +334,23 @@ const App: React.FC = () => {
302
  </div>
303
  )}
304
 
305
- <div className="mt-4">
306
- <button
307
- onClick={handleExtractInnovations}
308
- disabled={files.length === 0 || isProcessing}
309
- className="w-full bg-slate-900 hover:bg-slate-800 text-white font-medium py-3 rounded-lg shadow-md transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
310
- >
311
- {isProcessing ? (
312
- <><CircleDashed className="w-4 h-4 mr-2 animate-spin" /> Processing...</>
313
- ) : (
314
- <><Play className="w-4 h-4 mr-2 fill-current" /> Start Extraction</>
315
- )}
316
- </button>
 
 
 
 
 
317
  </div>
318
  </div>
319
 
@@ -346,17 +383,26 @@ const App: React.FC = () => {
346
  {/* Upload status is now handled in the main list */}
347
  </div>
348
 
349
- <button
350
- onClick={handleAnalyze}
351
- disabled={isAnalyzing || innovations.length === 0}
352
- className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 rounded-lg shadow-sm transition-all disabled:opacity-50 flex items-center justify-center text-sm"
353
- >
354
- {isAnalyzing ? (
355
- <><CircleDashed className="w-4 h-4 mr-2 animate-spin" /> Analyzing...</>
356
- ) : (
357
- <><Play className="w-4 h-4 mr-2 fill-current" /> Analyse</>
358
- )}
359
- </button>
 
 
 
 
 
 
 
 
 
360
  </div>
361
  </div>
362
  </div>
 
31
  const [selectedPatternId, setSelectedPatternId] = useState<number | null>(null);
32
  const [isAnalyzing, setIsAnalyzing] = useState(false);
33
 
34
+ const stopRef = React.useRef(false);
35
+
36
  // Metadata state for Backend
37
  const [selectedWG, setSelectedWG] = useState<string>("");
38
  const [selectedMeeting, setSelectedMeeting] = useState<string>("");
 
98
  setInnovations([]);
99
  }, []);
100
 
101
+ const handleStop = () => {
102
+ stopRef.current = true;
103
+ };
104
+
105
  // Logic: Backend Processing Engine (Phase 2 - SQLite)
106
  const handleExtractInnovations = async () => {
107
  if (files.length === 0) return;
108
  setIsProcessing(true);
109
+
110
+ let startIndex = 0;
111
+
112
+ // Resume logic: If we have processed some but not all
113
+ if (processedCount > 0 && processedCount < files.length) {
114
+ startIndex = processedCount;
115
+ } else {
116
+ // Start fresh
117
+ setInnovations([]);
118
+ setProcessedCount(0);
119
+ startIndex = 0;
120
+ }
121
+
122
+ stopRef.current = false;
123
 
124
  // Sequential Processing Logic
125
+ for (let i = startIndex; i < files.length; i++) {
126
+ if (stopRef.current) break;
127
+
128
  const file = files[i];
129
  setCurrentProcessingFile(file.filename);
130
 
 
140
 
141
  setIsProcessing(false);
142
  setCurrentProcessingFile("");
143
+ stopRef.current = false;
144
  };
145
 
146
  const handleClassify = async (id: string, classification: Classification) => {
 
193
  }
194
 
195
  setIsAnalyzing(true);
196
+ stopRef.current = false;
197
 
198
  try {
199
+ // Use index to iterate
200
+ const total = innovations.length;
201
+
202
+ for (let i = 0; i < total; i++) {
203
+ if (stopRef.current) break;
204
 
205
+ // Always get fresh state in loop if needed, but here we can iterate by index
206
+ // However, updates need to be functional
207
+ // Let's just grab the item from current state reference if possible or just use the initial list
208
+ // Since we are not adding items during analysis, looking up by ID is safer for updates
209
+ // But iterating the initial list is fine.
210
+ const inv = innovations[i]; // accessing closed-over innovations is fine as we only read static data from it for the request
211
+
212
+ // Skip if already analyzed or classification is DELETE
213
+ if (inv.classification === Classification.DELETE) continue; // optimization
214
+
215
+ let analysisData: any = null;
216
 
217
  // If it's an uploaded file (id starts with uploaded-), pass text content
218
  if (inv.id.startsWith('uploaded-')) {
219
  const res = await backendService.analyzeContent(selectedPatternId, undefined, inv.answer);
220
+ if (res) analysisData = res;
 
 
 
 
 
 
 
 
 
 
221
  } else {
222
  // It's a processed doc, pass the ID
223
  const res = await backendService.analyzeContent(selectedPatternId, inv.id);
224
+ if (res) analysisData = res;
225
+ }
226
+
227
+ if (analysisData) {
228
+ // Real-time update
229
+ setInnovations(current =>
230
+ current.map(item =>
231
+ item.id === inv.id
232
+ ? {
233
+ ...item,
234
+ analysis_result: analysisData.content,
235
+ result_id: analysisData.result_id,
236
+ methodology: analysisData.methodology,
237
+ context: analysisData.context,
238
+ problem: analysisData.problem,
239
+ pattern_name: analysisData.pattern_name
240
+ }
241
+ : item
242
+ )
243
+ );
244
  }
245
  }
246
 
 
247
  } catch (error) {
248
  console.error("Error during analysis:", error);
249
  } finally {
250
  setIsAnalyzing(false);
251
+ stopRef.current = false;
252
  }
253
  };
254
 
 
334
  </div>
335
  )}
336
 
337
+ <div className="mt-4 flex space-x-2">
338
+ {!isProcessing && !isAnalyzing ? (
339
+ <button
340
+ onClick={handleExtractInnovations}
341
+ disabled={files.length === 0}
342
+ className="w-full bg-slate-900 hover:bg-slate-800 text-white font-medium py-3 rounded-lg shadow-md transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
343
+ >
344
+ <Play className="w-4 h-4 mr-2 fill-current" /> Start Extraction
345
+ </button>
346
+ ) : (
347
+ <button
348
+ onClick={handleStop}
349
+ className="w-full bg-red-600 hover:bg-red-700 text-white font-medium py-3 rounded-lg shadow-md transition-all flex items-center justify-center animate-pulse"
350
+ >
351
+ <CircleDashed className="w-4 h-4 mr-2 animate-spin" /> Stop {isProcessing ? "Extraction" : "Analysis"}
352
+ </button>
353
+ )}
354
  </div>
355
  </div>
356
 
 
383
  {/* Upload status is now handled in the main list */}
384
  </div>
385
 
386
+ {!isAnalyzing && !isProcessing ? (
387
+ <button
388
+ onClick={handleAnalyze}
389
+ disabled={innovations.length === 0}
390
+ className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 rounded-lg shadow-sm transition-all disabled:opacity-50 flex items-center justify-center text-sm"
391
+ >
392
+ <Play className="w-4 h-4 mr-2 fill-current" /> Analyse
393
+ </button>
394
+ ) : isAnalyzing ? (
395
+ <button
396
+ onClick={handleStop}
397
+ className="w-full bg-red-600 hover:bg-red-700 text-white font-medium py-2 rounded-lg shadow-sm transition-all flex items-center justify-center text-sm animate-pulse"
398
+ >
399
+ <CircleDashed className="w-4 h-4 mr-2 animate-spin" /> Stop Analysis
400
+ </button>
401
+ ) : (
402
+ <button disabled className="w-full bg-slate-300 text-white font-medium py-2 rounded-lg shadow-sm cursor-not-allowed flex items-center justify-center text-sm">
403
+ Processing...
404
+ </button>
405
+ )}
406
  </div>
407
  </div>
408
  </div>
backend/main.py CHANGED
@@ -26,7 +26,15 @@ logging.basicConfig(level=logging.INFO)
26
  logger = logging.getLogger(__name__)
27
 
28
  app = FastAPI(title="3GPP Innovation Backend")
29
-
 
 
 
 
 
 
 
 
30
  # Initialize DataService
31
  data_service = DataService()
32
 
 
26
  logger = logging.getLogger(__name__)
27
 
28
  app = FastAPI(title="3GPP Innovation Backend")
29
+ from fastapi.middleware.cors import CORSMiddleware
30
+
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"], # React dev server
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
  # Initialize DataService
39
  data_service = DataService()
40
 
components/StatsDashboard.tsx CHANGED
@@ -15,6 +15,14 @@ const StatsDashboard: React.FC<StatsDashboardProps> = ({ isVisible }) => {
15
  const [dbInnovations, setDbInnovations] = useState<ResultResponse[]>([]);
16
  const [loading, setLoading] = useState(false);
17
 
 
 
 
 
 
 
 
 
18
  // Fetch from DB for the "True" state
19
  const fetchDbInnovations = async () => {
20
  setLoading(true);
@@ -52,9 +60,15 @@ const StatsDashboard: React.FC<StatsDashboardProps> = ({ isVisible }) => {
52
  }
53
  };
54
 
 
 
 
 
 
 
55
  // Filter for Display (High, Medium, Low, Unclassified)
56
  const displayInnovations = dbInnovations.filter(i =>
57
- [Classification.HIGH, Classification.MEDIUM, Classification.LOW, Classification.UNCLASSIFIED].includes(i.classification as Classification)
58
  );
59
 
60
  // Stats Logic - Use DB data for charts to be consistent with the list below
@@ -122,12 +136,32 @@ const StatsDashboard: React.FC<StatsDashboardProps> = ({ isVisible }) => {
122
 
123
  {/* Classified List Section */}
124
  <div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200">
125
- <div className="flex items-center justify-between mb-6">
126
  <h3 className="text-lg font-semibold text-slate-800">Classified Innovations</h3>
127
- <button onClick={fetchDbInnovations} className="text-sm text-slate-500 hover:text-blue-600 flex items-center">
128
- {loading && <CircleDashed className="w-4 h-4 mr-1 animate-spin" />}
129
- Refresh
130
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  </div>
132
 
133
  {displayInnovations.length === 0 ? (
 
15
  const [dbInnovations, setDbInnovations] = useState<ResultResponse[]>([]);
16
  const [loading, setLoading] = useState(false);
17
 
18
+ // Filter Toggle State
19
+ const [activeFilters, setActiveFilters] = useState<Classification[]>([
20
+ Classification.HIGH,
21
+ Classification.MEDIUM,
22
+ Classification.LOW,
23
+ Classification.UNCLASSIFIED
24
+ ]);
25
+
26
  // Fetch from DB for the "True" state
27
  const fetchDbInnovations = async () => {
28
  setLoading(true);
 
60
  }
61
  };
62
 
63
+ const toggleFilter = (cls: Classification) => {
64
+ setActiveFilters(prev =>
65
+ prev.includes(cls) ? prev.filter(c => c !== cls) : [...prev, cls]
66
+ );
67
+ };
68
+
69
  // Filter for Display (High, Medium, Low, Unclassified)
70
  const displayInnovations = dbInnovations.filter(i =>
71
+ activeFilters.includes(i.classification as Classification)
72
  );
73
 
74
  // Stats Logic - Use DB data for charts to be consistent with the list below
 
136
 
137
  {/* Classified List Section */}
138
  <div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200">
139
+ <div className="flex flex-col md:flex-row items-center justify-between mb-6 gap-4">
140
  <h3 className="text-lg font-semibold text-slate-800">Classified Innovations</h3>
141
+
142
+ <div className="flex items-center space-x-2 flex-wrap gap-y-2 justify-center">
143
+ {[Classification.HIGH, Classification.MEDIUM, Classification.LOW, Classification.UNCLASSIFIED].map(cls => (
144
+ <button
145
+ key={cls}
146
+ onClick={() => toggleFilter(cls)}
147
+ className={`px-3 py-1.5 rounded text-xs font-medium border transition-colors flex items-center
148
+ ${activeFilters.includes(cls)
149
+ ? `bg-slate-800 text-white border-slate-800`
150
+ : `bg-white text-slate-600 border-slate-300 hover:bg-slate-50`
151
+ }`}
152
+ >
153
+ <span
154
+ className="w-2 h-2 rounded-full mr-1.5"
155
+ style={{ backgroundColor: COLOR_MAP[cls] }}
156
+ ></span>
157
+ {cls}
158
+ </button>
159
+ ))}
160
+ <button onClick={fetchDbInnovations} className="text-sm text-slate-500 hover:text-blue-600 flex items-center ml-4">
161
+ {loading && <CircleDashed className="w-4 h-4 mr-1 animate-spin" />}
162
+ Refresh
163
+ </button>
164
+ </div>
165
  </div>
166
 
167
  {displayInnovations.length === 0 ? (