Commit ·
6d41c34
1
Parent(s): c6fefb4
fix: use structured report_json and show on-page autofill status
Browse files- backend/app.py +2 -1
- src/pages/ReportPage.tsx +29 -2
backend/app.py
CHANGED
|
@@ -411,7 +411,8 @@ Do NOT include any other keys. Do NOT wrap in markdown. Return raw JSON only."""
|
|
| 411 |
# Return as JSON object (not string) so it's properly encoded by FastAPI
|
| 412 |
return JSONResponse({
|
| 413 |
"status": "success",
|
| 414 |
-
"report": cleaned_text, #
|
|
|
|
| 415 |
"model": used_model
|
| 416 |
})
|
| 417 |
except json.JSONDecodeError as je:
|
|
|
|
| 411 |
# Return as JSON object (not string) so it's properly encoded by FastAPI
|
| 412 |
return JSONResponse({
|
| 413 |
"status": "success",
|
| 414 |
+
"report": cleaned_text, # Backward-compatible JSON string
|
| 415 |
+
"report_json": parsed_json, # Structured payload for robust frontend mapping
|
| 416 |
"model": used_model
|
| 417 |
})
|
| 418 |
except json.JSONDecodeError as je:
|
src/pages/ReportPage.tsx
CHANGED
|
@@ -79,6 +79,7 @@ export function ReportPage({
|
|
| 79 |
const [showCompletionModal, setShowCompletionModal] = useState(false);
|
| 80 |
const [isGenerating, setIsGenerating] = useState(false);
|
| 81 |
const [aiError, setAiError] = useState<string | null>(null);
|
|
|
|
| 82 |
const imageBucketsRef = useRef(imageBuckets);
|
| 83 |
const reportContentRef = useRef<HTMLDivElement>(null);
|
| 84 |
const patientId = sessionStore.get().patientInfo?.id;
|
|
@@ -156,6 +157,7 @@ export function ReportPage({
|
|
| 156 |
const generateWithAI = async () => {
|
| 157 |
setIsGenerating(true);
|
| 158 |
setAiError(null);
|
|
|
|
| 159 |
|
| 160 |
try {
|
| 161 |
const session = sessionStore.get();
|
|
@@ -206,6 +208,27 @@ export function ReportPage({
|
|
| 206 |
throw new Error('Invalid response from backend');
|
| 207 |
}
|
| 208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
// Get the report text - it should be a JSON string
|
| 210 |
let reportText: string = String(data.report).trim();
|
| 211 |
let parsed: Record<string, string> = {};
|
|
@@ -311,14 +334,15 @@ export function ReportPage({
|
|
| 311 |
// Show success feedback
|
| 312 |
if (missingKeys.length === 0) {
|
| 313 |
setAiError(null);
|
|
|
|
| 314 |
} else {
|
| 315 |
-
|
| 316 |
}
|
| 317 |
|
| 318 |
} catch (err: any) {
|
| 319 |
console.error('Gemini error:', err);
|
| 320 |
const msg = err?.message || err?.toString() || 'Unknown error';
|
| 321 |
-
setAiError(`
|
| 322 |
} finally {
|
| 323 |
setIsGenerating(false);
|
| 324 |
}
|
|
@@ -568,6 +592,9 @@ export function ReportPage({
|
|
| 568 |
{aiError && (
|
| 569 |
<p className="text-sm text-red-600 bg-red-50 border border-red-200 rounded-lg px-3 py-2">{aiError}</p>
|
| 570 |
)}
|
|
|
|
|
|
|
|
|
|
| 571 |
</div>
|
| 572 |
</div>
|
| 573 |
</div>
|
|
|
|
| 79 |
const [showCompletionModal, setShowCompletionModal] = useState(false);
|
| 80 |
const [isGenerating, setIsGenerating] = useState(false);
|
| 81 |
const [aiError, setAiError] = useState<string | null>(null);
|
| 82 |
+
const [aiStatus, setAiStatus] = useState<string | null>(null);
|
| 83 |
const imageBucketsRef = useRef(imageBuckets);
|
| 84 |
const reportContentRef = useRef<HTMLDivElement>(null);
|
| 85 |
const patientId = sessionStore.get().patientInfo?.id;
|
|
|
|
| 157 |
const generateWithAI = async () => {
|
| 158 |
setIsGenerating(true);
|
| 159 |
setAiError(null);
|
| 160 |
+
setAiStatus(null);
|
| 161 |
|
| 162 |
try {
|
| 163 |
const session = sessionStore.get();
|
|
|
|
| 208 |
throw new Error('Invalid response from backend');
|
| 209 |
}
|
| 210 |
|
| 211 |
+
// Prefer structured JSON from backend when available.
|
| 212 |
+
if (data.report_json && typeof data.report_json === 'object') {
|
| 213 |
+
const parsed = data.report_json as Record<string, string>;
|
| 214 |
+
const updatedFormData = {
|
| 215 |
+
examQuality: String(parsed.examQuality || '').trim(),
|
| 216 |
+
transformationZone: String(parsed.transformationZone || '').trim(),
|
| 217 |
+
acetowL: String(parsed.acetowL || '').trim(),
|
| 218 |
+
nativeFindings: String(parsed.nativeFindings || '').trim(),
|
| 219 |
+
aceticFindings: String(parsed.aceticFindings || '').trim(),
|
| 220 |
+
colposcopicFindings: String(parsed.colposcopicFindings || '').trim(),
|
| 221 |
+
treatmentPlan: String(parsed.treatmentPlan || '').trim(),
|
| 222 |
+
followUp: String(parsed.followUp || '').trim(),
|
| 223 |
+
biopsySites: String(parsed.biopsySites || '').trim(),
|
| 224 |
+
biopsyNotes: String(parsed.biopsyNotes || '').trim(),
|
| 225 |
+
};
|
| 226 |
+
|
| 227 |
+
setFormData(prev => ({ ...prev, ...updatedFormData }));
|
| 228 |
+
setAiStatus('Report generated and fields auto-filled successfully.');
|
| 229 |
+
return;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
// Get the report text - it should be a JSON string
|
| 233 |
let reportText: string = String(data.report).trim();
|
| 234 |
let parsed: Record<string, string> = {};
|
|
|
|
| 334 |
// Show success feedback
|
| 335 |
if (missingKeys.length === 0) {
|
| 336 |
setAiError(null);
|
| 337 |
+
setAiStatus('Report generated and fields auto-filled successfully.');
|
| 338 |
} else {
|
| 339 |
+
setAiStatus(`Report generated. Some fields were missing: ${missingKeys.join(', ')}`);
|
| 340 |
}
|
| 341 |
|
| 342 |
} catch (err: any) {
|
| 343 |
console.error('Gemini error:', err);
|
| 344 |
const msg = err?.message || err?.toString() || 'Unknown error';
|
| 345 |
+
setAiError(`AI generation failed: ${msg}`);
|
| 346 |
} finally {
|
| 347 |
setIsGenerating(false);
|
| 348 |
}
|
|
|
|
| 592 |
{aiError && (
|
| 593 |
<p className="text-sm text-red-600 bg-red-50 border border-red-200 rounded-lg px-3 py-2">{aiError}</p>
|
| 594 |
)}
|
| 595 |
+
{aiStatus && !aiError && (
|
| 596 |
+
<p className="text-sm text-green-700 bg-green-50 border border-green-200 rounded-lg px-3 py-2">{aiStatus}</p>
|
| 597 |
+
)}
|
| 598 |
</div>
|
| 599 |
</div>
|
| 600 |
</div>
|