Spaces:
Running
Running
fix HF register & dynamic fallback
Browse files- frontend/src/components/FilterBar.tsx +15 -4
- frontend/src/components/upload/GeneratedTextSection.tsx +1 -8
- frontend/src/components/upload/ModalComponents.tsx +3 -5
- frontend/src/pages/AdminPage/AdminPage.tsx +87 -8
- frontend/src/pages/UploadPage/UploadPage.tsx +8 -0
- py_backend/alembic/env.py +67 -4
- py_backend/alembic/versions/0020_add_is_fallback_to_models.py +36 -0
- py_backend/app/crud.py +20 -0
- py_backend/app/main.py +97 -0
- py_backend/app/models.py +5 -0
- py_backend/app/routers/admin.py +66 -24
- py_backend/app/routers/caption.py +17 -133
- py_backend/app/routers/models.py +1 -0
- py_backend/app/services/huggingface_service.py +147 -100
- py_backend/app/services/vlm_service.py +185 -181
- py_backend/db_probe.py +7 -0
- py_backend/dbtest.py +8 -0
- py_backend/debug_hf_models.py +0 -132
- py_backend/static/assets/{AdminPage-BwDJigwh.js β AdminPage-a1ONalH9.js} +6 -6
- py_backend/static/assets/ExportModal-DJ-UV1s7.js +0 -1
- py_backend/static/assets/ExportModal-Qf6Y7VAO.js +1 -0
- py_backend/static/assets/{index-D91JzoMl.js β index-4L12dHQs.js} +1 -1
- py_backend/static/assets/{index-C_hAuRbb.js β index-BCz4bkWK.js} +0 -0
- py_backend/static/assets/{index-Bc-_2bce.js β index-Cj53JezQ.js} +2 -2
- py_backend/static/assets/{index-Dw3gbXdi.js β index-D2ncrm5K.js} +2 -2
- py_backend/static/assets/{jszip.min-B2-9j7XO.js β jszip.min-C3B0d4UO.js} +1 -1
- py_backend/static/assets/{useAdmin-B_kWg5HW.js β useAdmin-LSEnRjVv.js} +1 -1
- py_backend/static/index.html +1 -1
- start-local.bat +1 -1
frontend/src/components/FilterBar.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import React, { useState } from 'react';
|
2 |
import { Container, TextInput, SelectInput, MultiSelectInput, Button } from '@ifrc-go/ui';
|
3 |
import { FilterLineIcon } from '@ifrc-go/icons';
|
4 |
import { useFilterContext } from '../hooks/useFilterContext';
|
@@ -21,6 +21,7 @@ export default function FilterBar({
|
|
21 |
isLoadingFilters = false
|
22 |
}: FilterBarProps) {
|
23 |
const [showFilters, setShowFilters] = useState(false);
|
|
|
24 |
|
25 |
const {
|
26 |
search, setSearch,
|
@@ -34,6 +35,11 @@ export default function FilterBar({
|
|
34 |
clearAllFilters
|
35 |
} = useFilterContext();
|
36 |
|
|
|
|
|
|
|
|
|
|
|
37 |
return (
|
38 |
<div className="mb-6 space-y-4">
|
39 |
{/* Layer 1: Search, Filter Button, Clear Filters */}
|
@@ -53,9 +59,14 @@ export default function FilterBar({
|
|
53 |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2 flex-1 min-w-[300px]">
|
54 |
<TextInput
|
55 |
name="search"
|
56 |
-
placeholder="Search
|
57 |
-
value={
|
58 |
-
onChange={(v) =>
|
|
|
|
|
|
|
|
|
|
|
59 |
/>
|
60 |
</Container>
|
61 |
|
|
|
1 |
+
import React, { useState, useEffect } from 'react';
|
2 |
import { Container, TextInput, SelectInput, MultiSelectInput, Button } from '@ifrc-go/ui';
|
3 |
import { FilterLineIcon } from '@ifrc-go/icons';
|
4 |
import { useFilterContext } from '../hooks/useFilterContext';
|
|
|
21 |
isLoadingFilters = false
|
22 |
}: FilterBarProps) {
|
23 |
const [showFilters, setShowFilters] = useState(false);
|
24 |
+
const [searchInput, setSearchInput] = useState('');
|
25 |
|
26 |
const {
|
27 |
search, setSearch,
|
|
|
35 |
clearAllFilters
|
36 |
} = useFilterContext();
|
37 |
|
38 |
+
// Sync local search input with context search value
|
39 |
+
useEffect(() => {
|
40 |
+
setSearchInput(search);
|
41 |
+
}, [search]);
|
42 |
+
|
43 |
return (
|
44 |
<div className="mb-6 space-y-4">
|
45 |
{/* Layer 1: Search, Filter Button, Clear Filters */}
|
|
|
59 |
<Container withInternalPadding className="bg-white/20 backdrop-blur-sm rounded-md p-2 flex-1 min-w-[300px]">
|
60 |
<TextInput
|
61 |
name="search"
|
62 |
+
placeholder="Search"
|
63 |
+
value={searchInput}
|
64 |
+
onChange={(v) => setSearchInput(v || '')}
|
65 |
+
onKeyDown={(e) => {
|
66 |
+
if (e.key === 'Enter') {
|
67 |
+
setSearch(searchInput);
|
68 |
+
}
|
69 |
+
}}
|
70 |
/>
|
71 |
</Container>
|
72 |
|
frontend/src/components/upload/GeneratedTextSection.tsx
CHANGED
@@ -97,14 +97,7 @@ export default function GeneratedTextSection({
|
|
97 |
onClick={onSubmit}
|
98 |
disabled={isSubmitting}
|
99 |
>
|
100 |
-
|
101 |
-
<div className="flex items-center gap-2">
|
102 |
-
<Spinner className="w-4 h-4" />
|
103 |
-
<span>Submitting...</span>
|
104 |
-
</div>
|
105 |
-
) : (
|
106 |
-
"Submit"
|
107 |
-
)}
|
108 |
</Button>
|
109 |
</div>
|
110 |
</Container>
|
|
|
97 |
onClick={onSubmit}
|
98 |
disabled={isSubmitting}
|
99 |
>
|
100 |
+
Submit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
</Button>
|
102 |
</div>
|
103 |
</Container>
|
frontend/src/components/upload/ModalComponents.tsx
CHANGED
@@ -42,11 +42,9 @@ export function FullSizeImageModal({ isOpen, imageUrl, preview, selectedImageDat
|
|
42 |
</div>
|
43 |
<div className={styles.fullSizeModalImage}>
|
44 |
{isLoading ? (
|
45 |
-
<div className=
|
46 |
-
<
|
47 |
-
|
48 |
-
<span>Loading image...</span>
|
49 |
-
</div>
|
50 |
</div>
|
51 |
) : (
|
52 |
<img
|
|
|
42 |
</div>
|
43 |
<div className={styles.fullSizeModalImage}>
|
44 |
{isLoading ? (
|
45 |
+
<div className={styles.loadingContainer}>
|
46 |
+
<Spinner className="text-ifrcRed" />
|
47 |
+
<p className={styles.loadingText}>Loading image...</p>
|
|
|
|
|
48 |
</div>
|
49 |
) : (
|
50 |
<img
|
frontend/src/pages/AdminPage/AdminPage.tsx
CHANGED
@@ -1,8 +1,9 @@
|
|
1 |
// Force rebuild - Frontend updated with edit prompt functionality
|
2 |
import React, { useState, useEffect, useCallback } from 'react';
|
3 |
import { useAdmin } from '../../hooks/useAdmin';
|
4 |
-
import { PageContainer, Heading, Button, Container, TextInput, SelectInput } from '@ifrc-go/ui';
|
5 |
import styles from './AdminPage.module.css';
|
|
|
6 |
|
7 |
const SELECTED_MODEL_KEY = 'selectedVlmModel';
|
8 |
|
@@ -27,6 +28,7 @@ interface ModelData {
|
|
27 |
stub?: boolean;
|
28 |
};
|
29 |
is_available: boolean;
|
|
|
30 |
}
|
31 |
|
32 |
interface ImageTypeData {
|
@@ -42,6 +44,7 @@ export default function AdminPage() {
|
|
42 |
|
43 |
const [availableModels, setAvailableModels] = useState<ModelData[]>([]);
|
44 |
const [selectedModel, setSelectedModel] = useState<string>('');
|
|
|
45 |
|
46 |
// Prompts state
|
47 |
const [availablePrompts, setAvailablePrompts] = useState<PromptData[]>([]);
|
@@ -71,7 +74,8 @@ export default function AdminPage() {
|
|
71 |
model_type: 'custom',
|
72 |
provider: 'huggingface',
|
73 |
model_id: '',
|
74 |
-
is_available: false
|
|
|
75 |
});
|
76 |
|
77 |
// Modal states
|
@@ -109,6 +113,25 @@ export default function AdminPage() {
|
|
109 |
.catch(() => {
|
110 |
// Handle error silently
|
111 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
}, []);
|
113 |
|
114 |
const fetchPrompts = useCallback(() => {
|
@@ -298,6 +321,32 @@ export default function AdminPage() {
|
|
298 |
}
|
299 |
};
|
300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
301 |
// Model management functions
|
302 |
const handleAddModel = async () => {
|
303 |
try {
|
@@ -334,7 +383,8 @@ Model "${newModelData.label}" added successfully!
|
|
334 |
model_type: 'custom',
|
335 |
provider: 'huggingface',
|
336 |
model_id: '',
|
337 |
-
is_available: false
|
|
|
338 |
});
|
339 |
fetchModels(); // Refresh the models list
|
340 |
} else {
|
@@ -354,7 +404,8 @@ Model "${newModelData.label}" added successfully!
|
|
354 |
model_type: model.model_type || 'custom',
|
355 |
provider: model.provider || model.config?.provider || 'huggingface',
|
356 |
model_id: model.model_id || model.config?.model_id || model.m_code,
|
357 |
-
is_available: model.is_available
|
|
|
358 |
});
|
359 |
setShowEditModelForm(true);
|
360 |
};
|
@@ -402,7 +453,8 @@ Model "${newModelData.label}" added successfully!
|
|
402 |
model_type: 'custom',
|
403 |
provider: 'huggingface',
|
404 |
model_id: '',
|
405 |
-
is_available: false
|
|
|
406 |
});
|
407 |
|
408 |
console.log('Refreshing models...');
|
@@ -497,6 +549,14 @@ Model "${newModelData.label}" added successfully!
|
|
497 |
if (!isAuthenticated) {
|
498 |
return (
|
499 |
<PageContainer>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
500 |
<div className="mx-auto max-w-md px-4 sm:px-6 lg:px-8 py-6 sm:py-10">
|
501 |
<div className="text-center mb-8">
|
502 |
<Heading level={2}>Admin Login</Heading>
|
@@ -520,7 +580,7 @@ Model "${newModelData.label}" added successfully!
|
|
520 |
</div>
|
521 |
|
522 |
{error && (
|
523 |
-
<div
|
524 |
<p className="text-sm text-ifrcRed font-medium">{error}</p>
|
525 |
</div>
|
526 |
)}
|
@@ -534,7 +594,7 @@ Model "${newModelData.label}" added successfully!
|
|
534 |
size={2}
|
535 |
disabled={isLoggingIn}
|
536 |
>
|
537 |
-
|
538 |
</Button>
|
539 |
</Container>
|
540 |
</div>
|
@@ -588,6 +648,24 @@ Model "${newModelData.label}" added successfully!
|
|
588 |
keySelector={(o) => o.value}
|
589 |
labelSelector={(o) => o.label}
|
590 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
591 |
|
592 |
</div>
|
593 |
</div>
|
@@ -832,7 +910,8 @@ Model "${newModelData.label}" added successfully!
|
|
832 |
model_type: 'custom',
|
833 |
provider: 'huggingface',
|
834 |
model_id: '',
|
835 |
-
is_available: false
|
|
|
836 |
});
|
837 |
}}
|
838 |
>
|
|
|
1 |
// Force rebuild - Frontend updated with edit prompt functionality
|
2 |
import React, { useState, useEffect, useCallback } from 'react';
|
3 |
import { useAdmin } from '../../hooks/useAdmin';
|
4 |
+
import { PageContainer, Heading, Button, Container, TextInput, SelectInput, Spinner } from '@ifrc-go/ui';
|
5 |
import styles from './AdminPage.module.css';
|
6 |
+
import uploadStyles from '../UploadPage/UploadPage.module.css';
|
7 |
|
8 |
const SELECTED_MODEL_KEY = 'selectedVlmModel';
|
9 |
|
|
|
28 |
stub?: boolean;
|
29 |
};
|
30 |
is_available: boolean;
|
31 |
+
is_fallback: boolean;
|
32 |
}
|
33 |
|
34 |
interface ImageTypeData {
|
|
|
44 |
|
45 |
const [availableModels, setAvailableModels] = useState<ModelData[]>([]);
|
46 |
const [selectedModel, setSelectedModel] = useState<string>('');
|
47 |
+
const [selectedFallbackModel, setSelectedFallbackModel] = useState<string>('');
|
48 |
|
49 |
// Prompts state
|
50 |
const [availablePrompts, setAvailablePrompts] = useState<PromptData[]>([]);
|
|
|
74 |
model_type: 'custom',
|
75 |
provider: 'huggingface',
|
76 |
model_id: '',
|
77 |
+
is_available: false,
|
78 |
+
is_fallback: false
|
79 |
});
|
80 |
|
81 |
// Modal states
|
|
|
113 |
.catch(() => {
|
114 |
// Handle error silently
|
115 |
});
|
116 |
+
|
117 |
+
// Fetch current fallback model
|
118 |
+
fetch('/api/admin/fallback-model', {
|
119 |
+
headers: {
|
120 |
+
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
121 |
+
}
|
122 |
+
})
|
123 |
+
.then(r => r.json())
|
124 |
+
.then(fallbackData => {
|
125 |
+
console.log('Fallback model data received:', fallbackData);
|
126 |
+
if (fallbackData.fallback_model) {
|
127 |
+
setSelectedFallbackModel(fallbackData.fallback_model.m_code);
|
128 |
+
} else {
|
129 |
+
setSelectedFallbackModel('');
|
130 |
+
}
|
131 |
+
})
|
132 |
+
.catch(() => {
|
133 |
+
// Handle error silently
|
134 |
+
});
|
135 |
}, []);
|
136 |
|
137 |
const fetchPrompts = useCallback(() => {
|
|
|
321 |
}
|
322 |
};
|
323 |
|
324 |
+
const handleFallbackModelChange = async (modelCode: string) => {
|
325 |
+
try {
|
326 |
+
const response = await fetch(`/api/admin/models/${modelCode}`, {
|
327 |
+
method: 'PUT',
|
328 |
+
headers: {
|
329 |
+
'Content-Type': 'application/json',
|
330 |
+
'Authorization': `Bearer ${localStorage.getItem('adminToken')}`
|
331 |
+
},
|
332 |
+
body: JSON.stringify({
|
333 |
+
is_fallback: true
|
334 |
+
})
|
335 |
+
});
|
336 |
+
|
337 |
+
if (response.ok) {
|
338 |
+
setSelectedFallbackModel(modelCode);
|
339 |
+
// Refresh models to update the is_fallback status
|
340 |
+
fetchModels();
|
341 |
+
} else {
|
342 |
+
const errorData = await response.json();
|
343 |
+
alert(`Failed to set fallback model: ${errorData.detail || 'Unknown error'}`);
|
344 |
+
}
|
345 |
+
} catch (error) {
|
346 |
+
alert('Error setting fallback model');
|
347 |
+
}
|
348 |
+
};
|
349 |
+
|
350 |
// Model management functions
|
351 |
const handleAddModel = async () => {
|
352 |
try {
|
|
|
383 |
model_type: 'custom',
|
384 |
provider: 'huggingface',
|
385 |
model_id: '',
|
386 |
+
is_available: false,
|
387 |
+
is_fallback: false
|
388 |
});
|
389 |
fetchModels(); // Refresh the models list
|
390 |
} else {
|
|
|
404 |
model_type: model.model_type || 'custom',
|
405 |
provider: model.provider || model.config?.provider || 'huggingface',
|
406 |
model_id: model.model_id || model.config?.model_id || model.m_code,
|
407 |
+
is_available: model.is_available,
|
408 |
+
is_fallback: model.is_fallback
|
409 |
});
|
410 |
setShowEditModelForm(true);
|
411 |
};
|
|
|
453 |
model_type: 'custom',
|
454 |
provider: 'huggingface',
|
455 |
model_id: '',
|
456 |
+
is_available: false,
|
457 |
+
is_fallback: false
|
458 |
});
|
459 |
|
460 |
console.log('Refreshing models...');
|
|
|
549 |
if (!isAuthenticated) {
|
550 |
return (
|
551 |
<PageContainer>
|
552 |
+
{/* Login Loading State */}
|
553 |
+
{isLoggingIn && (
|
554 |
+
<div className={uploadStyles.loadingContainer}>
|
555 |
+
<Spinner className="text-ifrcRed" />
|
556 |
+
<p className={uploadStyles.loadingText}>Logging in...</p>
|
557 |
+
</div>
|
558 |
+
)}
|
559 |
+
|
560 |
<div className="mx-auto max-w-md px-4 sm:px-6 lg:px-8 py-6 sm:py-10">
|
561 |
<div className="text-center mb-8">
|
562 |
<Heading level={2}>Admin Login</Heading>
|
|
|
580 |
</div>
|
581 |
|
582 |
{error && (
|
583 |
+
<div>
|
584 |
<p className="text-sm text-ifrcRed font-medium">{error}</p>
|
585 |
</div>
|
586 |
)}
|
|
|
594 |
size={2}
|
595 |
disabled={isLoggingIn}
|
596 |
>
|
597 |
+
Login
|
598 |
</Button>
|
599 |
</Container>
|
600 |
</div>
|
|
|
648 |
keySelector={(o) => o.value}
|
649 |
labelSelector={(o) => o.label}
|
650 |
/>
|
651 |
+
|
652 |
+
<SelectInput
|
653 |
+
label="Fallback"
|
654 |
+
name="fallback-model"
|
655 |
+
value={selectedFallbackModel}
|
656 |
+
onChange={(newValue) => handleFallbackModelChange(newValue || '')}
|
657 |
+
options={[
|
658 |
+
{ value: '', label: 'No fallback (use STUB_MODEL)' },
|
659 |
+
...availableModels
|
660 |
+
.filter(model => model.is_available)
|
661 |
+
.map(model => ({
|
662 |
+
value: model.m_code,
|
663 |
+
label: model.label
|
664 |
+
}))
|
665 |
+
]}
|
666 |
+
keySelector={(o) => o.value}
|
667 |
+
labelSelector={(o) => o.label}
|
668 |
+
/>
|
669 |
|
670 |
</div>
|
671 |
</div>
|
|
|
910 |
model_type: 'custom',
|
911 |
provider: 'huggingface',
|
912 |
model_id: '',
|
913 |
+
is_available: false,
|
914 |
+
is_fallback: false
|
915 |
});
|
916 |
}}
|
917 |
>
|
frontend/src/pages/UploadPage/UploadPage.tsx
CHANGED
@@ -1212,6 +1212,14 @@ export default function UploadPage() {
|
|
1212 |
{/* Step 2B: Rating and Generated Text */}
|
1213 |
{step === '2b' && (
|
1214 |
<div className={styles.step2bLayout}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1215 |
<div className={`${styles.topRow} ${isPerformanceConfirmed ? styles.ratingHidden : ''}`}>
|
1216 |
<div className={styles.imageSection}>
|
1217 |
<ImagePreviewSection
|
|
|
1212 |
{/* Step 2B: Rating and Generated Text */}
|
1213 |
{step === '2b' && (
|
1214 |
<div className={styles.step2bLayout}>
|
1215 |
+
{/* Submit Loading State */}
|
1216 |
+
{isSubmitting && (
|
1217 |
+
<div className={styles.loadingContainer}>
|
1218 |
+
<Spinner className="text-ifrcRed" />
|
1219 |
+
<p className={styles.loadingText}>Submitting...</p>
|
1220 |
+
</div>
|
1221 |
+
)}
|
1222 |
+
|
1223 |
<div className={`${styles.topRow} ${isPerformanceConfirmed ? styles.ratingHidden : ''}`}>
|
1224 |
<div className={styles.imageSection}>
|
1225 |
<ImagePreviewSection
|
py_backend/alembic/env.py
CHANGED
@@ -1,8 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
import sys
|
3 |
from dotenv import load_dotenv
|
4 |
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
8 |
|
@@ -29,15 +61,33 @@ def _get_db_url() -> str:
|
|
29 |
Prefer a dedicated migration URL; otherwise use the app URL.
|
30 |
Only adds sslmode=require for remote connections (not localhost).
|
31 |
"""
|
|
|
|
|
|
|
32 |
url = os.getenv("ALEMBIC_DATABASE_URL") or os.getenv("DATABASE_URL")
|
33 |
if not url:
|
|
|
34 |
raise RuntimeError("Set ALEMBIC_DATABASE_URL or DATABASE_URL for Alembic migrations.")
|
35 |
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
if url.startswith("psql '") and url.endswith("'"):
|
39 |
url = url[6:-1]
|
40 |
-
print(
|
41 |
|
42 |
if "sslmode=" not in url and "localhost" not in url and "127.0.0.1" not in url:
|
43 |
url = f"{url}{'&' if '?' in url else '?'}sslmode=require"
|
@@ -67,7 +117,20 @@ def run_migrations_online() -> None:
|
|
67 |
url = _get_db_url()
|
68 |
print(f"Creating engine with URL: {url}")
|
69 |
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
print("Engine created successfully")
|
72 |
|
73 |
with connectable.connect() as connection:
|
|
|
1 |
+
# ---- ensure .env is loaded for alembic too ----
|
2 |
+
from pathlib import Path
|
3 |
+
try:
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
# Use utf-8-sig to strip BOM just in case
|
6 |
+
load_dotenv(dotenv_path=Path(__file__).resolve().parents[1] / ".env",
|
7 |
+
override=True, encoding="utf-8-sig")
|
8 |
+
except Exception:
|
9 |
+
pass
|
10 |
+
# -----------------------------------------------
|
11 |
+
|
12 |
import os
|
13 |
import sys
|
14 |
from dotenv import load_dotenv
|
15 |
|
16 |
+
# Set environment encoding to handle Windows encoding issues
|
17 |
+
if sys.platform == "win32":
|
18 |
+
import locale
|
19 |
+
try:
|
20 |
+
# Try to set UTF-8 encoding for environment variables
|
21 |
+
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
22 |
+
except:
|
23 |
+
pass
|
24 |
+
|
25 |
+
|
26 |
+
env_path = os.path.join(os.path.dirname(__file__), '..', '.env')
|
27 |
+
print(f"Looking for .env file at: {env_path}")
|
28 |
+
print(f".env file exists: {os.path.exists(env_path)}")
|
29 |
+
|
30 |
+
if os.path.exists(env_path):
|
31 |
+
load_dotenv(env_path, encoding="utf-8-sig")
|
32 |
+
print("SUCCESS: Loaded .env file from py_backend directory")
|
33 |
+
else:
|
34 |
+
print("ERROR: .env file not found in py_backend directory")
|
35 |
+
# Try current directory
|
36 |
+
load_dotenv(encoding="utf-8-sig")
|
37 |
+
print("INFO: Attempted to load .env from current directory")
|
38 |
|
39 |
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
40 |
|
|
|
61 |
Prefer a dedicated migration URL; otherwise use the app URL.
|
62 |
Only adds sslmode=require for remote connections (not localhost).
|
63 |
"""
|
64 |
+
# Debug: Environment variables loaded
|
65 |
+
print("Environment variables loaded")
|
66 |
+
|
67 |
url = os.getenv("ALEMBIC_DATABASE_URL") or os.getenv("DATABASE_URL")
|
68 |
if not url:
|
69 |
+
print("No DATABASE_URL found in environment")
|
70 |
raise RuntimeError("Set ALEMBIC_DATABASE_URL or DATABASE_URL for Alembic migrations.")
|
71 |
|
72 |
+
# Clean the URL to remove any problematic characters (fallback for edge cases)
|
73 |
+
try:
|
74 |
+
# Test if the URL can be used for connection
|
75 |
+
url.encode('utf-8').decode('utf-8')
|
76 |
+
except UnicodeError:
|
77 |
+
print("WARNING: Encoding issue detected in database URL, attempting to clean...")
|
78 |
+
# Replace common problematic characters
|
79 |
+
url = url.replace('"', '"').replace('"', '"') # Smart quotes
|
80 |
+
url = url.replace(''', "'").replace(''', "'") # Smart apostrophes
|
81 |
+
url = url.replace('β', '-').replace('β', '-') # En/em dashes
|
82 |
+
# Remove any non-ASCII characters
|
83 |
+
url = ''.join(char for char in url if ord(char) < 128)
|
84 |
+
print("Cleaned URL: [HIDDEN]")
|
85 |
+
|
86 |
+
print("Alembic database URL: [HIDDEN]")
|
87 |
|
88 |
if url.startswith("psql '") and url.endswith("'"):
|
89 |
url = url[6:-1]
|
90 |
+
print("Cleaned URL: [HIDDEN]")
|
91 |
|
92 |
if "sslmode=" not in url and "localhost" not in url and "127.0.0.1" not in url:
|
93 |
url = f"{url}{'&' if '?' in url else '?'}sslmode=require"
|
|
|
117 |
url = _get_db_url()
|
118 |
print(f"Creating engine with URL: {url}")
|
119 |
|
120 |
+
# Add encoding parameters to handle Windows encoding issues
|
121 |
+
engine_kwargs = {
|
122 |
+
'poolclass': pool.NullPool,
|
123 |
+
'future': True,
|
124 |
+
}
|
125 |
+
|
126 |
+
# For PostgreSQL connections, add encoding parameters
|
127 |
+
if url.startswith('postgresql://'):
|
128 |
+
engine_kwargs['connect_args'] = {
|
129 |
+
'client_encoding': 'utf8',
|
130 |
+
'options': '-c client_encoding=utf8'
|
131 |
+
}
|
132 |
+
|
133 |
+
connectable = create_engine(url, **engine_kwargs)
|
134 |
print("Engine created successfully")
|
135 |
|
136 |
with connectable.connect() as connection:
|
py_backend/alembic/versions/0020_add_is_fallback_to_models.py
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""add_is_fallback_to_models
|
2 |
+
|
3 |
+
Revision ID: 0020_add_is_fallback_to_models
|
4 |
+
Revises: 0019_add_image_count_to_captions
|
5 |
+
Create Date: 2025-01-06 12:00:00.000000
|
6 |
+
|
7 |
+
"""
|
8 |
+
from alembic import op
|
9 |
+
import sqlalchemy as sa
|
10 |
+
|
11 |
+
|
12 |
+
# revision identifiers, used by Alembic.
|
13 |
+
revision = '0020_add_is_fallback_to_models'
|
14 |
+
down_revision = '0019_add_image_count_to_captions'
|
15 |
+
branch_labels = None
|
16 |
+
depends_on = None
|
17 |
+
|
18 |
+
|
19 |
+
def upgrade() -> None:
|
20 |
+
# Add is_fallback column to models table
|
21 |
+
op.add_column('models', sa.Column('is_fallback', sa.Boolean(), nullable=False, server_default='false'))
|
22 |
+
|
23 |
+
# Add check constraint to ensure fallback model must be available
|
24 |
+
op.create_check_constraint(
|
25 |
+
'fallback_must_be_available',
|
26 |
+
'models',
|
27 |
+
'NOT (is_fallback = true AND is_available = false)'
|
28 |
+
)
|
29 |
+
|
30 |
+
|
31 |
+
def downgrade() -> None:
|
32 |
+
# Remove check constraint
|
33 |
+
op.drop_constraint('fallback_must_be_available', 'models', type_='check')
|
34 |
+
|
35 |
+
# Remove is_fallback column
|
36 |
+
op.drop_column('models', 'is_fallback')
|
py_backend/app/crud.py
CHANGED
@@ -383,3 +383,23 @@ def get_schema(db: Session, schema_id: str):
|
|
383 |
def get_recent_images_with_validation(db: Session, limit: int = 100):
|
384 |
"""Get recent images with validation info"""
|
385 |
return db.query(models.Images).order_by(models.Images.captured_at.desc()).limit(limit).all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
def get_recent_images_with_validation(db: Session, limit: int = 100):
|
384 |
"""Get recent images with validation info"""
|
385 |
return db.query(models.Images).order_by(models.Images.captured_at.desc()).limit(limit).all()
|
386 |
+
|
387 |
+
# Fallback model CRUD operations
|
388 |
+
def get_fallback_model(db: Session) -> Optional[str]:
|
389 |
+
"""Get the configured fallback model"""
|
390 |
+
fallback_model = db.query(models.Models).filter(models.Models.is_fallback == True).first()
|
391 |
+
return fallback_model.m_code if fallback_model else None
|
392 |
+
|
393 |
+
def set_fallback_model(db: Session, model_code: str):
|
394 |
+
"""Set the fallback model - ensures only one model can be fallback"""
|
395 |
+
# First, clear any existing fallback
|
396 |
+
db.query(models.Models).filter(models.Models.is_fallback == True).update({"is_fallback": False})
|
397 |
+
|
398 |
+
# Set the new fallback model
|
399 |
+
model = db.query(models.Models).filter(models.Models.m_code == model_code).first()
|
400 |
+
if model:
|
401 |
+
model.is_fallback = True
|
402 |
+
db.commit()
|
403 |
+
db.refresh(model)
|
404 |
+
return model
|
405 |
+
return None
|
py_backend/app/main.py
CHANGED
@@ -350,6 +350,103 @@ def ensure_storage_ready():
|
|
350 |
else:
|
351 |
print(f"Unknown storage provider: {settings.STORAGE_PROVIDER}")
|
352 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 |
# Run startup tasks
|
354 |
run_migrations()
|
355 |
ensure_storage_ready()
|
|
|
350 |
else:
|
351 |
print(f"Unknown storage provider: {settings.STORAGE_PROVIDER}")
|
352 |
|
353 |
+
# --------------------------------------------------------------------
|
354 |
+
# VLM service registration on startup
|
355 |
+
# --------------------------------------------------------------------
|
356 |
+
from app.services.vlm_service import vlm_manager
|
357 |
+
|
358 |
+
# Providers
|
359 |
+
from app.services.stub_vlm_service import StubVLMService
|
360 |
+
from app.services.gpt4v_service import GPT4VService
|
361 |
+
from app.services.gemini_service import GeminiService
|
362 |
+
from app.services.huggingface_service import ProvidersGenericVLMService
|
363 |
+
|
364 |
+
from app.database import SessionLocal
|
365 |
+
from app import crud
|
366 |
+
import asyncio
|
367 |
+
|
368 |
+
|
369 |
+
@app.on_event("startup")
|
370 |
+
async def register_vlm_services() -> None:
|
371 |
+
"""Register OpenAI, Gemini, and Hugging Face models at startup (non-blocking)."""
|
372 |
+
print("Registering VLM services...")
|
373 |
+
|
374 |
+
# Always have a stub as a safe fallback
|
375 |
+
try:
|
376 |
+
vlm_manager.register_service(StubVLMService())
|
377 |
+
print("β STUB_MODEL registered")
|
378 |
+
except Exception as e:
|
379 |
+
print(f"β Failed to register STUB_MODEL: {e}")
|
380 |
+
|
381 |
+
# OpenAI GPT-4V (if configured)
|
382 |
+
if settings.OPENAI_API_KEY:
|
383 |
+
try:
|
384 |
+
vlm_manager.register_service(GPT4VService(settings.OPENAI_API_KEY))
|
385 |
+
print("β GPT-4 Vision service registered")
|
386 |
+
except Exception as e:
|
387 |
+
print(f"β GPT-4 Vision service failed to register: {e}")
|
388 |
+
else:
|
389 |
+
print("β GPT-4 Vision not configured (OPENAI_API_KEY missing)")
|
390 |
+
|
391 |
+
# Google Gemini (if configured)
|
392 |
+
if settings.GOOGLE_API_KEY:
|
393 |
+
try:
|
394 |
+
vlm_manager.register_service(GeminiService(settings.GOOGLE_API_KEY))
|
395 |
+
print("β Gemini service registered")
|
396 |
+
except Exception as e:
|
397 |
+
print(f"β Gemini service failed to register: {e}")
|
398 |
+
else:
|
399 |
+
print("β Gemini not configured (GOOGLE_API_KEY missing)")
|
400 |
+
|
401 |
+
# Hugging Face Inference Providers (if configured)
|
402 |
+
if settings.HF_API_KEY:
|
403 |
+
db = SessionLocal()
|
404 |
+
try:
|
405 |
+
models = crud.get_models(db)
|
406 |
+
registered = 0
|
407 |
+
skipped = 0
|
408 |
+
for m in models:
|
409 |
+
# Only register HF rows; skip βlogicalβ names that map to other providers
|
410 |
+
if (
|
411 |
+
getattr(m, "provider", "") == "huggingface"
|
412 |
+
and getattr(m, "model_id", None)
|
413 |
+
and m.m_code not in {"STUB_MODEL", "GPT-4O", "GEMINI15"}
|
414 |
+
):
|
415 |
+
try:
|
416 |
+
svc = ProvidersGenericVLMService(
|
417 |
+
api_key=settings.HF_API_KEY,
|
418 |
+
model_id=m.model_id,
|
419 |
+
public_name=m.m_code, # stable name your UI/DB uses
|
420 |
+
)
|
421 |
+
vlm_manager.register_service(svc)
|
422 |
+
print(f"β HF registered: {m.m_code} -> {m.model_id}")
|
423 |
+
registered += 1
|
424 |
+
except Exception as e:
|
425 |
+
print(f"β HF model {m.m_code} failed to register: {e}")
|
426 |
+
else:
|
427 |
+
skipped += 1
|
428 |
+
|
429 |
+
if registered:
|
430 |
+
print(f"β Hugging Face services registered: {registered}")
|
431 |
+
else:
|
432 |
+
print("β No Hugging Face models registered (none found or all skipped)")
|
433 |
+
if skipped:
|
434 |
+
print(f"βΉ HF skipped entries: {skipped}")
|
435 |
+
finally:
|
436 |
+
db.close()
|
437 |
+
else:
|
438 |
+
print("β Hugging Face not configured (HF_API_KEY missing)")
|
439 |
+
|
440 |
+
# Kick off lightweight probes in the background (donβt block startup)
|
441 |
+
try:
|
442 |
+
asyncio.create_task(vlm_manager.probe_all())
|
443 |
+
except Exception as e:
|
444 |
+
print(f"Probe scheduling failed: {e}")
|
445 |
+
|
446 |
+
print(f"β Available models now: {', '.join(vlm_manager.get_available_models())}")
|
447 |
+
print(f"β Total services: {len(vlm_manager.services)}")
|
448 |
+
|
449 |
+
|
450 |
# Run startup tasks
|
451 |
run_migrations()
|
452 |
ensure_storage_ready()
|
py_backend/app/models.py
CHANGED
@@ -90,10 +90,15 @@ class Models(Base):
|
|
90 |
label = Column(String, nullable=False)
|
91 |
model_type = Column(String, nullable=False)
|
92 |
is_available = Column(Boolean, default=True)
|
|
|
93 |
config = Column(JSONB, nullable=True)
|
94 |
provider = Column(String, nullable=True)
|
95 |
model_id = Column(String, nullable=True)
|
96 |
delete_count = Column(SmallInteger, nullable=False, default=0)
|
|
|
|
|
|
|
|
|
97 |
|
98 |
class JSONSchema(Base):
|
99 |
__tablename__ = "json_schemas"
|
|
|
90 |
label = Column(String, nullable=False)
|
91 |
model_type = Column(String, nullable=False)
|
92 |
is_available = Column(Boolean, default=True)
|
93 |
+
is_fallback = Column(Boolean, default=False, nullable=False)
|
94 |
config = Column(JSONB, nullable=True)
|
95 |
provider = Column(String, nullable=True)
|
96 |
model_id = Column(String, nullable=True)
|
97 |
delete_count = Column(SmallInteger, nullable=False, default=0)
|
98 |
+
|
99 |
+
__table_args__ = (
|
100 |
+
CheckConstraint('NOT (is_fallback = true AND is_available = false)', name='fallback_must_be_available'),
|
101 |
+
)
|
102 |
|
103 |
class JSONSchema(Base):
|
104 |
__tablename__ = "json_schemas"
|
py_backend/app/routers/admin.py
CHANGED
@@ -147,6 +147,7 @@ class ModelUpdateRequest(BaseModel):
|
|
147 |
provider: str | None = None
|
148 |
model_id: str | None = None
|
149 |
is_available: bool | None = None
|
|
|
150 |
|
151 |
@router.post("/models", response_model=dict)
|
152 |
async def create_model(
|
@@ -225,30 +226,38 @@ async def update_model(
|
|
225 |
detail=f"Model '{model_code}' not found"
|
226 |
)
|
227 |
|
228 |
-
#
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
update_data
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
#
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
|
253 |
return {
|
254 |
"message": "Model updated successfully",
|
@@ -266,6 +275,39 @@ async def update_model(
|
|
266 |
detail=f"Failed to update model: {str(e)}"
|
267 |
)
|
268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
@router.delete("/models/{model_code}", response_model=dict)
|
270 |
async def delete_model(
|
271 |
model_code: str,
|
|
|
147 |
provider: str | None = None
|
148 |
model_id: str | None = None
|
149 |
is_available: bool | None = None
|
150 |
+
is_fallback: bool | None = None
|
151 |
|
152 |
@router.post("/models", response_model=dict)
|
153 |
async def create_model(
|
|
|
226 |
detail=f"Model '{model_code}' not found"
|
227 |
)
|
228 |
|
229 |
+
# Handle fallback model setting specially
|
230 |
+
if request.is_fallback is not None and request.is_fallback:
|
231 |
+
# Set this model as fallback (will clear others)
|
232 |
+
crud.set_fallback_model(db, model_code)
|
233 |
+
updated_model = crud.get_model(db, model_code)
|
234 |
+
else:
|
235 |
+
# Update model fields normally
|
236 |
+
update_data = {}
|
237 |
+
if request.label is not None:
|
238 |
+
update_data["label"] = request.label
|
239 |
+
if request.model_type is not None:
|
240 |
+
update_data["model_type"] = request.model_type
|
241 |
+
if request.is_available is not None:
|
242 |
+
update_data["is_available"] = request.is_available
|
243 |
+
if request.is_fallback is not None and not request.is_fallback:
|
244 |
+
update_data["is_fallback"] = False
|
245 |
+
|
246 |
+
# Update config column for provider and model_id
|
247 |
+
config_updates = {}
|
248 |
+
if request.provider is not None:
|
249 |
+
config_updates["provider"] = request.provider
|
250 |
+
if request.model_id is not None:
|
251 |
+
config_updates["model_id"] = request.model_id
|
252 |
+
|
253 |
+
if config_updates:
|
254 |
+
# Get current config or create empty dict
|
255 |
+
current_config = existing_model.config or {}
|
256 |
+
# Merge with updates
|
257 |
+
updated_config = {**current_config, **config_updates}
|
258 |
+
update_data["config"] = updated_config
|
259 |
+
|
260 |
+
updated_model = crud.update_model(db, model_code, update_data)
|
261 |
|
262 |
return {
|
263 |
"message": "Model updated successfully",
|
|
|
275 |
detail=f"Failed to update model: {str(e)}"
|
276 |
)
|
277 |
|
278 |
+
@router.get("/fallback-model", response_model=dict)
|
279 |
+
async def get_fallback_model(
|
280 |
+
credentials: HTTPAuthorizationCredentials = Depends(security),
|
281 |
+
db: Session = Depends(get_db)
|
282 |
+
):
|
283 |
+
"""Get the current fallback model"""
|
284 |
+
token = credentials.credentials
|
285 |
+
|
286 |
+
if not verify_admin_token(token):
|
287 |
+
raise HTTPException(
|
288 |
+
status_code=401,
|
289 |
+
detail="Invalid or expired token"
|
290 |
+
)
|
291 |
+
|
292 |
+
try:
|
293 |
+
fallback_model_code = crud.get_fallback_model(db)
|
294 |
+
if fallback_model_code:
|
295 |
+
fallback_model = crud.get_model(db, fallback_model_code)
|
296 |
+
return {
|
297 |
+
"fallback_model": {
|
298 |
+
"m_code": fallback_model.m_code,
|
299 |
+
"label": fallback_model.label,
|
300 |
+
"is_available": fallback_model.is_available
|
301 |
+
}
|
302 |
+
}
|
303 |
+
else:
|
304 |
+
return {"fallback_model": None}
|
305 |
+
except Exception as e:
|
306 |
+
raise HTTPException(
|
307 |
+
status_code=500,
|
308 |
+
detail=f"Failed to get fallback model: {str(e)}"
|
309 |
+
)
|
310 |
+
|
311 |
@router.delete("/models/{model_code}", response_model=dict)
|
312 |
async def delete_model(
|
313 |
model_code: str,
|
py_backend/app/routers/caption.py
CHANGED
@@ -1,90 +1,13 @@
|
|
|
|
1 |
from fastapi import APIRouter, HTTPException, Depends, Form, Request
|
2 |
from sqlalchemy.orm import Session
|
3 |
from typing import List
|
|
|
4 |
from .. import crud, database, schemas, storage
|
5 |
from ..services.vlm_service import vlm_manager
|
6 |
from ..services.schema_validator import schema_validator
|
7 |
from ..config import settings
|
8 |
|
9 |
-
from ..services.stub_vlm_service import StubVLMService
|
10 |
-
from ..services.gpt4v_service import GPT4VService
|
11 |
-
from ..services.gemini_service import GeminiService
|
12 |
-
from ..services.huggingface_service import ProvidersGenericVLMService
|
13 |
-
|
14 |
-
stub_service = StubVLMService()
|
15 |
-
vlm_manager.register_service(stub_service)
|
16 |
-
|
17 |
-
if settings.OPENAI_API_KEY:
|
18 |
-
try:
|
19 |
-
gpt4v_service = GPT4VService(settings.OPENAI_API_KEY)
|
20 |
-
vlm_manager.register_service(gpt4v_service)
|
21 |
-
print(f"β GPT-4 Vision service registered")
|
22 |
-
except Exception as e:
|
23 |
-
print(f"β GPT-4 Vision service failed: {e}")
|
24 |
-
else:
|
25 |
-
print("β GPT-4 Vision service not configured")
|
26 |
-
|
27 |
-
if settings.GOOGLE_API_KEY:
|
28 |
-
try:
|
29 |
-
gemini_service = GeminiService(settings.GOOGLE_API_KEY)
|
30 |
-
vlm_manager.register_service(gemini_service)
|
31 |
-
print(f"β Gemini service registered")
|
32 |
-
except Exception as e:
|
33 |
-
print(f"β Gemini service failed: {e}")
|
34 |
-
else:
|
35 |
-
print("β Gemini service not configured")
|
36 |
-
|
37 |
-
if settings.HF_API_KEY:
|
38 |
-
try:
|
39 |
-
# Dynamically register models from database
|
40 |
-
from .. import crud
|
41 |
-
from ..database import SessionLocal
|
42 |
-
|
43 |
-
db = SessionLocal()
|
44 |
-
try:
|
45 |
-
models = crud.get_models(db)
|
46 |
-
registered_count = 0
|
47 |
-
failed_count = 0
|
48 |
-
|
49 |
-
for model in models:
|
50 |
-
if (model.provider == "huggingface" and
|
51 |
-
model.model_id and
|
52 |
-
model.m_code != "STUB_MODEL" and
|
53 |
-
model.m_code not in ["GPT-4O", "GEMINI15"]):
|
54 |
-
try:
|
55 |
-
service = ProvidersGenericVLMService(
|
56 |
-
api_key=settings.HF_API_KEY,
|
57 |
-
model_id=model.model_id,
|
58 |
-
public_name=model.m_code
|
59 |
-
)
|
60 |
-
vlm_manager.register_service(service)
|
61 |
-
print(f"β Registered HF model: {model.m_code} -> {model.model_id}")
|
62 |
-
registered_count += 1
|
63 |
-
except ValueError as e:
|
64 |
-
print(f"β Failed to register {model.m_code}: Configuration error - {e}")
|
65 |
-
failed_count += 1
|
66 |
-
except Exception as e:
|
67 |
-
print(f"β Failed to register {model.m_code}: {e}")
|
68 |
-
failed_count += 1
|
69 |
-
|
70 |
-
if registered_count > 0:
|
71 |
-
print(f"β Hugging Face services registered: {registered_count} models successfully")
|
72 |
-
if failed_count > 0:
|
73 |
-
print(f"β οΈ Hugging Face services: {failed_count} models failed to register")
|
74 |
-
if registered_count == 0 and failed_count == 0:
|
75 |
-
print("β No Hugging Face models found in database")
|
76 |
-
finally:
|
77 |
-
db.close()
|
78 |
-
except Exception as e:
|
79 |
-
print(f"β Hugging Face services failed: {e}")
|
80 |
-
import traceback
|
81 |
-
traceback.print_exc()
|
82 |
-
else:
|
83 |
-
print("β Hugging Face services not configured (HF_API_KEY not set)")
|
84 |
-
|
85 |
-
print(f"β Available models: {', '.join(vlm_manager.get_available_models())}")
|
86 |
-
print(f"β Total services: {len(vlm_manager.services)}")
|
87 |
-
|
88 |
router = APIRouter()
|
89 |
|
90 |
def get_db():
|
@@ -101,7 +24,7 @@ def get_db():
|
|
101 |
async def create_caption(
|
102 |
image_id: str,
|
103 |
title: str = Form(...),
|
104 |
-
prompt: str = Form(None), #
|
105 |
model_name: str | None = Form(None),
|
106 |
db: Session = Depends(get_db),
|
107 |
):
|
@@ -111,32 +34,24 @@ async def create_caption(
|
|
111 |
if not img:
|
112 |
raise HTTPException(404, "image not found")
|
113 |
|
114 |
-
# Get the active
|
115 |
if prompt:
|
116 |
-
# If prompt is provided, use it (for backward compatibility)
|
117 |
print(f"Looking for prompt: '{prompt}' (type: {type(prompt)})")
|
118 |
-
prompt_obj = crud.get_prompt(db, prompt)
|
119 |
-
|
120 |
-
if not prompt_obj:
|
121 |
-
print(f"Prompt not found by code, trying to find by label...")
|
122 |
-
prompt_obj = crud.get_prompt_by_label(db, prompt)
|
123 |
else:
|
124 |
-
# Use the active prompt for the image type
|
125 |
print(f"Looking for active prompt for image type: {img.image_type}")
|
126 |
prompt_obj = crud.get_active_prompt_by_image_type(db, img.image_type)
|
127 |
-
|
128 |
-
if not prompt_obj:
|
129 |
-
raise HTTPException(400, f"No active prompt found for image type '{img.image_type}'")
|
130 |
-
|
131 |
print(f"Prompt lookup result: {prompt_obj}")
|
132 |
if not prompt_obj:
|
133 |
-
raise HTTPException(400, f"
|
134 |
-
|
135 |
prompt_text = prompt_obj.label
|
136 |
metadata_instructions = prompt_obj.metadata_instructions or ""
|
137 |
print(f"Using prompt text: '{prompt_text}'")
|
138 |
print(f"Using metadata instructions: '{metadata_instructions[:100]}...'")
|
139 |
|
|
|
140 |
try:
|
141 |
print(f"DEBUG: About to call VLM service with model_name: {model_name}")
|
142 |
if hasattr(storage, 's3') and settings.STORAGE_PROVIDER != "local":
|
@@ -152,6 +67,7 @@ async def create_caption(
|
|
152 |
img_bytes = f.read()
|
153 |
except Exception as e:
|
154 |
print(f"Error reading image file: {e}")
|
|
|
155 |
try:
|
156 |
url = storage.get_object_url(img.file_key)
|
157 |
if url.startswith('/') and settings.STORAGE_PROVIDER == "local":
|
@@ -177,7 +93,6 @@ async def create_caption(
|
|
177 |
print(f"DEBUG: VLM service result: {result}")
|
178 |
print(f"DEBUG: Result model field: {result.get('model', 'NOT_FOUND')}")
|
179 |
|
180 |
-
# Get the raw response for validation
|
181 |
raw = result.get("raw_response", {})
|
182 |
|
183 |
# Validate and clean the data using schema validation
|
@@ -193,34 +108,22 @@ async def create_caption(
|
|
193 |
metadata = cleaned_data.get("metadata", {})
|
194 |
else:
|
195 |
print(f"β Schema validation failed for {image_type}: {validation_error}")
|
196 |
-
# Use fallback but log the validation error
|
197 |
text = result.get("caption", "This is a fallback caption due to schema validation error.")
|
198 |
metadata = result.get("metadata", {})
|
199 |
raw["validation_error"] = validation_error
|
200 |
raw["validation_failed"] = True
|
201 |
|
202 |
-
# Use the actual model that was used, not the requested model_name
|
203 |
used_model = result.get("model", model_name) or "STUB_MODEL"
|
204 |
-
|
205 |
-
# Ensure we never use 'random' as the model name in the database
|
206 |
if used_model == "random":
|
207 |
print(f"WARNING: VLM service returned 'random' as model name, using STUB_MODEL fallback")
|
208 |
used_model = "STUB_MODEL"
|
209 |
|
210 |
-
|
211 |
-
|
212 |
-
# Check if fallback was used
|
213 |
-
fallback_used = result.get("fallback_used", False)
|
214 |
-
original_model = result.get("original_model", None)
|
215 |
-
fallback_reason = result.get("fallback_reason", None)
|
216 |
-
|
217 |
-
if fallback_used:
|
218 |
-
print(f"β Model fallback occurred: {original_model} -> {used_model} (reason: {fallback_reason})")
|
219 |
-
# Add fallback info to raw response for frontend
|
220 |
raw["fallback_info"] = {
|
221 |
-
"original_model": original_model,
|
222 |
"fallback_model": used_model,
|
223 |
-
"reason": fallback_reason
|
224 |
}
|
225 |
|
226 |
except Exception as e:
|
@@ -242,10 +145,8 @@ async def create_caption(
|
|
242 |
)
|
243 |
|
244 |
db.refresh(caption)
|
245 |
-
|
246 |
print(f"DEBUG: Caption created, caption object: {caption}")
|
247 |
print(f"DEBUG: caption_id: {caption.caption_id}")
|
248 |
-
|
249 |
return schemas.CaptionOut.from_orm(caption)
|
250 |
|
251 |
@router.get(
|
@@ -264,21 +165,15 @@ def get_all_captions_legacy_format(
|
|
264 |
result = []
|
265 |
for caption in captions:
|
266 |
db.refresh(caption)
|
267 |
-
|
268 |
-
# Get the associated image for this caption
|
269 |
if caption.images:
|
270 |
for image in caption.images:
|
271 |
from .upload import convert_image_to_dict
|
272 |
-
|
273 |
-
# Build absolute URL using request context
|
274 |
base_url = str(request.base_url).rstrip('/')
|
275 |
url = f"{base_url}/api/images/{image.image_id}/file"
|
276 |
-
|
277 |
print(f"DEBUG: Generated image URL: {url}")
|
278 |
-
|
279 |
img_dict = convert_image_to_dict(image, url)
|
280 |
-
|
281 |
-
#
|
282 |
img_dict.update({
|
283 |
"title": caption.title,
|
284 |
"prompt": caption.prompt,
|
@@ -294,9 +189,7 @@ def get_all_captions_legacy_format(
|
|
294 |
"created_at": caption.created_at,
|
295 |
"updated_at": caption.updated_at,
|
296 |
})
|
297 |
-
|
298 |
result.append(schemas.ImageOut(**img_dict))
|
299 |
-
|
300 |
print(f"DEBUG: Returning {len(result)} legacy format results")
|
301 |
return result
|
302 |
|
@@ -315,10 +208,8 @@ def get_all_captions_with_images(
|
|
315 |
result = []
|
316 |
for caption in captions:
|
317 |
print(f"DEBUG: Processing caption {caption.caption_id}, title: {caption.title}, generated: {caption.generated}, model: {caption.model}")
|
318 |
-
|
319 |
db.refresh(caption)
|
320 |
result.append(schemas.CaptionOut.from_orm(caption))
|
321 |
-
|
322 |
print(f"DEBUG: Returning {len(result)} formatted results")
|
323 |
return result
|
324 |
|
@@ -332,12 +223,10 @@ def get_captions_by_image(
|
|
332 |
):
|
333 |
"""Get all captions for a specific image"""
|
334 |
captions = crud.get_captions_by_image(db, image_id)
|
335 |
-
|
336 |
result = []
|
337 |
for caption in captions:
|
338 |
db.refresh(caption)
|
339 |
result.append(schemas.CaptionOut.from_orm(caption))
|
340 |
-
|
341 |
return result
|
342 |
|
343 |
@router.get(
|
@@ -351,7 +240,6 @@ def get_caption(
|
|
351 |
caption = crud.get_caption(db, caption_id)
|
352 |
if not caption:
|
353 |
raise HTTPException(404, "caption not found")
|
354 |
-
|
355 |
db.refresh(caption)
|
356 |
return schemas.CaptionOut.from_orm(caption)
|
357 |
|
@@ -367,7 +255,6 @@ def update_caption(
|
|
367 |
caption = crud.update_caption(db, caption_id, update)
|
368 |
if not caption:
|
369 |
raise HTTPException(404, "caption not found")
|
370 |
-
|
371 |
db.refresh(caption)
|
372 |
return schemas.CaptionOut.from_orm(caption)
|
373 |
|
@@ -384,15 +271,12 @@ def update_caption_by_image(
|
|
384 |
img = crud.get_image(db, image_id)
|
385 |
if not img:
|
386 |
raise HTTPException(404, "image not found")
|
387 |
-
|
388 |
if not img.captions:
|
389 |
raise HTTPException(404, "no captions found for this image")
|
390 |
-
|
391 |
-
# Update the first caption
|
392 |
caption = crud.update_caption(db, str(img.captions[0].caption_id), update)
|
393 |
if not caption:
|
394 |
raise HTTPException(404, "caption not found")
|
395 |
-
|
396 |
db.refresh(caption)
|
397 |
return schemas.CaptionOut.from_orm(caption)
|
398 |
|
|
|
1 |
+
# py_backend/app/routers/caption.py
|
2 |
from fastapi import APIRouter, HTTPException, Depends, Form, Request
|
3 |
from sqlalchemy.orm import Session
|
4 |
from typing import List
|
5 |
+
|
6 |
from .. import crud, database, schemas, storage
|
7 |
from ..services.vlm_service import vlm_manager
|
8 |
from ..services.schema_validator import schema_validator
|
9 |
from ..config import settings
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
router = APIRouter()
|
12 |
|
13 |
def get_db():
|
|
|
24 |
async def create_caption(
|
25 |
image_id: str,
|
26 |
title: str = Form(...),
|
27 |
+
prompt: str = Form(None), # optional; will use active prompts if not provided
|
28 |
model_name: str | None = Form(None),
|
29 |
db: Session = Depends(get_db),
|
30 |
):
|
|
|
34 |
if not img:
|
35 |
raise HTTPException(404, "image not found")
|
36 |
|
37 |
+
# Get the prompt (explicit by code/label, or active for image type)
|
38 |
if prompt:
|
|
|
39 |
print(f"Looking for prompt: '{prompt}' (type: {type(prompt)})")
|
40 |
+
prompt_obj = crud.get_prompt(db, prompt) or crud.get_prompt_by_label(db, prompt)
|
|
|
|
|
|
|
|
|
41 |
else:
|
|
|
42 |
print(f"Looking for active prompt for image type: {img.image_type}")
|
43 |
prompt_obj = crud.get_active_prompt_by_image_type(db, img.image_type)
|
44 |
+
|
|
|
|
|
|
|
45 |
print(f"Prompt lookup result: {prompt_obj}")
|
46 |
if not prompt_obj:
|
47 |
+
raise HTTPException(400, f"No prompt found (requested: '{prompt}' or active for type '{img.image_type}')")
|
48 |
+
|
49 |
prompt_text = prompt_obj.label
|
50 |
metadata_instructions = prompt_obj.metadata_instructions or ""
|
51 |
print(f"Using prompt text: '{prompt_text}'")
|
52 |
print(f"Using metadata instructions: '{metadata_instructions[:100]}...'")
|
53 |
|
54 |
+
# Load image bytes (S3 or local)
|
55 |
try:
|
56 |
print(f"DEBUG: About to call VLM service with model_name: {model_name}")
|
57 |
if hasattr(storage, 's3') and settings.STORAGE_PROVIDER != "local":
|
|
|
67 |
img_bytes = f.read()
|
68 |
except Exception as e:
|
69 |
print(f"Error reading image file: {e}")
|
70 |
+
# fallback: try presigned/public URL
|
71 |
try:
|
72 |
url = storage.get_object_url(img.file_key)
|
73 |
if url.startswith('/') and settings.STORAGE_PROVIDER == "local":
|
|
|
93 |
print(f"DEBUG: VLM service result: {result}")
|
94 |
print(f"DEBUG: Result model field: {result.get('model', 'NOT_FOUND')}")
|
95 |
|
|
|
96 |
raw = result.get("raw_response", {})
|
97 |
|
98 |
# Validate and clean the data using schema validation
|
|
|
108 |
metadata = cleaned_data.get("metadata", {})
|
109 |
else:
|
110 |
print(f"β Schema validation failed for {image_type}: {validation_error}")
|
|
|
111 |
text = result.get("caption", "This is a fallback caption due to schema validation error.")
|
112 |
metadata = result.get("metadata", {})
|
113 |
raw["validation_error"] = validation_error
|
114 |
raw["validation_failed"] = True
|
115 |
|
|
|
116 |
used_model = result.get("model", model_name) or "STUB_MODEL"
|
|
|
|
|
117 |
if used_model == "random":
|
118 |
print(f"WARNING: VLM service returned 'random' as model name, using STUB_MODEL fallback")
|
119 |
used_model = "STUB_MODEL"
|
120 |
|
121 |
+
# Fallback info (if any)
|
122 |
+
if result.get("fallback_used"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
raw["fallback_info"] = {
|
124 |
+
"original_model": result.get("original_model"),
|
125 |
"fallback_model": used_model,
|
126 |
+
"reason": result.get("fallback_reason"),
|
127 |
}
|
128 |
|
129 |
except Exception as e:
|
|
|
145 |
)
|
146 |
|
147 |
db.refresh(caption)
|
|
|
148 |
print(f"DEBUG: Caption created, caption object: {caption}")
|
149 |
print(f"DEBUG: caption_id: {caption.caption_id}")
|
|
|
150 |
return schemas.CaptionOut.from_orm(caption)
|
151 |
|
152 |
@router.get(
|
|
|
165 |
result = []
|
166 |
for caption in captions:
|
167 |
db.refresh(caption)
|
|
|
|
|
168 |
if caption.images:
|
169 |
for image in caption.images:
|
170 |
from .upload import convert_image_to_dict
|
|
|
|
|
171 |
base_url = str(request.base_url).rstrip('/')
|
172 |
url = f"{base_url}/api/images/{image.image_id}/file"
|
|
|
173 |
print(f"DEBUG: Generated image URL: {url}")
|
|
|
174 |
img_dict = convert_image_to_dict(image, url)
|
175 |
+
|
176 |
+
# Overlay caption fields (legacy shape)
|
177 |
img_dict.update({
|
178 |
"title": caption.title,
|
179 |
"prompt": caption.prompt,
|
|
|
189 |
"created_at": caption.created_at,
|
190 |
"updated_at": caption.updated_at,
|
191 |
})
|
|
|
192 |
result.append(schemas.ImageOut(**img_dict))
|
|
|
193 |
print(f"DEBUG: Returning {len(result)} legacy format results")
|
194 |
return result
|
195 |
|
|
|
208 |
result = []
|
209 |
for caption in captions:
|
210 |
print(f"DEBUG: Processing caption {caption.caption_id}, title: {caption.title}, generated: {caption.generated}, model: {caption.model}")
|
|
|
211 |
db.refresh(caption)
|
212 |
result.append(schemas.CaptionOut.from_orm(caption))
|
|
|
213 |
print(f"DEBUG: Returning {len(result)} formatted results")
|
214 |
return result
|
215 |
|
|
|
223 |
):
|
224 |
"""Get all captions for a specific image"""
|
225 |
captions = crud.get_captions_by_image(db, image_id)
|
|
|
226 |
result = []
|
227 |
for caption in captions:
|
228 |
db.refresh(caption)
|
229 |
result.append(schemas.CaptionOut.from_orm(caption))
|
|
|
230 |
return result
|
231 |
|
232 |
@router.get(
|
|
|
240 |
caption = crud.get_caption(db, caption_id)
|
241 |
if not caption:
|
242 |
raise HTTPException(404, "caption not found")
|
|
|
243 |
db.refresh(caption)
|
244 |
return schemas.CaptionOut.from_orm(caption)
|
245 |
|
|
|
255 |
caption = crud.update_caption(db, caption_id, update)
|
256 |
if not caption:
|
257 |
raise HTTPException(404, "caption not found")
|
|
|
258 |
db.refresh(caption)
|
259 |
return schemas.CaptionOut.from_orm(caption)
|
260 |
|
|
|
271 |
img = crud.get_image(db, image_id)
|
272 |
if not img:
|
273 |
raise HTTPException(404, "image not found")
|
|
|
274 |
if not img.captions:
|
275 |
raise HTTPException(404, "no captions found for this image")
|
276 |
+
|
|
|
277 |
caption = crud.update_caption(db, str(img.captions[0].caption_id), update)
|
278 |
if not caption:
|
279 |
raise HTTPException(404, "caption not found")
|
|
|
280 |
db.refresh(caption)
|
281 |
return schemas.CaptionOut.from_orm(caption)
|
282 |
|
py_backend/app/routers/models.py
CHANGED
@@ -26,6 +26,7 @@ def get_available_models(db: Session = Depends(get_db)):
|
|
26 |
"label": model.label,
|
27 |
"model_type": model.model_type,
|
28 |
"is_available": model.is_available,
|
|
|
29 |
"config": model.config,
|
30 |
"delete_count": model.delete_count
|
31 |
})
|
|
|
26 |
"label": model.label,
|
27 |
"model_type": model.model_type,
|
28 |
"is_available": model.is_available,
|
29 |
+
"is_fallback": model.is_fallback,
|
30 |
"config": model.config,
|
31 |
"delete_count": model.delete_count
|
32 |
})
|
py_backend/app/services/huggingface_service.py
CHANGED
@@ -1,26 +1,57 @@
|
|
1 |
# services/huggingface_service.py
|
2 |
-
from
|
3 |
-
|
|
|
|
|
|
|
|
|
4 |
import aiohttp
|
5 |
import base64
|
6 |
import time
|
7 |
import re
|
8 |
import json
|
9 |
import imghdr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
|
12 |
class HuggingFaceService(VLMService):
|
13 |
"""
|
14 |
-
HuggingFace Inference Providers service implementation.
|
15 |
-
|
|
|
|
|
16 |
"""
|
17 |
-
|
18 |
-
def __init__(self, api_key: str, model_id: str, providers_url: str):
|
19 |
-
super().__init__(
|
|
|
|
|
|
|
|
|
|
|
20 |
self.api_key = api_key
|
21 |
self.model_id = model_id
|
22 |
self.providers_url = providers_url
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
def _guess_mime(self, image_bytes: bytes) -> str:
|
26 |
kind = imghdr.what(None, h=image_bytes)
|
@@ -34,6 +65,44 @@ class HuggingFaceService(VLMService):
|
|
34 |
return "image/webp"
|
35 |
return "image/jpeg"
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
async def generate_caption(
|
38 |
self,
|
39 |
image_bytes: bytes,
|
@@ -41,8 +110,11 @@ class HuggingFaceService(VLMService):
|
|
41 |
metadata_instructions: str = "",
|
42 |
) -> Dict[str, Any]:
|
43 |
"""
|
44 |
-
Generate caption using HF Inference Providers (OpenAI-style).
|
45 |
"""
|
|
|
|
|
|
|
46 |
start_time = time.time()
|
47 |
|
48 |
instruction = (prompt or "").strip()
|
@@ -57,7 +129,6 @@ class HuggingFaceService(VLMService):
|
|
57 |
"Content-Type": "application/json",
|
58 |
}
|
59 |
|
60 |
-
# OpenAI-compatible chat payload with one text + one image block.
|
61 |
payload = {
|
62 |
"model": self.model_id,
|
63 |
"messages": [
|
@@ -74,34 +145,32 @@ class HuggingFaceService(VLMService):
|
|
74 |
}
|
75 |
|
76 |
try:
|
77 |
-
async with aiohttp.ClientSession() as session:
|
78 |
async with session.post(
|
79 |
self.providers_url,
|
80 |
headers=headers,
|
81 |
json=payload,
|
82 |
-
timeout=aiohttp.ClientTimeout(total=180),
|
83 |
) as resp:
|
84 |
raw_text = await resp.text()
|
85 |
if resp.status != 200:
|
86 |
-
#
|
87 |
-
raise Exception(f"MODEL_UNAVAILABLE: {self.model_name}
|
88 |
result = await resp.json()
|
89 |
except Exception as e:
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
raise
|
94 |
|
95 |
-
#
|
96 |
message = (result.get("choices") or [{}])[0].get("message", {})
|
97 |
content = message.get("content", "")
|
98 |
-
|
99 |
-
# GLM models sometimes put content in reasoning_content
|
100 |
if not content and message.get("reasoning_content"):
|
101 |
content = message.get("reasoning_content", "")
|
102 |
|
103 |
if isinstance(content, list):
|
104 |
-
# Some providers may return a list of output blocks (e.g., {"type":"output_text","text":...})
|
105 |
parts = []
|
106 |
for block in content:
|
107 |
if isinstance(block, dict):
|
@@ -110,31 +179,28 @@ class HuggingFaceService(VLMService):
|
|
110 |
parts.append(str(block))
|
111 |
content = "\n".join([p for p in parts if p])
|
112 |
|
113 |
-
caption = content or ""
|
114 |
-
cleaned = caption.strip()
|
115 |
|
116 |
# Strip accidental fenced JSON
|
117 |
-
if
|
118 |
-
|
119 |
-
|
120 |
|
121 |
-
# Best-effort JSON protocol
|
122 |
metadata = {}
|
123 |
description = ""
|
124 |
-
analysis =
|
125 |
recommended_actions = ""
|
126 |
-
|
127 |
try:
|
128 |
-
parsed = json.loads(
|
129 |
description = parsed.get("description", "")
|
130 |
-
analysis = parsed.get("analysis",
|
131 |
recommended_actions = parsed.get("recommended_actions", "")
|
132 |
metadata = parsed.get("metadata", {})
|
133 |
-
|
134 |
-
# Combine all three parts for backward compatibility
|
135 |
caption_text = f"Description: {description}\n\nAnalysis: {analysis}\n\nRecommended Actions: {recommended_actions}"
|
136 |
except json.JSONDecodeError:
|
137 |
-
|
|
|
138 |
|
139 |
elapsed = time.time() - start_time
|
140 |
|
@@ -146,11 +212,11 @@ class HuggingFaceService(VLMService):
|
|
146 |
"raw_response": {
|
147 |
"model": self.model_id,
|
148 |
"content": content,
|
149 |
-
"parsed": parsed
|
150 |
},
|
151 |
"description": description,
|
152 |
"analysis": analysis,
|
153 |
-
"recommended_actions": recommended_actions
|
154 |
}
|
155 |
|
156 |
async def generate_multi_image_caption(
|
@@ -160,8 +226,11 @@ class HuggingFaceService(VLMService):
|
|
160 |
metadata_instructions: str = "",
|
161 |
) -> Dict[str, Any]:
|
162 |
"""
|
163 |
-
Generate caption for multiple images using HF Inference Providers (OpenAI-style).
|
164 |
"""
|
|
|
|
|
|
|
165 |
start_time = time.time()
|
166 |
|
167 |
instruction = (prompt or "").strip()
|
@@ -173,90 +242,71 @@ class HuggingFaceService(VLMService):
|
|
173 |
"Content-Type": "application/json",
|
174 |
}
|
175 |
|
176 |
-
# Create content array with text and multiple images
|
177 |
content = [{"type": "text", "text": instruction}]
|
178 |
-
|
179 |
-
# Add each image to the content
|
180 |
for image_bytes in image_bytes_list:
|
181 |
mime = self._guess_mime(image_bytes)
|
182 |
data_url = f"data:{mime};base64,{base64.b64encode(image_bytes).decode('utf-8')}"
|
183 |
content.append({"type": "image_url", "image_url": {"url": data_url}})
|
184 |
|
185 |
-
# OpenAI-compatible chat payload with one text + multiple image blocks.
|
186 |
payload = {
|
187 |
"model": self.model_id,
|
188 |
-
"messages": [
|
189 |
-
|
190 |
-
"role": "user",
|
191 |
-
"content": content,
|
192 |
-
}
|
193 |
-
],
|
194 |
-
"max_tokens": 800, # Increased for multiple images
|
195 |
"temperature": 0.2,
|
196 |
}
|
197 |
|
198 |
try:
|
199 |
-
async with aiohttp.ClientSession() as session:
|
200 |
async with session.post(
|
201 |
self.providers_url,
|
202 |
headers=headers,
|
203 |
json=payload,
|
204 |
-
timeout=aiohttp.ClientTimeout(total=180),
|
205 |
) as resp:
|
206 |
raw_text = await resp.text()
|
207 |
if resp.status != 200:
|
208 |
-
|
209 |
-
raise Exception(f"MODEL_UNAVAILABLE: {self.model_name} is currently unavailable (HTTP {resp.status}). Switching to another model.")
|
210 |
result = await resp.json()
|
211 |
except Exception as e:
|
212 |
-
if "MODEL_UNAVAILABLE" in str(e):
|
213 |
-
raise
|
214 |
-
|
215 |
-
raise Exception(f"MODEL_UNAVAILABLE: {self.model_name} is currently unavailable due to an error. Switching to another model.")
|
216 |
|
217 |
-
# Extract model output (string or list-of-blocks)
|
218 |
message = (result.get("choices") or [{}])[0].get("message", {})
|
219 |
-
|
220 |
-
|
221 |
-
# GLM models sometimes put content in reasoning_content field
|
222 |
-
if not content and message.get("reasoning_content"):
|
223 |
-
content = message.get("reasoning_content", "")
|
224 |
|
225 |
-
if
|
226 |
-
|
|
|
|
|
227 |
parts = []
|
228 |
-
for block in
|
229 |
if isinstance(block, dict):
|
230 |
parts.append(block.get("text") or block.get("content") or "")
|
231 |
else:
|
232 |
parts.append(str(block))
|
233 |
-
|
234 |
|
235 |
-
caption =
|
236 |
-
cleaned = caption.strip()
|
237 |
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
cleaned = re.sub(r"\s*```$", "", cleaned)
|
242 |
|
243 |
-
# Best-effort JSON protocol
|
244 |
metadata = {}
|
245 |
description = ""
|
246 |
-
analysis =
|
247 |
recommended_actions = ""
|
248 |
-
|
249 |
try:
|
250 |
-
parsed = json.loads(
|
251 |
description = parsed.get("description", "")
|
252 |
-
analysis = parsed.get("analysis",
|
253 |
recommended_actions = parsed.get("recommended_actions", "")
|
254 |
metadata = parsed.get("metadata", {})
|
255 |
-
|
256 |
-
# Combine all three parts for backward compatibility
|
257 |
caption_text = f"Description: {description}\n\nAnalysis: {analysis}\n\nRecommended Actions: {recommended_actions}"
|
258 |
except json.JSONDecodeError:
|
259 |
-
|
|
|
260 |
|
261 |
elapsed = time.time() - start_time
|
262 |
|
@@ -267,34 +317,31 @@ class HuggingFaceService(VLMService):
|
|
267 |
"processing_time": elapsed,
|
268 |
"raw_response": {
|
269 |
"model": self.model_id,
|
270 |
-
"content":
|
271 |
-
"parsed": parsed
|
272 |
-
"image_count": len(image_bytes_list)
|
273 |
},
|
274 |
"description": description,
|
275 |
"analysis": analysis,
|
276 |
-
"recommended_actions": recommended_actions
|
277 |
}
|
278 |
|
279 |
|
280 |
-
# --- Generic
|
281 |
-
|
282 |
class ProvidersGenericVLMService(HuggingFaceService):
|
283 |
"""
|
284 |
-
Generic wrapper so you can register ANY Providers VLM by model_id from config.
|
285 |
Example:
|
286 |
-
ProvidersGenericVLMService(
|
287 |
"""
|
288 |
def __init__(self, api_key: str, model_id: str, public_name: str | None = None):
|
289 |
-
if not api_key:
|
290 |
-
raise ValueError("HF_API_KEY is required for Hugging Face models")
|
291 |
-
if not model_id:
|
292 |
-
raise ValueError("model_id is required for Hugging Face models")
|
293 |
-
|
294 |
-
# Use the default HuggingFace providers URL
|
295 |
providers_url = "https://api-inference.huggingface.co/providers/openai"
|
296 |
-
super().__init__(
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
|
|
|
|
|
|
|
|
|
1 |
# services/huggingface_service.py
|
2 |
+
from __future__ import annotations
|
3 |
+
|
4 |
+
|
5 |
+
from .vlm_service import VLMService, ModelType, ServiceStatus
|
6 |
+
|
7 |
+
from typing import Dict, Any, List, Optional
|
8 |
import aiohttp
|
9 |
import base64
|
10 |
import time
|
11 |
import re
|
12 |
import json
|
13 |
import imghdr
|
14 |
+
import os
|
15 |
+
|
16 |
+
|
17 |
+
def _env_token() -> Optional[str]:
|
18 |
+
return (
|
19 |
+
os.getenv("HF_API_KEY")
|
20 |
+
or os.getenv("HF_TOKEN")
|
21 |
+
or os.getenv("HUGGINGFACEHUB_API_TOKEN")
|
22 |
+
)
|
23 |
+
|
24 |
+
|
25 |
+
def _providers_url_default() -> str:
|
26 |
+
# OpenAI-compatible gateway on HF Inference Providers
|
27 |
+
return os.getenv("HF_PROVIDERS_URL", "https://api-inference.huggingface.co/providers/openai")
|
28 |
|
29 |
|
30 |
class HuggingFaceService(VLMService):
|
31 |
"""
|
32 |
+
HuggingFace Inference Providers service implementation (OpenAI-compatible).
|
33 |
+
- No network in __init__
|
34 |
+
- Short, safe probe()
|
35 |
+
- Lazy use during generate_*
|
36 |
"""
|
37 |
+
|
38 |
+
def __init__(self, api_key: str, model_id: str, providers_url: str, public_name: str | None = None):
|
39 |
+
super().__init__(
|
40 |
+
public_name or (model_id or "HUGGINGFACE"),
|
41 |
+
ModelType.CUSTOM,
|
42 |
+
provider="huggingface",
|
43 |
+
lazy_init=True,
|
44 |
+
)
|
45 |
self.api_key = api_key
|
46 |
self.model_id = model_id
|
47 |
self.providers_url = providers_url
|
48 |
+
# also keep model_name aligned
|
49 |
+
self.model_name = public_name or (model_id or "HUGGINGFACE")
|
50 |
+
if not self.api_key or not self.model_id:
|
51 |
+
self.is_available = False
|
52 |
+
self.status = ServiceStatus.DEGRADED
|
53 |
+
|
54 |
+
# ---------- helpers ----------
|
55 |
|
56 |
def _guess_mime(self, image_bytes: bytes) -> str:
|
57 |
kind = imghdr.what(None, h=image_bytes)
|
|
|
65 |
return "image/webp"
|
66 |
return "image/jpeg"
|
67 |
|
68 |
+
# ---------- lifecycle ----------
|
69 |
+
|
70 |
+
async def probe(self) -> bool:
|
71 |
+
"""
|
72 |
+
Lightweight reachability check.
|
73 |
+
- Validates token with whoami
|
74 |
+
- Checks model endpoint exists/reachable
|
75 |
+
Never raises, returns bool.
|
76 |
+
"""
|
77 |
+
if not self.api_key or not self.model_id:
|
78 |
+
return False
|
79 |
+
|
80 |
+
try:
|
81 |
+
timeout = aiohttp.ClientTimeout(total=5)
|
82 |
+
headers_auth = {"Authorization": f"Bearer {self.api_key}"}
|
83 |
+
|
84 |
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
85 |
+
# Token check
|
86 |
+
r1 = await session.get("https://huggingface.co/api/whoami-v2", headers=headers_auth)
|
87 |
+
if r1.status != 200:
|
88 |
+
return False
|
89 |
+
|
90 |
+
# Model reachability (Inference API β GET is fine)
|
91 |
+
r2 = await session.get(f"https://api-inference.huggingface.co/models/{self.model_id}", headers=headers_auth)
|
92 |
+
# Consider 200, 503 (loading), 403/404 (exists but gated/private) as "reachable"
|
93 |
+
if r2.status in (200, 503, 403, 404):
|
94 |
+
return True
|
95 |
+
return False
|
96 |
+
except Exception:
|
97 |
+
return False
|
98 |
+
|
99 |
+
async def ensure_ready(self) -> bool:
|
100 |
+
# Nothing to warm here; we keep it trivial.
|
101 |
+
self._initialized = True
|
102 |
+
return True
|
103 |
+
|
104 |
+
# ---------- caption APIs ----------
|
105 |
+
|
106 |
async def generate_caption(
|
107 |
self,
|
108 |
image_bytes: bytes,
|
|
|
110 |
metadata_instructions: str = "",
|
111 |
) -> Dict[str, Any]:
|
112 |
"""
|
113 |
+
Generate caption using HF Inference Providers (OpenAI-style chat).
|
114 |
"""
|
115 |
+
if not self.api_key or not self.model_id:
|
116 |
+
raise Exception("MODEL_UNAVAILABLE: HuggingFace credentials or model_id missing.")
|
117 |
+
|
118 |
start_time = time.time()
|
119 |
|
120 |
instruction = (prompt or "").strip()
|
|
|
129 |
"Content-Type": "application/json",
|
130 |
}
|
131 |
|
|
|
132 |
payload = {
|
133 |
"model": self.model_id,
|
134 |
"messages": [
|
|
|
145 |
}
|
146 |
|
147 |
try:
|
148 |
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60)) as session:
|
149 |
async with session.post(
|
150 |
self.providers_url,
|
151 |
headers=headers,
|
152 |
json=payload,
|
|
|
153 |
) as resp:
|
154 |
raw_text = await resp.text()
|
155 |
if resp.status != 200:
|
156 |
+
# Surface a consistent, catchable error for fallback
|
157 |
+
raise Exception(f"MODEL_UNAVAILABLE: {self.model_name} unavailable (HTTP {resp.status}).")
|
158 |
result = await resp.json()
|
159 |
except Exception as e:
|
160 |
+
# Never leak aiohttp exceptions outward as-is; normalize to your fallback signal
|
161 |
+
if "MODEL_UNAVAILABLE" not in str(e):
|
162 |
+
raise Exception(f"MODEL_UNAVAILABLE: {self.model_name} is unavailable due to a network/error.")
|
163 |
+
raise
|
164 |
|
165 |
+
# ----- Parse response -----
|
166 |
message = (result.get("choices") or [{}])[0].get("message", {})
|
167 |
content = message.get("content", "")
|
168 |
+
|
169 |
+
# GLM models sometimes put content in reasoning_content
|
170 |
if not content and message.get("reasoning_content"):
|
171 |
content = message.get("reasoning_content", "")
|
172 |
|
173 |
if isinstance(content, list):
|
|
|
174 |
parts = []
|
175 |
for block in content:
|
176 |
if isinstance(block, dict):
|
|
|
179 |
parts.append(str(block))
|
180 |
content = "\n".join([p for p in parts if p])
|
181 |
|
182 |
+
caption = (content or "").strip()
|
|
|
183 |
|
184 |
# Strip accidental fenced JSON
|
185 |
+
if caption.startswith("```json"):
|
186 |
+
caption = re.sub(r"^```json\s*", "", caption)
|
187 |
+
caption = re.sub(r"\s*```$", "", caption)
|
188 |
|
|
|
189 |
metadata = {}
|
190 |
description = ""
|
191 |
+
analysis = caption
|
192 |
recommended_actions = ""
|
193 |
+
|
194 |
try:
|
195 |
+
parsed = json.loads(caption)
|
196 |
description = parsed.get("description", "")
|
197 |
+
analysis = parsed.get("analysis", caption)
|
198 |
recommended_actions = parsed.get("recommended_actions", "")
|
199 |
metadata = parsed.get("metadata", {})
|
|
|
|
|
200 |
caption_text = f"Description: {description}\n\nAnalysis: {analysis}\n\nRecommended Actions: {recommended_actions}"
|
201 |
except json.JSONDecodeError:
|
202 |
+
parsed = None
|
203 |
+
caption_text = caption
|
204 |
|
205 |
elapsed = time.time() - start_time
|
206 |
|
|
|
212 |
"raw_response": {
|
213 |
"model": self.model_id,
|
214 |
"content": content,
|
215 |
+
"parsed": parsed,
|
216 |
},
|
217 |
"description": description,
|
218 |
"analysis": analysis,
|
219 |
+
"recommended_actions": recommended_actions,
|
220 |
}
|
221 |
|
222 |
async def generate_multi_image_caption(
|
|
|
226 |
metadata_instructions: str = "",
|
227 |
) -> Dict[str, Any]:
|
228 |
"""
|
229 |
+
Generate caption for multiple images using HF Inference Providers (OpenAI-style chat).
|
230 |
"""
|
231 |
+
if not self.api_key or not self.model_id:
|
232 |
+
raise Exception("MODEL_UNAVAILABLE: HuggingFace credentials or model_id missing.")
|
233 |
+
|
234 |
start_time = time.time()
|
235 |
|
236 |
instruction = (prompt or "").strip()
|
|
|
242 |
"Content-Type": "application/json",
|
243 |
}
|
244 |
|
|
|
245 |
content = [{"type": "text", "text": instruction}]
|
|
|
|
|
246 |
for image_bytes in image_bytes_list:
|
247 |
mime = self._guess_mime(image_bytes)
|
248 |
data_url = f"data:{mime};base64,{base64.b64encode(image_bytes).decode('utf-8')}"
|
249 |
content.append({"type": "image_url", "image_url": {"url": data_url}})
|
250 |
|
|
|
251 |
payload = {
|
252 |
"model": self.model_id,
|
253 |
+
"messages": [{"role": "user", "content": content}],
|
254 |
+
"max_tokens": 800,
|
|
|
|
|
|
|
|
|
|
|
255 |
"temperature": 0.2,
|
256 |
}
|
257 |
|
258 |
try:
|
259 |
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60)) as session:
|
260 |
async with session.post(
|
261 |
self.providers_url,
|
262 |
headers=headers,
|
263 |
json=payload,
|
|
|
264 |
) as resp:
|
265 |
raw_text = await resp.text()
|
266 |
if resp.status != 200:
|
267 |
+
raise Exception(f"MODEL_UNAVAILABLE: {self.model_name} unavailable (HTTP {resp.status}).")
|
|
|
268 |
result = await resp.json()
|
269 |
except Exception as e:
|
270 |
+
if "MODEL_UNAVAILABLE" not in str(e):
|
271 |
+
raise Exception(f"MODEL_UNAVAILABLE: {self.model_name} is unavailable due to a network/error.")
|
272 |
+
raise
|
|
|
273 |
|
|
|
274 |
message = (result.get("choices") or [{}])[0].get("message", {})
|
275 |
+
content_out = message.get("content", "")
|
|
|
|
|
|
|
|
|
276 |
|
277 |
+
if not content_out and message.get("reasoning_content"):
|
278 |
+
content_out = message.get("reasoning_content", "")
|
279 |
+
|
280 |
+
if isinstance(content_out, list):
|
281 |
parts = []
|
282 |
+
for block in content_out:
|
283 |
if isinstance(block, dict):
|
284 |
parts.append(block.get("text") or block.get("content") or "")
|
285 |
else:
|
286 |
parts.append(str(block))
|
287 |
+
content_out = "\n".join([p for p in parts if p])
|
288 |
|
289 |
+
caption = (content_out or "").strip()
|
|
|
290 |
|
291 |
+
if caption.startswith("```json"):
|
292 |
+
caption = re.sub(r"^```json\s*", "", caption)
|
293 |
+
caption = re.sub(r"\s*```$", "", caption)
|
|
|
294 |
|
|
|
295 |
metadata = {}
|
296 |
description = ""
|
297 |
+
analysis = caption
|
298 |
recommended_actions = ""
|
299 |
+
|
300 |
try:
|
301 |
+
parsed = json.loads(caption)
|
302 |
description = parsed.get("description", "")
|
303 |
+
analysis = parsed.get("analysis", caption)
|
304 |
recommended_actions = parsed.get("recommended_actions", "")
|
305 |
metadata = parsed.get("metadata", {})
|
|
|
|
|
306 |
caption_text = f"Description: {description}\n\nAnalysis: {analysis}\n\nRecommended Actions: {recommended_actions}"
|
307 |
except json.JSONDecodeError:
|
308 |
+
parsed = None
|
309 |
+
caption_text = caption
|
310 |
|
311 |
elapsed = time.time() - start_time
|
312 |
|
|
|
317 |
"processing_time": elapsed,
|
318 |
"raw_response": {
|
319 |
"model": self.model_id,
|
320 |
+
"content": content_out,
|
321 |
+
"parsed": parsed,
|
322 |
+
"image_count": len(image_bytes_list),
|
323 |
},
|
324 |
"description": description,
|
325 |
"analysis": analysis,
|
326 |
+
"recommended_actions": recommended_actions,
|
327 |
}
|
328 |
|
329 |
|
330 |
+
# --- Generic wrapper for easy dynamic registration ---
|
|
|
331 |
class ProvidersGenericVLMService(HuggingFaceService):
|
332 |
"""
|
333 |
+
Generic wrapper so you can register ANY Providers VLM by model_id from config/DB.
|
334 |
Example:
|
335 |
+
ProvidersGenericVLMService(None, "Qwen/Qwen2.5-VL-32B-Instruct", "QWEN2_5_VL_32B")
|
336 |
"""
|
337 |
def __init__(self, api_key: str, model_id: str, public_name: str | None = None):
|
|
|
|
|
|
|
|
|
|
|
|
|
338 |
providers_url = "https://api-inference.huggingface.co/providers/openai"
|
339 |
+
super().__init__(
|
340 |
+
api_key=api_key,
|
341 |
+
model_id=model_id,
|
342 |
+
providers_url=providers_url,
|
343 |
+
public_name=public_name or model_id.replace("/", "_").upper(),
|
344 |
+
)
|
345 |
+
if not self.api_key or not self.model_id:
|
346 |
+
self.is_available = False
|
347 |
+
self.status = ServiceStatus.DEGRADED
|
py_backend/app/services/vlm_service.py
CHANGED
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
1 |
from abc import ABC, abstractmethod
|
2 |
from typing import Dict, Any, Optional, List
|
3 |
import logging
|
@@ -5,6 +8,7 @@ from enum import Enum
|
|
5 |
|
6 |
logger = logging.getLogger(__name__)
|
7 |
|
|
|
8 |
class ModelType(Enum):
|
9 |
"""Enum for different VLM model types"""
|
10 |
GPT4V = "gpt4v"
|
@@ -13,244 +17,244 @@ class ModelType(Enum):
|
|
13 |
LLAMA_VISION = "llama_vision"
|
14 |
CUSTOM = "custom"
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
class VLMService(ABC):
|
17 |
"""Abstract base class for VLM services"""
|
18 |
-
|
19 |
-
def __init__(self, model_name: str, model_type: ModelType):
|
20 |
self.model_name = model_name
|
21 |
self.model_type = model_type
|
22 |
-
self.
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
@abstractmethod
|
25 |
async def generate_caption(self, image_bytes: bytes, prompt: str, metadata_instructions: str = "") -> Dict[str, Any]:
|
26 |
"""Generate caption for an image"""
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
|
|
29 |
def get_model_info(self) -> Dict[str, Any]:
|
30 |
"""Get model information"""
|
31 |
return {
|
32 |
"name": self.model_name,
|
33 |
"type": self.model_type.value,
|
|
|
34 |
"available": self.is_available,
|
|
|
|
|
35 |
}
|
36 |
|
|
|
37 |
class VLMServiceManager:
|
38 |
"""Manager for multiple VLM services"""
|
39 |
-
|
40 |
def __init__(self):
|
41 |
self.services: Dict[str, VLMService] = {}
|
42 |
self.default_service: Optional[str] = None
|
43 |
-
|
44 |
def register_service(self, service: VLMService):
|
45 |
-
"""
|
|
|
|
|
|
|
46 |
self.services[service.model_name] = service
|
47 |
if not self.default_service:
|
48 |
self.default_service = service.model_name
|
49 |
-
logger.info(
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
def get_service(self, model_name: str) -> Optional[VLMService]:
|
52 |
"""Get a specific VLM service"""
|
53 |
return self.services.get(model_name)
|
54 |
-
|
55 |
def get_default_service(self) -> Optional[VLMService]:
|
56 |
"""Get the default VLM service"""
|
57 |
-
if self.default_service
|
58 |
-
|
59 |
-
return None
|
60 |
-
|
61 |
def get_available_models(self) -> list:
|
62 |
"""Get list of available model names"""
|
63 |
return list(self.services.keys())
|
64 |
-
|
65 |
-
async def
|
66 |
-
|
67 |
-
|
68 |
service = None
|
69 |
if model_name and model_name != "random":
|
70 |
service = self.services.get(model_name)
|
71 |
if not service:
|
72 |
-
|
73 |
-
|
|
|
74 |
if not service and self.services:
|
75 |
-
# If random is selected or no specific model, choose a random available service
|
76 |
if db_session:
|
77 |
-
# Check database availability for random selection
|
78 |
try:
|
79 |
-
from .. import crud
|
80 |
available_models = crud.get_models(db_session)
|
81 |
-
|
82 |
-
|
83 |
-
print(f"DEBUG: Available models in database: {available_model_codes}")
|
84 |
-
print(f"DEBUG: Registered services: {list(self.services.keys())}")
|
85 |
|
86 |
-
#
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
88 |
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
if available_services:
|
95 |
-
import random
|
96 |
-
import time
|
97 |
-
# Use current time as seed for better randomness
|
98 |
-
random.seed(int(time.time() * 1000000) % 1000000)
|
99 |
-
|
100 |
-
# Shuffle the list first for better randomization
|
101 |
-
shuffled_services = available_services.copy()
|
102 |
-
random.shuffle(shuffled_services)
|
103 |
-
|
104 |
-
service = shuffled_services[0]
|
105 |
-
print(f"Randomly selected service: {service.model_name} (from {len(available_services)} available)")
|
106 |
-
print(f"DEBUG: All available services were: {[s.model_name for s in available_services]}")
|
107 |
-
print(f"DEBUG: Shuffled order: {[s.model_name for s in shuffled_services]}")
|
108 |
-
else:
|
109 |
-
# Fallback to any available service, prioritizing STUB_MODEL
|
110 |
-
print(f"WARNING: No services found in database intersection, using fallback")
|
111 |
-
if "STUB_MODEL" in self.services:
|
112 |
-
service = self.services["STUB_MODEL"]
|
113 |
-
print(f"Using STUB_MODEL fallback service: {service.model_name}")
|
114 |
-
else:
|
115 |
-
service = next(iter(self.services.values()))
|
116 |
-
print(f"Using first available fallback service: {service.model_name}")
|
117 |
except Exception as e:
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
print(f"Using STUB_MODEL fallback service: {service.model_name}")
|
122 |
-
else:
|
123 |
-
service = next(iter(self.services.values()))
|
124 |
-
print(f"Using fallback service: {service.model_name}")
|
125 |
else:
|
126 |
-
|
127 |
-
|
128 |
-
if
|
129 |
-
|
130 |
-
service = random.choice(available_services)
|
131 |
-
print(f"Randomly selected service: {service.model_name}")
|
132 |
-
else:
|
133 |
-
# Fallback to any available service, prioritizing STUB_MODEL
|
134 |
-
if "STUB_MODEL" in self.services:
|
135 |
-
service = self.services["STUB_MODEL"]
|
136 |
-
print(f"Using STUB_MODEL fallback service: {service.model_name}")
|
137 |
-
else:
|
138 |
-
service = next(iter(self.services.values()))
|
139 |
-
print(f"Using fallback service: {service.model_name}")
|
140 |
-
|
141 |
if not service:
|
142 |
-
raise
|
143 |
-
|
144 |
-
|
145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
try:
|
147 |
-
print(f"DEBUG: Calling service {service.model_name} for caption generation")
|
148 |
result = await service.generate_caption(image_bytes, prompt, metadata_instructions)
|
149 |
result["model"] = service.model_name
|
150 |
-
print(f"DEBUG: Service {service.model_name} returned result with model: {result.get('model', 'NOT_FOUND')}")
|
151 |
return result
|
152 |
except Exception as e:
|
153 |
-
|
154 |
-
# Try other services
|
155 |
-
for other_service in self.services.values():
|
156 |
-
if other_service != service:
|
157 |
-
try:
|
158 |
-
result = await other_service.generate_caption(image_bytes, prompt, metadata_instructions)
|
159 |
-
result["model"] = other_service.model_name
|
160 |
-
result["fallback_used"] = True
|
161 |
-
result["original_model"] = service.model_name
|
162 |
-
result["fallback_reason"] = str(e)
|
163 |
-
return result
|
164 |
-
except Exception as fallback_error:
|
165 |
-
print(f"Fallback service {other_service.model_name} also failed: {fallback_error}")
|
166 |
-
continue
|
167 |
|
168 |
-
#
|
169 |
-
raise Exception(f"All VLM services failed. Last error: {str(e)}")
|
170 |
-
|
171 |
-
async def generate_multi_image_caption(self, image_bytes_list: List[bytes], prompt: str, metadata_instructions: str = "", model_name: str | None = None, db_session = None) -> dict:
|
172 |
-
"""Generate caption for multiple images using the specified model or fallback to available service."""
|
173 |
-
|
174 |
-
service = None
|
175 |
-
if model_name and model_name != "random":
|
176 |
-
service = self.services.get(model_name)
|
177 |
-
if not service:
|
178 |
-
print(f"Model '{model_name}' not found, using fallback")
|
179 |
-
|
180 |
-
if not service and self.services:
|
181 |
-
# If random is selected or no specific model, choose a random available service
|
182 |
if db_session:
|
183 |
-
# Check database availability for random selection
|
184 |
try:
|
185 |
from .. import crud
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
try:
|
234 |
result = await service.generate_multi_image_caption(image_bytes_list, prompt, metadata_instructions)
|
235 |
result["model"] = service.model_name
|
236 |
return result
|
237 |
except Exception as e:
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
|
|
|
|
255 |
|
256 |
-
|
|
|
|
1 |
+
# app/services/vlm_services.py
|
2 |
+
from __future__ import annotations
|
3 |
+
|
4 |
from abc import ABC, abstractmethod
|
5 |
from typing import Dict, Any, Optional, List
|
6 |
import logging
|
|
|
8 |
|
9 |
logger = logging.getLogger(__name__)
|
10 |
|
11 |
+
|
12 |
class ModelType(Enum):
|
13 |
"""Enum for different VLM model types"""
|
14 |
GPT4V = "gpt4v"
|
|
|
17 |
LLAMA_VISION = "llama_vision"
|
18 |
CUSTOM = "custom"
|
19 |
|
20 |
+
|
21 |
+
class ServiceStatus(Enum):
|
22 |
+
READY = "ready"
|
23 |
+
DEGRADED = "degraded" # registered but probe failed or not run
|
24 |
+
UNAVAILABLE = "unavailable"
|
25 |
+
|
26 |
+
|
27 |
class VLMService(ABC):
|
28 |
"""Abstract base class for VLM services"""
|
29 |
+
|
30 |
+
def __init__(self, model_name: str, model_type: ModelType, provider: str = "custom", lazy_init: bool = True):
|
31 |
self.model_name = model_name
|
32 |
self.model_type = model_type
|
33 |
+
self.provider = provider
|
34 |
+
self.lazy_init = lazy_init
|
35 |
+
self.is_available = True # quick flag used by manager for random selection
|
36 |
+
self.status = ServiceStatus.DEGRADED
|
37 |
+
self._initialized = False
|
38 |
+
|
39 |
+
async def probe(self) -> bool:
|
40 |
+
"""
|
41 |
+
Lightweight reachability/metadata check. Providers should override.
|
42 |
+
Must be quick (<5s) and NEVER raise. Return True if reachable/ok.
|
43 |
+
"""
|
44 |
+
return True
|
45 |
+
|
46 |
+
async def ensure_ready(self) -> bool:
|
47 |
+
"""
|
48 |
+
Called once before first use. Providers may override to open clients/warm caches.
|
49 |
+
Must set _initialized True and return True on success. NEVER raise.
|
50 |
+
"""
|
51 |
+
self._initialized = True
|
52 |
+
self.status = ServiceStatus.READY
|
53 |
+
return True
|
54 |
+
|
55 |
@abstractmethod
|
56 |
async def generate_caption(self, image_bytes: bytes, prompt: str, metadata_instructions: str = "") -> Dict[str, Any]:
|
57 |
"""Generate caption for an image"""
|
58 |
+
...
|
59 |
+
|
60 |
+
# Optional for multi-image models; override in providers that support it.
|
61 |
+
async def generate_multi_image_caption(self, image_bytes_list: List[bytes], prompt: str, metadata_instructions: str = "") -> Dict[str, Any]:
|
62 |
+
raise NotImplementedError("Multi-image caption not implemented for this service")
|
63 |
+
|
64 |
def get_model_info(self) -> Dict[str, Any]:
|
65 |
"""Get model information"""
|
66 |
return {
|
67 |
"name": self.model_name,
|
68 |
"type": self.model_type.value,
|
69 |
+
"provider": self.provider,
|
70 |
"available": self.is_available,
|
71 |
+
"status": self.status.value,
|
72 |
+
"lazy_init": self.lazy_init,
|
73 |
}
|
74 |
|
75 |
+
|
76 |
class VLMServiceManager:
|
77 |
"""Manager for multiple VLM services"""
|
78 |
+
|
79 |
def __init__(self):
|
80 |
self.services: Dict[str, VLMService] = {}
|
81 |
self.default_service: Optional[str] = None
|
82 |
+
|
83 |
def register_service(self, service: VLMService):
|
84 |
+
"""
|
85 |
+
Register a VLM service (NO network calls here).
|
86 |
+
Weβll probe later, asynchronously, so registration never blocks startup.
|
87 |
+
"""
|
88 |
self.services[service.model_name] = service
|
89 |
if not self.default_service:
|
90 |
self.default_service = service.model_name
|
91 |
+
logger.info("Registered VLM service: %s (%s)", service.model_name, service.provider)
|
92 |
+
|
93 |
+
async def probe_all(self):
|
94 |
+
"""
|
95 |
+
Run lightweight probes for all registered services.
|
96 |
+
Failures do not remove services; they stay DEGRADED and will lazy-init on first use.
|
97 |
+
"""
|
98 |
+
for svc in self.services.values():
|
99 |
+
try:
|
100 |
+
ok = await svc.probe()
|
101 |
+
svc.status = ServiceStatus.READY if ok else ServiceStatus.DEGRADED
|
102 |
+
# If probe fails but lazy_init is allowed, keep is_available True so selection still works.
|
103 |
+
svc.is_available = ok or svc.lazy_init
|
104 |
+
logger.info("Probe %s -> %s", svc.model_name, svc.status.value)
|
105 |
+
except Exception as e:
|
106 |
+
logger.warning("Probe failed for %s: %r", svc.model_name, e)
|
107 |
+
svc.status = ServiceStatus.DEGRADED
|
108 |
+
svc.is_available = bool(svc.lazy_init)
|
109 |
+
|
110 |
def get_service(self, model_name: str) -> Optional[VLMService]:
|
111 |
"""Get a specific VLM service"""
|
112 |
return self.services.get(model_name)
|
113 |
+
|
114 |
def get_default_service(self) -> Optional[VLMService]:
|
115 |
"""Get the default VLM service"""
|
116 |
+
return self.services.get(self.default_service) if self.default_service else None
|
117 |
+
|
|
|
|
|
118 |
def get_available_models(self) -> list:
|
119 |
"""Get list of available model names"""
|
120 |
return list(self.services.keys())
|
121 |
+
|
122 |
+
async def _pick_service(self, model_name: Optional[str], db_session) -> VLMService:
|
123 |
+
# Specific pick
|
|
|
124 |
service = None
|
125 |
if model_name and model_name != "random":
|
126 |
service = self.services.get(model_name)
|
127 |
if not service:
|
128 |
+
logger.warning("Model '%s' not found; will pick fallback", model_name)
|
129 |
+
|
130 |
+
# Fallback / random based on DB allowlist (is_available==True)
|
131 |
if not service and self.services:
|
|
|
132 |
if db_session:
|
|
|
133 |
try:
|
134 |
+
from .. import crud # local import to avoid cycles at import time
|
135 |
available_models = crud.get_models(db_session)
|
136 |
+
allowed = {m.m_code for m in available_models if getattr(m, "is_available", False)}
|
|
|
|
|
|
|
137 |
|
138 |
+
# Check for configured fallback model first
|
139 |
+
configured_fallback = crud.get_fallback_model(db_session)
|
140 |
+
if configured_fallback and configured_fallback in allowed:
|
141 |
+
fallback_service = self.services.get(configured_fallback)
|
142 |
+
if fallback_service and fallback_service.is_available:
|
143 |
+
logger.info("Using configured fallback model: %s", configured_fallback)
|
144 |
+
service = fallback_service
|
145 |
|
146 |
+
# If no configured fallback or it's not available, use STUB_MODEL as final fallback
|
147 |
+
if not service:
|
148 |
+
service = self.services.get("STUB_MODEL") or next(iter(self.services.values()))
|
149 |
+
logger.info("Using STUB_MODEL as final fallback")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
150 |
except Exception as e:
|
151 |
+
logger.warning("DB availability check failed: %r; using first available", e)
|
152 |
+
avail = [s for s in self.services.values() if s.is_available]
|
153 |
+
service = (self.services.get("STUB_MODEL") or (random.choice(avail) if avail else next(iter(self.services.values()))))
|
|
|
|
|
|
|
|
|
154 |
else:
|
155 |
+
import random
|
156 |
+
avail = [s for s in self.services.values() if s.is_available]
|
157 |
+
service = (random.choice(avail) if avail else (self.services.get("STUB_MODEL") or next(iter(self.services.values()))))
|
158 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
if not service:
|
160 |
+
raise RuntimeError("No VLM service available")
|
161 |
+
|
162 |
+
# Lazy init on first use
|
163 |
+
if service.lazy_init and not service._initialized:
|
164 |
+
try:
|
165 |
+
ok = await service.ensure_ready()
|
166 |
+
service.status = ServiceStatus.READY if ok else ServiceStatus.DEGRADED
|
167 |
+
except Exception as e:
|
168 |
+
logger.warning("ensure_ready failed for %s: %r", service.model_name, e)
|
169 |
+
service.status = ServiceStatus.DEGRADED
|
170 |
+
|
171 |
+
return service
|
172 |
+
|
173 |
+
async def generate_caption(self, image_bytes: bytes, prompt: str, metadata_instructions: str = "", model_name: str | None = None, db_session=None) -> dict:
|
174 |
+
"""Generate caption using the specified model or fallback to available service."""
|
175 |
+
service = await self._pick_service(model_name, db_session)
|
176 |
try:
|
|
|
177 |
result = await service.generate_caption(image_bytes, prompt, metadata_instructions)
|
178 |
result["model"] = service.model_name
|
|
|
179 |
return result
|
180 |
except Exception as e:
|
181 |
+
logger.error("Error with %s: %r; trying fallbacks", service.model_name, e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
+
# First, try the configured fallback model if available
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
if db_session:
|
|
|
185 |
try:
|
186 |
from .. import crud
|
187 |
+
configured_fallback = crud.get_fallback_model(db_session)
|
188 |
+
if configured_fallback and configured_fallback != service.model_name:
|
189 |
+
fallback_service = self.services.get(configured_fallback)
|
190 |
+
if fallback_service and fallback_service.is_available:
|
191 |
+
logger.info("Trying configured fallback model: %s", configured_fallback)
|
192 |
+
try:
|
193 |
+
if fallback_service.lazy_init and not fallback_service._initialized:
|
194 |
+
await fallback_service.ensure_ready()
|
195 |
+
res = await fallback_service.generate_caption(image_bytes, prompt, metadata_instructions)
|
196 |
+
res.update({
|
197 |
+
"model": fallback_service.model_name,
|
198 |
+
"fallback_used": True,
|
199 |
+
"original_model": service.model_name,
|
200 |
+
"fallback_reason": str(e),
|
201 |
+
})
|
202 |
+
logger.info("Configured fallback model %s succeeded", configured_fallback)
|
203 |
+
return res
|
204 |
+
except Exception as fe:
|
205 |
+
logger.warning("Configured fallback service %s also failed: %r", configured_fallback, fe)
|
206 |
+
except Exception as db_error:
|
207 |
+
logger.warning("Failed to get configured fallback: %r", db_error)
|
208 |
+
|
209 |
+
# If configured fallback failed or not available, try STUB_MODEL
|
210 |
+
stub_service = self.services.get("STUB_MODEL")
|
211 |
+
if stub_service and stub_service != service.model_name:
|
212 |
+
logger.info("Trying STUB_MODEL as final fallback")
|
213 |
+
try:
|
214 |
+
if stub_service.lazy_init and not stub_service._initialized:
|
215 |
+
await stub_service.ensure_ready()
|
216 |
+
res = await stub_service.generate_caption(image_bytes, prompt, metadata_instructions)
|
217 |
+
res.update({
|
218 |
+
"model": stub_service.model_name,
|
219 |
+
"fallback_used": True,
|
220 |
+
"original_model": service.model_name,
|
221 |
+
"fallback_reason": str(e),
|
222 |
+
})
|
223 |
+
logger.info("STUB_MODEL succeeded as final fallback")
|
224 |
+
return res
|
225 |
+
except Exception as fe:
|
226 |
+
logger.warning("STUB_MODEL also failed: %r", fe)
|
227 |
+
|
228 |
+
# All services failed
|
229 |
+
raise RuntimeError(f"All VLM services failed. Last error from {service.model_name}: {e}")
|
230 |
+
|
231 |
+
async def generate_multi_image_caption(self, image_bytes_list: List[bytes], prompt: str, metadata_instructions: str = "", model_name: str | None = None, db_session=None) -> dict:
|
232 |
+
"""Multi-image version if a provider supports it."""
|
233 |
+
service = await self._pick_service(model_name, db_session)
|
234 |
try:
|
235 |
result = await service.generate_multi_image_caption(image_bytes_list, prompt, metadata_instructions)
|
236 |
result["model"] = service.model_name
|
237 |
return result
|
238 |
except Exception as e:
|
239 |
+
logger.error("Error with %s (multi): %r; trying fallbacks", service.model_name, e)
|
240 |
+
for other in self.services.values():
|
241 |
+
if other is service:
|
242 |
+
continue
|
243 |
+
try:
|
244 |
+
if other.lazy_init and not other._initialized:
|
245 |
+
await other.ensure_ready()
|
246 |
+
res = await other.generate_multi_image_caption(image_bytes_list, prompt, metadata_instructions)
|
247 |
+
res.update({
|
248 |
+
"model": other.model_name,
|
249 |
+
"fallback_used": True,
|
250 |
+
"original_model": service.model_name,
|
251 |
+
"fallback_reason": str(e),
|
252 |
+
})
|
253 |
+
return res
|
254 |
+
except Exception:
|
255 |
+
continue
|
256 |
+
raise RuntimeError(f"All VLM services failed (multi). Last error from {service.model_name}: {e}")
|
257 |
+
|
258 |
|
259 |
+
# Global manager instance (as in your current code)
|
260 |
+
vlm_manager = VLMServiceManager()
|
py_backend/db_probe.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
print('ENV URL repr:', repr(os.getenv('DATABASE_URL')))
|
3 |
+
from sqlalchemy import create_engine, text
|
4 |
+
u = os.getenv('DATABASE_URL')
|
5 |
+
engine = create_engine(u, pool_pre_ping=True, future=True)
|
6 |
+
with engine.connect() as c:
|
7 |
+
print('Connected OK. version:', c.execute(text('select version()')).scalar())
|
py_backend/dbtest.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from sqlalchemy import create_engine
|
3 |
+
u = os.getenv('DATABASE_URL')
|
4 |
+
print('URL:', u)
|
5 |
+
e = create_engine(u, pool_pre_ping=True)
|
6 |
+
print('Dialect:', e.dialect.name, 'Driver:', e.dialect.driver)
|
7 |
+
with e.connect() as c:
|
8 |
+
print('Connected OK:', c.exec_driver_sql('select version()').scalar())
|
py_backend/debug_hf_models.py
DELETED
@@ -1,132 +0,0 @@
|
|
1 |
-
#!/usr/bin/env python3
|
2 |
-
"""
|
3 |
-
Debug script to diagnose Hugging Face model registration issues.
|
4 |
-
Run this script to check API key validity and model availability.
|
5 |
-
"""
|
6 |
-
|
7 |
-
import os
|
8 |
-
import sys
|
9 |
-
import asyncio
|
10 |
-
import aiohttp
|
11 |
-
import json
|
12 |
-
from typing import Dict, Any
|
13 |
-
|
14 |
-
# Add the current directory to Python path
|
15 |
-
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
16 |
-
|
17 |
-
from app.config import settings
|
18 |
-
from app.database import SessionLocal
|
19 |
-
from app import crud
|
20 |
-
|
21 |
-
async def test_hf_api_key(api_key: str) -> Dict[str, Any]:
|
22 |
-
"""Test if the Hugging Face API key is valid"""
|
23 |
-
headers = {
|
24 |
-
"Authorization": f"Bearer {api_key}",
|
25 |
-
"Content-Type": "application/json",
|
26 |
-
}
|
27 |
-
|
28 |
-
# Test with a simple model list request
|
29 |
-
test_url = "https://api-inference.huggingface.co/models"
|
30 |
-
|
31 |
-
try:
|
32 |
-
async with aiohttp.ClientSession() as session:
|
33 |
-
async with session.get(test_url, headers=headers, timeout=30) as resp:
|
34 |
-
if resp.status == 200:
|
35 |
-
return {"valid": True, "message": "API key is valid"}
|
36 |
-
elif resp.status == 401:
|
37 |
-
return {"valid": False, "message": "API key is invalid or expired"}
|
38 |
-
else:
|
39 |
-
return {"valid": False, "message": f"API test failed with status {resp.status}"}
|
40 |
-
except Exception as e:
|
41 |
-
return {"valid": False, "message": f"API test failed: {str(e)}"}
|
42 |
-
|
43 |
-
async def test_model_availability(api_key: str, model_id: str) -> Dict[str, Any]:
|
44 |
-
"""Test if a specific model is available"""
|
45 |
-
headers = {
|
46 |
-
"Authorization": f"Bearer {api_key}",
|
47 |
-
"Content-Type": "application/json",
|
48 |
-
}
|
49 |
-
|
50 |
-
# Test with a simple text generation request
|
51 |
-
test_url = f"https://api-inference.huggingface.co/models/{model_id}"
|
52 |
-
payload = {
|
53 |
-
"inputs": "Hello, how are you?",
|
54 |
-
"parameters": {"max_new_tokens": 10}
|
55 |
-
}
|
56 |
-
|
57 |
-
try:
|
58 |
-
async with aiohttp.ClientSession() as session:
|
59 |
-
async with session.post(test_url, headers=headers, json=payload, timeout=30) as resp:
|
60 |
-
if resp.status == 200:
|
61 |
-
return {"available": True, "message": "Model is available"}
|
62 |
-
elif resp.status == 404:
|
63 |
-
return {"available": False, "message": "Model not found"}
|
64 |
-
elif resp.status == 503:
|
65 |
-
return {"available": False, "message": "Model is loading or unavailable"}
|
66 |
-
else:
|
67 |
-
return {"available": False, "message": f"Model test failed with status {resp.status}"}
|
68 |
-
except Exception as e:
|
69 |
-
return {"available": False, "message": f"Model test failed: {str(e)}"}
|
70 |
-
|
71 |
-
async def main():
|
72 |
-
"""Main diagnostic function"""
|
73 |
-
print("π Hugging Face Model Diagnostic Tool")
|
74 |
-
print("=" * 50)
|
75 |
-
|
76 |
-
# Check if API key is set
|
77 |
-
if not settings.HF_API_KEY:
|
78 |
-
print("β HF_API_KEY is not set in environment variables")
|
79 |
-
print(" Please set HF_API_KEY to your Hugging Face API token")
|
80 |
-
return
|
81 |
-
|
82 |
-
print(f"β
HF_API_KEY is set (length: {len(settings.HF_API_KEY)})")
|
83 |
-
|
84 |
-
# Test API key validity
|
85 |
-
print("\nπ Testing API key validity...")
|
86 |
-
api_test = await test_hf_api_key(settings.HF_API_KEY)
|
87 |
-
if api_test["valid"]:
|
88 |
-
print(f"β
{api_test['message']}")
|
89 |
-
else:
|
90 |
-
print(f"β {api_test['message']}")
|
91 |
-
print(" Please check your Hugging Face API token at https://huggingface.co/settings/tokens")
|
92 |
-
return
|
93 |
-
|
94 |
-
# Get models from database
|
95 |
-
print("\nπ Checking models in database...")
|
96 |
-
db = SessionLocal()
|
97 |
-
try:
|
98 |
-
models = crud.get_models(db)
|
99 |
-
hf_models = [m for m in models if m.provider == "huggingface" and m.model_id]
|
100 |
-
|
101 |
-
if not hf_models:
|
102 |
-
print("β οΈ No Hugging Face models found in database")
|
103 |
-
return
|
104 |
-
|
105 |
-
print(f"β
Found {len(hf_models)} Hugging Face models in database:")
|
106 |
-
for model in hf_models:
|
107 |
-
print(f" - {model.m_code}: {model.model_id}")
|
108 |
-
|
109 |
-
# Test each model
|
110 |
-
print("\nπ§ͺ Testing model availability...")
|
111 |
-
for model in hf_models:
|
112 |
-
print(f"\nTesting {model.m_code} ({model.model_id})...")
|
113 |
-
model_test = await test_model_availability(settings.HF_API_KEY, model.model_id)
|
114 |
-
|
115 |
-
if model_test["available"]:
|
116 |
-
print(f" β
{model_test['message']}")
|
117 |
-
else:
|
118 |
-
print(f" β {model_test['message']}")
|
119 |
-
|
120 |
-
finally:
|
121 |
-
db.close()
|
122 |
-
|
123 |
-
print("\n" + "=" * 50)
|
124 |
-
print("π‘ Troubleshooting Tips:")
|
125 |
-
print("1. Check your Hugging Face API token at https://huggingface.co/settings/tokens")
|
126 |
-
print("2. Ensure your token has 'read' permissions")
|
127 |
-
print("3. Some models may require special access or may be temporarily unavailable")
|
128 |
-
print("4. Check model availability at https://huggingface.co/models")
|
129 |
-
print("5. Network issues can cause connection failures")
|
130 |
-
|
131 |
-
if __name__ == "__main__":
|
132 |
-
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
py_backend/static/assets/{AdminPage-BwDJigwh.js β AdminPage-a1ONalH9.js}
RENAMED
@@ -1,4 +1,4 @@
|
|
1 |
-
import{r as
|
2 |
Model "${o.label}" added successfully!
|
3 |
|
4 |
β οΈ IMPORTANT: Model will NOT work until you complete these steps:
|
@@ -8,27 +8,27 @@ Model "${o.label}" added successfully!
|
|
8 |
2. π Verify model_id format.
|
9 |
|
10 |
3. π Check model specific documentation for details.
|
11 |
-
`;
|
12 |
Code: ${e.p_code}
|
13 |
Label: ${e.label}
|
14 |
Image Type: ${e.image_type}
|
15 |
Active: ${e.is_active}
|
16 |
|
17 |
Metadata Instructions:
|
18 |
-
${e.metadata_instructions||"No instructions available"}`),
|
19 |
Code: ${e.p_code}
|
20 |
Label: ${e.label}
|
21 |
Image Type: ${e.image_type}
|
22 |
Active: ${e.is_active}
|
23 |
|
24 |
Metadata Instructions:
|
25 |
-
${e.metadata_instructions||"No instructions available"}`),
|
26 |
|
27 |
Found ${s.models?.length||0} models in database.
|
28 |
|
29 |
Available models:
|
30 |
${s.models?.filter(m=>m.is_available).map(m=>`- ${m.label} (${m.m_code})`).join(`
|
31 |
-
`)||"None"}`;_(i)}else{const s=`β API connection failed: HTTP ${e.status}`;_(s)}p(!0)}catch(e){const s=`β Connection error: ${e}`;_(s),p(!0)}},children:"Test Connection"}),a.jsx(
|
32 |
|
33 |
`,e.forEach((m,g)=>{s+=`=== Schema ${g+1} ===
|
34 |
`,s+=JSON.stringify(m,null,2),s+=`
|
@@ -42,4 +42,4 @@ Raw data:
|
|
42 |
${JSON.stringify(e,null,2)}`:s=`Prompts Response:
|
43 |
|
44 |
Unexpected data type: ${typeof e}
|
45 |
-
Value: ${e}`,_(s),
|
|
|
1 |
+
import{r as t,j as a,N as D,H as Z,_ as Ae,I as Ie,O as h,z as f,n as d,J as b}from"./index-BCz4bkWK.js";import{u as Pe}from"./useAdmin-LSEnRjVv.js";const $e="_adminContainer_j11pf_5",Le="_adminHeader_j11pf_13",Ee="_adminSection_j11pf_20",Be="_modelSelectionArea_j11pf_29",Oe="_modelSelectionRow_j11pf_36",De="_modelsTable_j11pf_89",Re="_promptSubsection_j11pf_97",Ue="_promptSubsectionTitle_j11pf_109",ze="_modelCode_j11pf_152",He="_modelId_j11pf_157",Je="_modelActions_j11pf_163",Ge="_addModelButtonContainer_j11pf_169",Ve="_addModelForm_j11pf_177",Ke="_addModelFormTitle_j11pf_185",We="_addModelFormGrid_j11pf_193",qe="_addModelFormField_j11pf_206",Ye="_addModelFormCheckbox_j11pf_250",Qe="_addModelFormActions_j11pf_268",Xe="_modalOverlay_j11pf_277",Ze="_modalContent_j11pf_291",ea="_modalBody_j11pf_302",aa="_modalTitle_j11pf_312",la="_modalText_j11pf_320",sa="_modalTextLeft_j11pf_332",oa="_modalButtons_j11pf_355",ta="_modalForm_j11pf_363",da="_formField_j11pf_372",ia="_formLabel_j11pf_376",na="_formInput_j11pf_385",ra="_textarea_j11pf_407",l={adminContainer:$e,adminHeader:Le,adminSection:Ee,modelSelectionArea:Be,modelSelectionRow:Oe,modelsTable:De,promptSubsection:Re,promptSubsectionTitle:Ue,modelCode:ze,modelId:He,modelActions:Je,addModelButtonContainer:Ge,addModelForm:Ve,addModelFormTitle:Ke,addModelFormGrid:We,addModelFormField:qe,addModelFormCheckbox:Ye,addModelFormActions:Qe,modalOverlay:Xe,modalContent:Ze,modalBody:ea,modalTitle:aa,modalText:la,modalTextLeft:sa,modalButtons:oa,modalForm:ta,formField:da,formLabel:ia,formInput:na,textarea:ra},k="selectedVlmModel";function ha(){const{isAuthenticated:w,isLoading:ee,login:ae,logout:le}=Pe(),[F,R]=t.useState(""),[U,j]=t.useState(""),[z,H]=t.useState(!1),[T,J]=t.useState([]),[se,y]=t.useState(""),[oe,A]=t.useState(""),[G,te]=t.useState([]),[de,ie]=t.useState([]),[ne,N]=t.useState(!1),[re,C]=t.useState(!1),[ce,I]=t.useState(null),[M,P]=t.useState(null),[n,r]=t.useState({p_code:"",label:"",metadata_instructions:"",image_type:"crisis_map",is_active:!1}),[V,$]=t.useState(!1),[me,L]=t.useState(!1),[E,B]=t.useState(null),[o,c]=t.useState({m_code:"",label:"",model_type:"custom",provider:"huggingface",model_id:"",is_available:!1,is_fallback:!1}),[he,S]=t.useState(!1),[pe,O]=t.useState(!1),[_e,p]=t.useState(!1),[K,W]=t.useState(""),[ue,je]=t.useState(""),[xe,_]=t.useState(""),[ve,x]=t.useState(""),u=t.useCallback(()=>{fetch("/api/models").then(e=>e.json()).then(e=>{console.log("Models data received:",e),J(e.models||[]);const s=localStorage.getItem(k);if(e.models&&e.models.length>0)if(s==="random")y("random");else if(s&&e.models.find(i=>i.m_code===s&&i.is_available))y(s);else{const i=e.models.find(m=>m.is_available)||e.models[0];y(i.m_code),localStorage.setItem(k,i.m_code)}}).catch(()=>{}),fetch("/api/admin/fallback-model",{headers:{Authorization:`Bearer ${localStorage.getItem("adminToken")}`}}).then(e=>e.json()).then(e=>{console.log("Fallback model data received:",e),e.fallback_model?A(e.fallback_model.m_code):A("")}).catch(()=>{})},[]),v=t.useCallback(()=>{console.log("=== fetchPrompts called ==="),fetch("/api/prompts").then(e=>e.json()).then(e=>{console.log("Prompts data received:",e),te(e||[]),console.log("State update triggered with:",e||[])}).catch(e=>{console.error("Error fetching prompts:",e)})},[]),q=t.useCallback(()=>{fetch("/api/image-types").then(e=>e.json()).then(e=>{console.log("Image types data received:",e),ie(e||[])}).catch(()=>{})},[]);t.useEffect(()=>{w&&(u(),v(),q())},[w,u,v,q]);const Y=e=>{P(e),r({p_code:e.p_code,label:e.label||"",metadata_instructions:e.metadata_instructions||"",image_type:e.image_type||"crisis_map",is_active:e.is_active||!1}),N(!0)},ge=async()=>{try{if(!M){alert("No prompt selected for editing");return}const e=await fetch(`/api/prompts/${M.p_code}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:n.label,metadata_instructions:n.metadata_instructions,image_type:n.image_type,is_active:n.is_active})});if(e.ok)v(),N(!1),P(null),r({p_code:"",label:"",metadata_instructions:"",image_type:"crisis_map",is_active:!1});else{const s=await e.json();alert(`Failed to update prompt: ${s.error||"Unknown error"}`)}}catch{alert("Error updating prompt")}},Q=async(e,s)=>{try{const i=await fetch(`/api/prompts/${e}/toggle-active?image_type=${s}`,{method:"POST",headers:{"Content-Type":"application/json"}});if(i.ok)v();else{const m=await i.json();alert(`Failed to toggle prompt active status: ${m.detail||"Unknown error"}`)}}catch{alert("Error toggling prompt active status")}},X=e=>{I(e),r({p_code:"",label:"",metadata_instructions:"",image_type:e,is_active:!1}),C(!0)},fe=async()=>{try{const e=await fetch("/api/prompts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(e.ok)v(),C(!1),I(null),r({p_code:"",label:"",metadata_instructions:"",image_type:"crisis_map",is_active:!1});else{const s=await e.json();alert(`Failed to create prompt: ${s.detail||"Unknown error"}`)}}catch{alert("Error creating prompt")}},be=async(e,s)=>{try{const i=await fetch(`/api/models/${e}/toggle`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({is_available:!s})});if(i.ok)J(m=>m.map(g=>g.m_code===e?{...g,is_available:!s}:g));else{const m=await i.json();alert(`Failed to toggle model availability: ${m.error||"Unknown error"}`)}}catch{alert("Error toggling model availability")}},ye=e=>{y(e),e==="random"?localStorage.setItem(k,"random"):localStorage.setItem(k,e)},Ne=async e=>{try{const s=await fetch(`/api/admin/models/${e}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${localStorage.getItem("adminToken")}`},body:JSON.stringify({is_fallback:!0})});if(s.ok)A(e),u();else{const i=await s.json();alert(`Failed to set fallback model: ${i.detail||"Unknown error"}`)}}catch{alert("Error setting fallback model")}},Ce=async()=>{try{const e=await fetch("/api/admin/models",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${localStorage.getItem("adminToken")}`},body:JSON.stringify(o)});if(e.ok){const s=`
|
2 |
Model "${o.label}" added successfully!
|
3 |
|
4 |
β οΈ IMPORTANT: Model will NOT work until you complete these steps:
|
|
|
8 |
2. π Verify model_id format.
|
9 |
|
10 |
3. π Check model specific documentation for details.
|
11 |
+
`;je(s),O(!0),$(!1),c({m_code:"",label:"",model_type:"custom",provider:"huggingface",model_id:"",is_available:!1,is_fallback:!1}),u()}else{const s=await e.json();alert(`Failed to add model: ${s.detail||"Unknown error"}`)}}catch{alert("Error adding model")}},Me=e=>{B(e),c({m_code:e.m_code,label:e.label,model_type:e.model_type||"custom",provider:e.provider||e.config?.provider||"huggingface",model_id:e.model_id||e.config?.model_id||e.m_code,is_available:e.is_available,is_fallback:e.is_fallback}),L(!0)},Se=async()=>{try{console.log("Updating model with data:",o);const e={label:o.label,model_type:o.model_type,provider:o.provider,model_id:o.model_id,is_available:o.is_available};if(console.log("Update payload:",e),!E){alert("No model selected for editing");return}const s=await fetch(`/api/admin/models/${E.m_code}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${localStorage.getItem("adminToken")}`},body:JSON.stringify(e)});if(console.log("Update response status:",s.status),s.ok){const i=await s.json();console.log("Update successful:",i),L(!1),B(null),c({m_code:"",label:"",model_type:"custom",provider:"huggingface",model_id:"",is_available:!1,is_fallback:!1}),console.log("Refreshing models..."),u()}else{const i=await s.json();console.error("Update failed:",i),alert(`Failed to update model: ${i.detail||"Unknown error"}`)}}catch(e){console.error("Update error:",e),alert("Error updating model")}},ke=async e=>{W(e),S(!0)},we=async()=>{try{const e=await fetch(`/api/admin/models/${K}`,{method:"DELETE",headers:{Authorization:`Bearer ${localStorage.getItem("adminToken")}`}});if(e.ok)S(!1),W(""),u();else{const s=await e.json();alert(`Failed to delete model: ${s.detail||"Unknown error"}`)}}catch{alert("Error deleting model")}},Fe=async e=>{if(e.preventDefault(),!F.trim()){j("Please enter a password");return}H(!0),j("");try{await ae(F)||j("Invalid password")}catch{j("Login failed. Please try again.")}finally{H(!1)}},Te=()=>{le(),R(""),j("")};return ee?a.jsx(D,{children:a.jsx("div",{className:"flex items-center justify-center min-h-[400px]",children:a.jsxs("div",{className:"text-center",children:[a.jsx("div",{className:"animate-spin rounded-full h-12 w-12 border-b-2 border-ifrcRed mx-auto mb-4"}),a.jsx("p",{className:"text-gray-600",children:"Loading..."})]})})}):w?a.jsxs(D,{children:[a.jsxs("div",{className:l.adminContainer,children:[a.jsx("div",{className:l.adminHeader,children:a.jsx(d,{name:"logout",variant:"secondary",onClick:Te,children:"Logout"})}),a.jsxs("div",{className:l.adminSection,children:[a.jsx(f,{heading:"VLM Model Selection",headingLevel:2,withHeaderBorder:!0,withInternalPadding:!0,children:a.jsxs("div",{className:l.modelSelectionArea,children:[a.jsx("p",{className:"text-gray-700",children:"Select which Vision Language Model to use for caption generation."}),a.jsxs("div",{className:l.modelSelectionRow,children:[a.jsx(b,{label:"Model",name:"selected-model",value:se,onChange:e=>ye(e||""),options:[{value:"random",label:"Random"},...T.filter(e=>e.is_available).map(e=>({value:e.m_code,label:e.label}))],keySelector:e=>e.value,labelSelector:e=>e.label}),a.jsx(b,{label:"Fallback",name:"fallback-model",value:oe,onChange:e=>Ne(e||""),options:[{value:"",label:"No fallback (use STUB_MODEL)"},...T.filter(e=>e.is_available).map(e=>({value:e.m_code,label:e.label}))],keySelector:e=>e.value,labelSelector:e=>e.label})]})]})}),a.jsx(f,{heading:"Model Management",headingLevel:2,withHeaderBorder:!0,withInternalPadding:!0,children:a.jsxs("div",{className:l.modelManagementArea,children:[a.jsx("div",{className:l.modelsTable,children:a.jsxs("table",{children:[a.jsx("thead",{children:a.jsxs("tr",{children:[a.jsx("th",{children:"Code"}),a.jsx("th",{children:"Label"}),a.jsx("th",{children:"Provider"}),a.jsx("th",{children:"Model ID"}),a.jsx("th",{children:"Available"}),a.jsx("th",{children:"Actions"})]})}),a.jsx("tbody",{children:T.map(e=>a.jsxs("tr",{children:[a.jsx("td",{className:l.modelCode,children:e.m_code}),a.jsx("td",{children:e.label}),a.jsx("td",{children:e.provider||e.config?.provider||"huggingface"}),a.jsx("td",{className:l.modelId,children:e.model_id||e.config?.model_id||e.m_code||"N/A"}),a.jsx("td",{children:a.jsx(d,{name:`toggle-${e.m_code}`,variant:e.is_available?"primary":"secondary",size:1,onClick:()=>be(e.m_code,e.is_available),children:e.is_available?"Enabled":"Disabled"})}),a.jsx("td",{children:a.jsxs("div",{className:l.modelActions,children:[a.jsx(d,{name:`edit-${e.m_code}`,variant:"secondary",size:1,onClick:()=>Me(e),children:"Edit"}),a.jsx(d,{name:`delete-${e.m_code}`,variant:"secondary",size:1,onClick:()=>ke(e.m_code),children:"Delete"})]})})]},e.m_code))})]})}),!V&&a.jsx("div",{className:l.addModelButtonContainer,children:a.jsx(d,{name:"show-add-form",variant:"primary",onClick:()=>$(!0),children:"Add New Model"})}),V&&a.jsxs("div",{className:l.addModelForm,children:[a.jsx("h4",{className:l.addModelFormTitle,children:"Add New Model"}),a.jsxs("div",{className:l.addModelFormGrid,children:[a.jsx("div",{className:l.addModelFormField,children:a.jsx(h,{label:"Model Code",name:"model-code",value:o.m_code,onChange:e=>c({...o,m_code:e||""}),placeholder:"e.g., NEW_MODEL_123"})}),a.jsx("div",{className:l.addModelFormField,children:a.jsx(h,{label:"Label",name:"model-label",value:o.label,onChange:e=>c({...o,label:e||""}),placeholder:"e.g., New Model Name"})}),a.jsx("div",{className:l.addModelFormField,children:a.jsx(b,{label:"Provider",name:"model-provider",value:o.provider,onChange:e=>c({...o,provider:e||"huggingface"}),options:[{value:"huggingface",label:"HuggingFace"},{value:"openai",label:"OpenAI"},{value:"google",label:"Google"}],keySelector:e=>e.value,labelSelector:e=>e.label})}),a.jsx("div",{className:l.addModelFormField,children:a.jsx(h,{label:"Model ID",name:"model-id",value:o.model_id,onChange:e=>c({...o,model_id:e||""}),placeholder:"e.g., org/model-name"})}),a.jsx("div",{className:l.addModelFormField,children:a.jsxs("div",{className:l.addModelFormCheckbox,children:[a.jsx("input",{type:"checkbox",checked:o.is_available,onChange:e=>c({...o,is_available:e.target.checked})}),a.jsx("span",{children:"Available for use"})]})})]}),a.jsxs("div",{className:l.addModelFormActions,children:[a.jsx(d,{name:"save-model",variant:"primary",onClick:Ce,disabled:!o.m_code||!o.label||!o.model_id,children:"Save Model"}),a.jsx(d,{name:"cancel-add",variant:"secondary",onClick:()=>$(!1),children:"Cancel"})]})]}),me&&a.jsxs("div",{className:l.addModelForm,children:[a.jsxs("h4",{className:l.addModelFormTitle,children:["Edit Model: ",E?.label]}),a.jsxs("div",{className:l.addModelFormGrid,children:[a.jsx("div",{className:l.addModelFormField,children:a.jsx(h,{label:"Model Code",name:"model-code",value:o.m_code,onChange:e=>c({...o,m_code:e||""}),placeholder:"e.g., NEW_MODEL_123",disabled:!0})}),a.jsx("div",{className:l.addModelFormField,children:a.jsx(h,{label:"Label",name:"model-label",value:o.label,onChange:e=>c({...o,label:e||""}),placeholder:"e.g., New Model Name"})}),a.jsx("div",{className:l.addModelFormField,children:a.jsx(b,{label:"Provider",name:"model-provider",value:o.provider,onChange:e=>c({...o,provider:e||"huggingface"}),options:[{value:"huggingface",label:"HuggingFace"},{value:"openai",label:"OpenAI"},{value:"google",label:"Google"}],keySelector:e=>e.value,labelSelector:e=>e.label})}),a.jsx("div",{className:l.addModelFormField,children:a.jsx(h,{label:"Model ID",name:"model-id",value:o.model_id,onChange:e=>c({...o,model_id:e||""}),placeholder:"e.g., org/model-name"})}),a.jsx("div",{className:l.addModelFormField,children:a.jsxs("div",{className:l.addModelFormCheckbox,children:[a.jsx("input",{type:"checkbox",checked:o.is_available,onChange:e=>c({...o,is_available:e.target.checked})}),a.jsx("span",{children:"Available for use"})]})})]}),a.jsxs("div",{className:l.addModelFormActions,children:[a.jsx(d,{name:"update-model",variant:"primary",onClick:Se,disabled:!o.m_code||!o.label||!o.model_id,children:"Update Model"}),a.jsx(d,{name:"cancel-edit",variant:"secondary",onClick:()=>{L(!1),B(null),c({m_code:"",label:"",model_type:"custom",provider:"huggingface",model_id:"",is_available:!1,is_fallback:!1})},children:"Cancel"})]})]})]})}),a.jsx(f,{heading:"Prompt Management",headingLevel:2,withHeaderBorder:!0,withInternalPadding:!0,children:a.jsxs("div",{className:l.modelManagementArea,children:[a.jsxs("div",{className:l.promptSubsection,children:[a.jsx("h4",{className:l.promptSubsectionTitle,children:"Crisis Maps"}),a.jsx("div",{className:l.modelsTable,children:a.jsxs("table",{children:[a.jsx("thead",{children:a.jsxs("tr",{children:[a.jsx("th",{children:"Code"}),a.jsx("th",{children:"Label"}),a.jsx("th",{children:"Status"}),a.jsx("th",{children:"Actions"})]})}),a.jsx("tbody",{children:G.filter(e=>e.image_type==="crisis_map").sort((e,s)=>e.p_code.localeCompare(s.p_code)).map(e=>a.jsxs("tr",{children:[a.jsx("td",{className:l.modelCode,children:e.p_code}),a.jsx("td",{className:l.promptLabel,children:e.label||"No label"}),a.jsx("td",{children:a.jsx(d,{name:`toggle-crisis-${e.p_code}`,variant:e.is_active?"primary":"secondary",size:1,onClick:()=>Q(e.p_code,"crisis_map"),children:e.is_active?"Active":"Inactive"})}),a.jsx("td",{children:a.jsxs("div",{className:l.modelActions,children:[a.jsx(d,{name:`view-${e.p_code}`,variant:"secondary",size:1,onClick:()=>{_(`=== Prompt Details ===
|
12 |
Code: ${e.p_code}
|
13 |
Label: ${e.label}
|
14 |
Image Type: ${e.image_type}
|
15 |
Active: ${e.is_active}
|
16 |
|
17 |
Metadata Instructions:
|
18 |
+
${e.metadata_instructions||"No instructions available"}`),x(`Prompt: ${e.p_code}`),p(!0)},children:"View"}),a.jsx(d,{name:`edit-${e.p_code}`,variant:"secondary",size:1,onClick:()=>Y(e),children:"Edit"})]})})]},e.p_code))})]})}),a.jsx("div",{className:l.addModelButtonContainer,children:a.jsx(d,{name:"add-crisis-prompt",variant:"primary",onClick:()=>X("crisis_map"),children:"Add New Crisis Map Prompt"})})]}),a.jsxs("div",{className:l.promptSubsection,children:[a.jsx("h4",{className:l.promptSubsectionTitle,children:"Drone Images"}),a.jsx("div",{className:l.modelsTable,children:a.jsxs("table",{children:[a.jsx("thead",{children:a.jsxs("tr",{children:[a.jsx("th",{children:"Code"}),a.jsx("th",{children:"Label"}),a.jsx("th",{children:"Status"}),a.jsx("th",{children:"Actions"})]})}),a.jsx("tbody",{children:G.filter(e=>e.image_type==="drone_image").sort((e,s)=>e.p_code.localeCompare(s.p_code)).map(e=>a.jsxs("tr",{children:[a.jsx("td",{className:l.modelCode,children:e.p_code}),a.jsx("td",{className:l.promptLabel,children:e.label||"No label"}),a.jsx("td",{children:a.jsx(d,{name:`toggle-drone-${e.p_code}`,variant:e.is_active?"primary":"secondary",size:1,onClick:()=>Q(e.p_code,"drone_image"),children:e.is_active?"Active":"Inactive"})}),a.jsx("td",{children:a.jsxs("div",{className:l.modelActions,children:[a.jsx(d,{name:`view-${e.p_code}`,variant:"secondary",size:1,onClick:()=>{_(`=== Prompt Details ===
|
19 |
Code: ${e.p_code}
|
20 |
Label: ${e.label}
|
21 |
Image Type: ${e.image_type}
|
22 |
Active: ${e.is_active}
|
23 |
|
24 |
Metadata Instructions:
|
25 |
+
${e.metadata_instructions||"No instructions available"}`),x(`Prompt: ${e.p_code}`),p(!0)},children:"View"}),a.jsx(d,{name:`edit-${e.p_code}`,variant:"secondary",size:1,onClick:()=>Y(e),children:"Edit"})]})})]},e.p_code))})]})}),a.jsx("div",{className:l.addModelButtonContainer,children:a.jsx(d,{name:"add-drone-prompt",variant:"primary",onClick:()=>X("drone_image"),children:"Add New Drone Image Prompt"})})]})]})}),a.jsx(f,{heading:"Utilities",headingLevel:2,withHeaderBorder:!0,withInternalPadding:!0,children:a.jsxs("div",{className:"flex flex-wrap gap-4",children:[a.jsx(d,{name:"test-connection",variant:"secondary",onClick:async()=>{_("Testing API connection..."),x("Connection Test Results");try{const e=await fetch("/api/models");if(e.ok){const s=await e.json(),i=`β
API connection successful!
|
26 |
|
27 |
Found ${s.models?.length||0} models in database.
|
28 |
|
29 |
Available models:
|
30 |
${s.models?.filter(m=>m.is_available).map(m=>`- ${m.label} (${m.m_code})`).join(`
|
31 |
+
`)||"None"}`;_(i)}else{const s=`β API connection failed: HTTP ${e.status}`;_(s)}p(!0)}catch(e){const s=`β Connection error: ${e}`;_(s),p(!0)}},children:"Test Connection"}),a.jsx(d,{name:"view-schemas",variant:"secondary",onClick:()=>{fetch("/api/schemas",{headers:{Authorization:`Bearer ${localStorage.getItem("adminToken")}`}}).then(e=>e.json()).then(e=>{console.log("Schemas Response:",e);let s="",i="Schemas Response";e&&Array.isArray(e)?(s=`Found ${e.length} schemas:
|
32 |
|
33 |
`,e.forEach((m,g)=>{s+=`=== Schema ${g+1} ===
|
34 |
`,s+=JSON.stringify(m,null,2),s+=`
|
|
|
42 |
${JSON.stringify(e,null,2)}`:s=`Prompts Response:
|
43 |
|
44 |
Unexpected data type: ${typeof e}
|
45 |
+
Value: ${e}`,_(s),x(i),p(!0)}).catch(e=>{console.error("Schemas Error:",e);const s=`Failed to fetch prompts: ${e.message||"Unknown error"}`;_(s),x("Schemas Error"),p(!0)})},children:"View Schemas"})]})})]})]}),he&&a.jsx("div",{className:l.modalOverlay,onClick:()=>S(!1),children:a.jsx("div",{className:l.modalContent,onClick:e=>e.stopPropagation(),children:a.jsxs("div",{className:l.modalBody,children:[a.jsx("h3",{className:l.modalTitle,children:"Delete Model"}),a.jsxs("p",{className:l.modalText,children:["Are you sure you want to delete model ",a.jsx("span",{className:l.modelCode,children:K}),"? This action cannot be undone."]}),a.jsxs("div",{className:l.modalButtons,children:[a.jsx(d,{name:"cancel-delete",variant:"tertiary",onClick:()=>S(!1),children:"Cancel"}),a.jsx(d,{name:"confirm-delete",variant:"secondary",onClick:we,children:"Delete"})]})]})})}),pe&&a.jsx("div",{className:l.modalOverlay,onClick:()=>O(!1),children:a.jsx("div",{className:l.modalContent,onClick:e=>e.stopPropagation(),children:a.jsxs("div",{className:l.modalBody,children:[a.jsx("h3",{className:l.modalTitle,children:"Model Added Successfully!"}),a.jsx("div",{className:`${l.modalText} ${l.modalTextLeft}`,children:ue}),a.jsx("div",{className:l.modalButtons,children:a.jsx(d,{name:"close-setup-instructions",variant:"secondary",onClick:()=>O(!1),children:"Got it!"})})]})})}),_e&&a.jsx("div",{className:l.modalOverlay,onClick:()=>p(!1),children:a.jsx("div",{className:l.modalContent,onClick:e=>e.stopPropagation(),children:a.jsxs("div",{className:l.modalBody,children:[a.jsx("h3",{className:l.modalTitle,children:ve}),a.jsx("div",{className:`${l.modalText} ${l.modalTextLeft}`,children:a.jsx("div",{className:"whitespace-pre-wrap font-mono text-sm leading-relaxed",children:xe})}),a.jsx("div",{className:l.modalButtons,children:a.jsx(d,{name:"close-test-results",variant:"secondary",onClick:()=>p(!1),children:"Close"})})]})})}),ne&&a.jsx("div",{className:l.modalOverlay,onClick:()=>N(!1),children:a.jsx("div",{className:l.modalContent,onClick:e=>e.stopPropagation(),children:a.jsxs("div",{className:l.modalBody,children:[a.jsxs("h3",{className:l.modalTitle,children:["Edit Prompt: ",M?.p_code]}),a.jsxs("div",{className:l.modalForm,children:[a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Code:"}),a.jsx(h,{name:"prompt-code",value:M?.p_code,onChange:()=>{},disabled:!0,className:l.formInput})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Label:"}),a.jsx(h,{name:"prompt-label",value:n.label,onChange:e=>r(s=>({...s,label:e||""})),className:l.formInput})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Image Type:"}),a.jsx(b,{name:"prompt-image-type",value:n.image_type,onChange:e=>r(s=>({...s,image_type:e||"crisis_map"})),options:de,keySelector:e=>e.image_type,labelSelector:e=>e.label})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Active Status:"}),a.jsxs("div",{className:l.addModelFormCheckbox,children:[a.jsx("input",{type:"checkbox",checked:n.is_active,onChange:e=>r(s=>({...s,is_active:e.target.checked}))}),a.jsx("span",{children:"Active for this image type"})]})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Metadata Instructions:"}),a.jsx("textarea",{name:"prompt-instructions",value:n.metadata_instructions,onChange:e=>r(s=>({...s,metadata_instructions:e.target.value})),className:`${l.formInput} ${l.textarea}`,rows:8})]})]}),a.jsxs("div",{className:l.modalButtons,children:[a.jsx(d,{name:"cancel-edit-prompt",variant:"tertiary",onClick:()=>{N(!1),P(null),r({p_code:"",label:"",metadata_instructions:"",image_type:"crisis_map",is_active:!1})},children:"Cancel"}),a.jsx(d,{name:"save-prompt",variant:"primary",onClick:ge,children:"Save Changes"})]})]})})}),re&&a.jsx("div",{className:l.modalOverlay,onClick:()=>C(!1),children:a.jsx("div",{className:l.modalContent,onClick:e=>e.stopPropagation(),children:a.jsxs("div",{className:l.modalBody,children:[a.jsxs("h3",{className:l.modalTitle,children:["Add New ",ce==="crisis_map"?"Crisis Map":"Drone Image"," Prompt"]}),a.jsxs("div",{className:l.modalForm,children:[a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Code:"}),a.jsx(h,{name:"prompt-code",value:n.p_code,onChange:e=>r(s=>({...s,p_code:e||""})),placeholder:"e.g., CUSTOM_CRISIS_MAP_001",className:l.formInput})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Label:"}),a.jsx(h,{name:"prompt-label",value:n.label,onChange:e=>r(s=>({...s,label:e||""})),placeholder:"Enter prompt description...",className:l.formInput})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Image Type:"}),a.jsx(h,{name:"prompt-image-type",value:n.image_type,onChange:()=>{},disabled:!0,className:l.formInput})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Active Status:"}),a.jsxs("div",{className:l.addModelFormCheckbox,children:[a.jsx("input",{type:"checkbox",checked:n.is_active,onChange:e=>r(s=>({...s,is_active:e.target.checked}))}),a.jsx("span",{children:"Active for this image type"})]})]}),a.jsxs("div",{className:l.formField,children:[a.jsx("label",{className:l.formLabel,children:"Metadata Instructions:"}),a.jsx("textarea",{name:"prompt-instructions",value:n.metadata_instructions,onChange:e=>r(s=>({...s,metadata_instructions:e.target.value})),placeholder:"Enter metadata extraction instructions...",className:`${l.formInput} ${l.textarea}`,rows:8})]})]}),a.jsxs("div",{className:l.modalButtons,children:[a.jsx(d,{name:"cancel-add-prompt",variant:"tertiary",onClick:()=>{C(!1),I(null),r({p_code:"",label:"",metadata_instructions:"",image_type:"crisis_map",is_active:!1})},children:"Cancel"}),a.jsx(d,{name:"save-new-prompt",variant:"primary",onClick:fe,disabled:!n.p_code||!n.label,children:"Create Prompt"})]})]})})})]}):a.jsxs(D,{children:[z&&a.jsxs("div",{className:Z.loadingContainer,children:[a.jsx(Ae,{className:"text-ifrcRed"}),a.jsx("p",{className:Z.loadingText,children:"Logging in..."})]}),a.jsxs("div",{className:"mx-auto max-w-md px-4 sm:px-6 lg:px-8 py-6 sm:py-10",children:[a.jsx("div",{className:"text-center mb-8",children:a.jsx(Ie,{level:2,children:"Admin Login"})}),a.jsxs("form",{onSubmit:Fe,className:"space-y-6",children:[a.jsxs("div",{children:[a.jsx("label",{htmlFor:"password",className:"block text-sm font-medium text-gray-700 mb-2",children:"Password"}),a.jsx(h,{id:"password",name:"password",type:"password",value:F,onChange:e=>R(e||""),placeholder:"Enter admin password",required:!0,className:"w-full"})]}),U&&a.jsx("div",{children:a.jsx("p",{className:"text-sm text-ifrcRed font-medium",children:U})}),a.jsx("div",{className:"flex justify-center",children:a.jsx(f,{withInternalPadding:!0,className:"p-2",children:a.jsx(d,{name:"login",type:"submit",variant:"primary",size:2,disabled:z,children:"Login"})})})]})]})]})}export{ha as default};
|
py_backend/static/assets/ExportModal-DJ-UV1s7.js
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
import{r as n,j as e,M as P,P as W,o as F,S as A,D as H,z as b,n as v,O as V,I as z,T as q,_ as O,L as G}from"./index-C_hAuRbb.js";const R=({title:c,titleId:i,...r})=>n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":i},r),c?n.createElement("title",{id:i},c):null,n.createElement("g",{clipPath:"url(#checkbox-indeterminate-line_svg__a)"},n.createElement("path",{d:"M4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Zm1 2v14h14V5H5Zm2 6h10v2H7v-2Z"})),n.createElement("defs",null,n.createElement("clipPath",{id:"checkbox-indeterminate-line_svg__a"},n.createElement("path",{d:"M0 0h24v24H0z"})))),$=({title:c,titleId:i,...r})=>n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":i},r),c?n.createElement("title",{id:i},c):null,n.createElement("g",{clipPath:"url(#filter-line_svg__a)"},n.createElement("path",{d:"M9 13.5 4 6H3V4h18v2h-1l-5 7.5V22H9v-8.5ZM6.404 6 11 12.894V20h2v-7.106L17.596 6H6.404Z"})),n.createElement("defs",null,n.createElement("clipPath",{id:"filter-line_svg__a"},n.createElement("path",{d:"M0 0h24v24H0z"}))));function D(c){const{className:i,indeterminate:r,value:j}=c;return e.jsxs(e.Fragment,{children:[r&&e.jsx(R,{className:i}),j&&!r&&e.jsx(P,{className:i}),!j&&!r&&e.jsx(W,{className:i})]})}const U="_checkbox_12g7n_1",Z="_with-background_12g7n_7",Y="_checkmark-container_12g7n_12",J="_input_12g7n_18",K="_content_12g7n_33",Q="_description_12g7n_40",X="_checked_12g7n_45",L="_checkmark_12g7n_12",ee="_disabled-checkbox_12g7n_58",m={checkbox:U,withBackground:Z,checkmarkContainer:Y,input:J,content:K,description:Q,checked:X,checkmark:L,disabledCheckbox:ee};function B(c){const{className:i,checkmark:r=D,checkmarkClassName:j,checkmarkContainerClassName:I,disabled:t,error:p,indeterminate:N,inputClassName:E,invertedLogic:d=!1,label:y,labelContainerClassName:_,name:w,onChange:f,readOnly:h,tooltip:k,value:x,description:u,withBackground:T,...S}=c,M=n.useCallback(s=>{const o=s.currentTarget.checked;f(d?!o:o,w)},[w,f,d]),C=d?!x:x,g=F(m.checkbox,i,!N&&C&&m.checked,T&&m.withBackground,t&&m.disabledCheckbox,h&&m.readOnly);return e.jsxs("label",{className:g,title:k,children:[e.jsxs("div",{className:F(m.checkmarkContainer,I),children:[e.jsx("input",{onChange:M,className:F(m.input,E),type:"checkbox",checked:C??!1,disabled:t||h,readOnly:h,...S}),e.jsx(r,{className:F(m.checkmark,j),value:C??!1,indeterminate:N,"aria-hidden":"true"})]}),(y||u)&&e.jsxs("div",{className:m.content,children:[y&&e.jsx("div",{className:_,children:y}),u&&e.jsx("div",{className:m.description,children:u})]}),p&&e.jsx(A,{children:p})]})}function _e({sources:c,types:i,regions:r,countries:j,imageTypes:I,isLoadingFilters:t=!1}){const[p,N]=n.useState(!1),{search:E,setSearch:d,srcFilter:y,setSrcFilter:_,catFilter:w,setCatFilter:f,regionFilter:h,setRegionFilter:k,countryFilter:x,setCountryFilter:u,imageTypeFilter:T,setImageTypeFilter:S,uploadTypeFilter:M,setUploadTypeFilter:C,showReferenceExamples:g,setShowReferenceExamples:s,clearAllFilters:o}=H();return e.jsxs("div",{className:"mb-6 space-y-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-4",children:[e.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:e.jsx(v,{name:"toggle-filters",variant:"secondary",onClick:()=>N(!p),className:"whitespace-nowrap",title:p?"Hide Filters":"Show Filters",children:e.jsx($,{className:"w-4 h-4"})})}),e.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2 flex-1 min-w-[300px]",children:e.jsx(V,{name:"search",placeholder:"Search examples...",value:E,onChange:a=>d(a||"")})}),e.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:e.jsx(v,{name:"clear-filters",variant:"secondary",onClick:o,children:"Clear Filters"})})]}),p&&e.jsx("div",{className:"bg-white/20 backdrop-blur-sm rounded-md p-4",children:e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-4",children:[e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(z,{name:"source",placeholder:t?"Loading...":"All Sources",options:c,value:y||null,onChange:a=>_(a||""),keySelector:a=>a.s_code,labelSelector:a=>a.label,required:!1,disabled:t})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(z,{name:"category",placeholder:t?"Loading...":"All Categories",options:i,value:w||null,onChange:a=>f(a||""),keySelector:a=>a.t_code,labelSelector:a=>a.label,required:!1,disabled:t})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(z,{name:"region",placeholder:t?"Loading...":"All Regions",options:r,value:h||null,onChange:a=>k(a||""),keySelector:a=>a.r_code,labelSelector:a=>a.label,required:!1,disabled:t})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(q,{name:"country",placeholder:t?"Loading...":"All Countries",options:j,value:x?[x]:[],onChange:a=>u(a[0]||""),keySelector:a=>a.c_code,labelSelector:a=>a.label,disabled:t})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(z,{name:"imageType",placeholder:t?"Loading...":"All Image Types",options:I,value:T||null,onChange:a=>S(a||""),keySelector:a=>a.image_type,labelSelector:a=>a.label,required:!1,disabled:t})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(z,{name:"uploadType",placeholder:"All Upload Types",options:[{key:"single",label:"Single Upload"},{key:"multiple",label:"Multiple Upload"}],value:M||null,onChange:a=>C(a||""),keySelector:a=>a.key,labelSelector:a=>a.label,required:!1,disabled:!1})})]})})]})}const ae="_fullSizeModalOverlay_cyz3b_1",le="_fullSizeModalContent_cyz3b_29",te="_ratingWarningContent_cyz3b_53",se="_ratingWarningTitle_cyz3b_65",ne="_exportModeSection_cyz3b_133",ie="_splitConfigSection_cyz3b_143",re="_splitConfigTitle_cyz3b_153",ce="_splitInputsContainer_cyz3b_167",oe="_splitInputGroup_cyz3b_183",de="_splitInputLabel_cyz3b_197",me="_splitInput_cyz3b_167",pe="_splitTotal_cyz3b_247",he="_splitTotalError_cyz3b_261",xe="_checkboxesContainer_cyz3b_271",ue="_ratingWarningButtons_cyz3b_289",ge="_singleExportMessage_cyz3b_309",be="_navigateButtonContainer_cyz3b_333",ve="_loadingOverlay_cyz3b_349",l={fullSizeModalOverlay:ae,fullSizeModalContent:le,ratingWarningContent:te,ratingWarningTitle:se,exportModeSection:ne,splitConfigSection:ie,splitConfigTitle:re,splitInputsContainer:ce,splitInputGroup:oe,splitInputLabel:de,splitInput:me,splitTotal:pe,splitTotalError:he,checkboxesContainer:xe,ratingWarningButtons:ue,singleExportMessage:ge,navigateButtonContainer:be,loadingOverlay:ve};function fe({isOpen:c,onClose:i,onExport:r,crisisMapsCount:j,droneImagesCount:I,isLoading:t=!1,exportSuccess:p=!1,variant:N="bulk",onNavigateAndExport:E}){const[d,y]=n.useState("standard"),[_,w]=n.useState(80),[f,h]=n.useState(10),[k,x]=n.useState(10),[u,T]=n.useState(!0),[S,M]=n.useState(!0),C=()=>{if(N==="single"){r(d,["crisis_map","drone_image"]);return}if(!u&&!S){alert("Please select at least one image type to export.");return}const s=[];u&&s.push("crisis_map"),S&&s.push("drone_image"),r(d,s)},g=()=>{i()};return c?N==="single"?e.jsx("div",{className:l.fullSizeModalOverlay,onClick:g,children:e.jsxs("div",{className:l.fullSizeModalContent,onClick:s=>s.stopPropagation(),children:[t&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx(O,{className:"text-ifrcRed"}),e.jsx("div",{className:"text-lg font-medium",children:"Exporting..."}),e.jsx("div",{className:"text-sm text-gray-600",children:"This might take a few seconds"})]})}),p&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx("div",{className:"text-lg font-medium",children:"Export Successful!"}),e.jsx("div",{className:"text-sm text-gray-600",children:"Your dataset has been downloaded"}),e.jsx(v,{name:"close-export-success",onClick:g,className:"mt-4",children:"Close"})]})}),e.jsxs("div",{className:l.ratingWarningContent,children:[e.jsx("h3",{className:l.ratingWarningTitle,children:"Export Single Item"}),e.jsxs("div",{className:l.singleExportMessage,children:[e.jsx("p",{children:"This only exports the 1 item currently on display."}),e.jsx("p",{children:'You may export the entire dataset from the "list view" here:'})]}),e.jsx("div",{className:l.navigateButtonContainer,children:e.jsx(v,{name:"navigate-to-list",variant:"secondary",onClick:E,children:"Navigate to List View"})}),e.jsxs("div",{className:l.ratingWarningButtons,children:[e.jsx(v,{name:"continue-export",onClick:C,disabled:t,children:t?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(O,{className:"text-white"}),"Exporting..."]}):"Continue"}),e.jsx(v,{name:"cancel-export",variant:"tertiary",onClick:g,disabled:t,children:"Cancel"})]})]})]})}):e.jsx("div",{className:l.fullSizeModalOverlay,onClick:g,children:e.jsxs("div",{className:l.fullSizeModalContent,onClick:s=>s.stopPropagation(),children:[t&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx(O,{className:"text-ifrcRed"}),e.jsx("div",{className:"text-lg font-medium",children:"Exporting..."}),e.jsx("div",{className:"text-sm text-gray-600",children:"This might take a few seconds"})]})}),p&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx("div",{className:"text-lg font-medium",children:"Export Successful!"}),e.jsx("div",{className:"text-sm text-gray-600",children:"Your dataset has been downloaded"}),e.jsx(v,{name:"close-export-success",onClick:g,className:"mt-4",children:"Close"})]})}),e.jsxs("div",{className:l.ratingWarningContent,children:[e.jsx("h3",{className:l.ratingWarningTitle,children:"Export Dataset"}),e.jsx("div",{className:l.exportModeSection,children:e.jsx(G,{name:"export-mode",value:d,onChange:s=>{(s==="standard"||s==="fine-tuning")&&y(s)},options:[{key:"standard",label:"Standard"},{key:"fine-tuning",label:"Fine-tuning"}],keySelector:s=>s.key,labelSelector:s=>s.label,disabled:t})}),d==="fine-tuning"&&e.jsxs("div",{className:l.splitConfigSection,children:[e.jsx("div",{className:l.splitConfigTitle,children:"Dataset Split Configuration"}),e.jsxs("div",{className:l.splitInputsContainer,children:[e.jsxs("div",{className:l.splitInputGroup,children:[e.jsx("label",{htmlFor:"train-split",className:l.splitInputLabel,children:"Train (%)"}),e.jsx("input",{id:"train-split",type:"number",min:"0",max:"100",value:_,onChange:s=>{const o=parseInt(s.target.value)||0,a=100-o;a>=0&&(w(o),f+k>a&&(h(Math.floor(a/2)),x(a-Math.floor(a/2))))},className:l.splitInput,disabled:t})]}),e.jsxs("div",{className:l.splitInputGroup,children:[e.jsx("label",{htmlFor:"test-split",className:l.splitInputLabel,children:"Test (%)"}),e.jsx("input",{id:"test-split",type:"number",min:"0",max:"100",value:f,onChange:s=>{const o=parseInt(s.target.value)||0,a=100-_-o;a>=0&&(h(o),x(a))},className:l.splitInput,disabled:t})]}),e.jsxs("div",{className:l.splitInputGroup,children:[e.jsx("label",{htmlFor:"val-split",className:l.splitInputLabel,children:"Val (%)"}),e.jsx("input",{id:"val-split",type:"number",min:"0",max:"100",value:k,onChange:s=>{const o=parseInt(s.target.value)||0,a=100-_-o;a>=0&&(x(o),h(a))},className:l.splitInput,disabled:t})]})]}),_+f+k!==100&&e.jsx("div",{className:l.splitTotal,children:e.jsx("span",{className:l.splitTotalError,children:"Must equal 100%"})})]}),e.jsxs("div",{className:l.checkboxesContainer,children:[e.jsx("div",{className:"flex items-center gap-3",children:e.jsx(B,{name:"crisis-maps",label:`Crisis Maps (${j} images)`,value:u,onChange:s=>T(s),disabled:t})}),e.jsx("div",{className:"flex items-center gap-3",children:e.jsx(B,{name:"drone-images",label:`Drone Images (${I} images)`,value:S,onChange:s=>M(s),disabled:t})})]}),e.jsxs("div",{className:l.ratingWarningButtons,children:[e.jsx(v,{name:"confirm-export",onClick:C,disabled:t,children:t?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(O,{className:"text-white"}),"Exporting..."]}):"Export Selected"}),e.jsx(v,{name:"cancel-export",variant:"tertiary",onClick:g,disabled:t,children:"Cancel"})]})]})]})}):null}export{fe as E,_e as F};
|
|
|
|
py_backend/static/assets/ExportModal-Qf6Y7VAO.js
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
import{r as n,j as e,P as V,S as A,o as O,T as H,D as q,z as b,n as v,O as D,J as F,V as G,_ as B,L as R}from"./index-BCz4bkWK.js";const $=({title:c,titleId:i,...r})=>n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":i},r),c?n.createElement("title",{id:i},c):null,n.createElement("g",{clipPath:"url(#checkbox-indeterminate-line_svg__a)"},n.createElement("path",{d:"M4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Zm1 2v14h14V5H5Zm2 6h10v2H7v-2Z"})),n.createElement("defs",null,n.createElement("clipPath",{id:"checkbox-indeterminate-line_svg__a"},n.createElement("path",{d:"M0 0h24v24H0z"})))),U=({title:c,titleId:i,...r})=>n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":i},r),c?n.createElement("title",{id:i},c):null,n.createElement("g",{clipPath:"url(#filter-line_svg__a)"},n.createElement("path",{d:"M9 13.5 4 6H3V4h18v2h-1l-5 7.5V22H9v-8.5ZM6.404 6 11 12.894V20h2v-7.106L17.596 6H6.404Z"})),n.createElement("defs",null,n.createElement("clipPath",{id:"filter-line_svg__a"},n.createElement("path",{d:"M0 0h24v24H0z"}))));function Z(c){const{className:i,indeterminate:r,value:j}=c;return e.jsxs(e.Fragment,{children:[r&&e.jsx($,{className:i}),j&&!r&&e.jsx(V,{className:i}),!j&&!r&&e.jsx(A,{className:i})]})}const Y="_checkbox_12g7n_1",J="_with-background_12g7n_7",K="_checkmark-container_12g7n_12",Q="_input_12g7n_18",X="_content_12g7n_33",L="_description_12g7n_40",ee="_checked_12g7n_45",ae="_checkmark_12g7n_12",le="_disabled-checkbox_12g7n_58",h={checkbox:Y,withBackground:J,checkmarkContainer:K,input:Q,content:X,description:L,checked:ee,checkmark:ae,disabledCheckbox:le};function P(c){const{className:i,checkmark:r=Z,checkmarkClassName:j,checkmarkContainerClassName:z,disabled:s,error:x,indeterminate:k,inputClassName:I,invertedLogic:p=!1,label:_,labelContainerClassName:f,name:E,onChange:C,readOnly:u,tooltip:S,value:N,description:g,withBackground:T,...w}=c,M=n.useCallback(t=>{const o=t.currentTarget.checked;C(p?!o:o,E)},[E,C,p]),y=p?!N:N,m=O(h.checkbox,i,!k&&y&&h.checked,T&&h.withBackground,s&&h.disabledCheckbox,u&&h.readOnly);return e.jsxs("label",{className:m,title:S,children:[e.jsxs("div",{className:O(h.checkmarkContainer,z),children:[e.jsx("input",{onChange:M,className:O(h.input,I),type:"checkbox",checked:y??!1,disabled:s||u,readOnly:u,...w}),e.jsx(r,{className:O(h.checkmark,j),value:y??!1,indeterminate:k,"aria-hidden":"true"})]}),(_||g)&&e.jsxs("div",{className:h.content,children:[_&&e.jsx("div",{className:f,children:_}),g&&e.jsx("div",{className:h.description,children:g})]}),x&&e.jsx(H,{children:x})]})}function Ce({sources:c,types:i,regions:r,countries:j,imageTypes:z,isLoadingFilters:s=!1}){const[x,k]=n.useState(!1),[I,p]=n.useState(""),{search:_,setSearch:f,srcFilter:E,setSrcFilter:C,catFilter:u,setCatFilter:S,regionFilter:N,setRegionFilter:g,countryFilter:T,setCountryFilter:w,imageTypeFilter:M,setImageTypeFilter:y,uploadTypeFilter:m,setUploadTypeFilter:t,showReferenceExamples:o,setShowReferenceExamples:d,clearAllFilters:W}=q();return n.useEffect(()=>{p(_)},[_]),e.jsxs("div",{className:"mb-6 space-y-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-4",children:[e.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:e.jsx(v,{name:"toggle-filters",variant:"secondary",onClick:()=>k(!x),className:"whitespace-nowrap",title:x?"Hide Filters":"Show Filters",children:e.jsx(U,{className:"w-4 h-4"})})}),e.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2 flex-1 min-w-[300px]",children:e.jsx(D,{name:"search",placeholder:"Search",value:I,onChange:a=>p(a||""),onKeyDown:a=>{a.key==="Enter"&&f(I)}})}),e.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:e.jsx(v,{name:"clear-filters",variant:"secondary",onClick:W,children:"Clear Filters"})})]}),x&&e.jsx("div",{className:"bg-white/20 backdrop-blur-sm rounded-md p-4",children:e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-4",children:[e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(F,{name:"source",placeholder:s?"Loading...":"All Sources",options:c,value:E||null,onChange:a=>C(a||""),keySelector:a=>a.s_code,labelSelector:a=>a.label,required:!1,disabled:s})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(F,{name:"category",placeholder:s?"Loading...":"All Categories",options:i,value:u||null,onChange:a=>S(a||""),keySelector:a=>a.t_code,labelSelector:a=>a.label,required:!1,disabled:s})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(F,{name:"region",placeholder:s?"Loading...":"All Regions",options:r,value:N||null,onChange:a=>g(a||""),keySelector:a=>a.r_code,labelSelector:a=>a.label,required:!1,disabled:s})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(G,{name:"country",placeholder:s?"Loading...":"All Countries",options:j,value:T?[T]:[],onChange:a=>w(a[0]||""),keySelector:a=>a.c_code,labelSelector:a=>a.label,disabled:s})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(F,{name:"imageType",placeholder:s?"Loading...":"All Image Types",options:z,value:M||null,onChange:a=>y(a||""),keySelector:a=>a.image_type,labelSelector:a=>a.label,required:!1,disabled:s})}),e.jsx(b,{withInternalPadding:!0,className:"p-2",children:e.jsx(F,{name:"uploadType",placeholder:"All Upload Types",options:[{key:"single",label:"Single Upload"},{key:"multiple",label:"Multiple Upload"}],value:m||null,onChange:a=>t(a||""),keySelector:a=>a.key,labelSelector:a=>a.label,required:!1,disabled:!1})})]})})]})}const te="_fullSizeModalOverlay_cyz3b_1",se="_fullSizeModalContent_cyz3b_29",ne="_ratingWarningContent_cyz3b_53",ie="_ratingWarningTitle_cyz3b_65",re="_exportModeSection_cyz3b_133",ce="_splitConfigSection_cyz3b_143",oe="_splitConfigTitle_cyz3b_153",de="_splitInputsContainer_cyz3b_167",pe="_splitInputGroup_cyz3b_183",me="_splitInputLabel_cyz3b_197",he="_splitInput_cyz3b_167",xe="_splitTotal_cyz3b_247",ue="_splitTotalError_cyz3b_261",ge="_checkboxesContainer_cyz3b_271",be="_ratingWarningButtons_cyz3b_289",ve="_singleExportMessage_cyz3b_309",je="_navigateButtonContainer_cyz3b_333",_e="_loadingOverlay_cyz3b_349",l={fullSizeModalOverlay:te,fullSizeModalContent:se,ratingWarningContent:ne,ratingWarningTitle:ie,exportModeSection:re,splitConfigSection:ce,splitConfigTitle:oe,splitInputsContainer:de,splitInputGroup:pe,splitInputLabel:me,splitInput:he,splitTotal:xe,splitTotalError:ue,checkboxesContainer:ge,ratingWarningButtons:be,singleExportMessage:ve,navigateButtonContainer:je,loadingOverlay:_e};function Ne({isOpen:c,onClose:i,onExport:r,crisisMapsCount:j,droneImagesCount:z,isLoading:s=!1,exportSuccess:x=!1,variant:k="bulk",onNavigateAndExport:I}){const[p,_]=n.useState("standard"),[f,E]=n.useState(80),[C,u]=n.useState(10),[S,N]=n.useState(10),[g,T]=n.useState(!0),[w,M]=n.useState(!0),y=()=>{if(k==="single"){r(p,["crisis_map","drone_image"]);return}if(!g&&!w){alert("Please select at least one image type to export.");return}const t=[];g&&t.push("crisis_map"),w&&t.push("drone_image"),r(p,t)},m=()=>{i()};return c?k==="single"?e.jsx("div",{className:l.fullSizeModalOverlay,onClick:m,children:e.jsxs("div",{className:l.fullSizeModalContent,onClick:t=>t.stopPropagation(),children:[s&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx(B,{className:"text-ifrcRed"}),e.jsx("div",{className:"text-lg font-medium",children:"Exporting..."}),e.jsx("div",{className:"text-sm text-gray-600",children:"This might take a few seconds"})]})}),x&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx("div",{className:"text-lg font-medium",children:"Export Successful!"}),e.jsx("div",{className:"text-sm text-gray-600",children:"Your dataset has been downloaded"}),e.jsx(v,{name:"close-export-success",onClick:m,className:"mt-4",children:"Close"})]})}),e.jsxs("div",{className:l.ratingWarningContent,children:[e.jsx("h3",{className:l.ratingWarningTitle,children:"Export Single Item"}),e.jsxs("div",{className:l.singleExportMessage,children:[e.jsx("p",{children:"This only exports the 1 item currently on display."}),e.jsx("p",{children:'You may export the entire dataset from the "list view" here:'})]}),e.jsx("div",{className:l.navigateButtonContainer,children:e.jsx(v,{name:"navigate-to-list",variant:"secondary",onClick:I,children:"Navigate to List View"})}),e.jsxs("div",{className:l.ratingWarningButtons,children:[e.jsx(v,{name:"continue-export",onClick:y,disabled:s,children:s?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(B,{className:"text-white"}),"Exporting..."]}):"Continue"}),e.jsx(v,{name:"cancel-export",variant:"tertiary",onClick:m,disabled:s,children:"Cancel"})]})]})]})}):e.jsx("div",{className:l.fullSizeModalOverlay,onClick:m,children:e.jsxs("div",{className:l.fullSizeModalContent,onClick:t=>t.stopPropagation(),children:[s&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx(B,{className:"text-ifrcRed"}),e.jsx("div",{className:"text-lg font-medium",children:"Exporting..."}),e.jsx("div",{className:"text-sm text-gray-600",children:"This might take a few seconds"})]})}),x&&e.jsx("div",{className:l.loadingOverlay,children:e.jsxs("div",{className:"flex flex-col items-center gap-4",children:[e.jsx("div",{className:"text-lg font-medium",children:"Export Successful!"}),e.jsx("div",{className:"text-sm text-gray-600",children:"Your dataset has been downloaded"}),e.jsx(v,{name:"close-export-success",onClick:m,className:"mt-4",children:"Close"})]})}),e.jsxs("div",{className:l.ratingWarningContent,children:[e.jsx("h3",{className:l.ratingWarningTitle,children:"Export Dataset"}),e.jsx("div",{className:l.exportModeSection,children:e.jsx(R,{name:"export-mode",value:p,onChange:t=>{(t==="standard"||t==="fine-tuning")&&_(t)},options:[{key:"standard",label:"Standard"},{key:"fine-tuning",label:"Fine-tuning"}],keySelector:t=>t.key,labelSelector:t=>t.label,disabled:s})}),p==="fine-tuning"&&e.jsxs("div",{className:l.splitConfigSection,children:[e.jsx("div",{className:l.splitConfigTitle,children:"Dataset Split Configuration"}),e.jsxs("div",{className:l.splitInputsContainer,children:[e.jsxs("div",{className:l.splitInputGroup,children:[e.jsx("label",{htmlFor:"train-split",className:l.splitInputLabel,children:"Train (%)"}),e.jsx("input",{id:"train-split",type:"number",min:"0",max:"100",value:f,onChange:t=>{const o=parseInt(t.target.value)||0,d=100-o;d>=0&&(E(o),C+S>d&&(u(Math.floor(d/2)),N(d-Math.floor(d/2))))},className:l.splitInput,disabled:s})]}),e.jsxs("div",{className:l.splitInputGroup,children:[e.jsx("label",{htmlFor:"test-split",className:l.splitInputLabel,children:"Test (%)"}),e.jsx("input",{id:"test-split",type:"number",min:"0",max:"100",value:C,onChange:t=>{const o=parseInt(t.target.value)||0,d=100-f-o;d>=0&&(u(o),N(d))},className:l.splitInput,disabled:s})]}),e.jsxs("div",{className:l.splitInputGroup,children:[e.jsx("label",{htmlFor:"val-split",className:l.splitInputLabel,children:"Val (%)"}),e.jsx("input",{id:"val-split",type:"number",min:"0",max:"100",value:S,onChange:t=>{const o=parseInt(t.target.value)||0,d=100-f-o;d>=0&&(N(o),u(d))},className:l.splitInput,disabled:s})]})]}),f+C+S!==100&&e.jsx("div",{className:l.splitTotal,children:e.jsx("span",{className:l.splitTotalError,children:"Must equal 100%"})})]}),e.jsxs("div",{className:l.checkboxesContainer,children:[e.jsx("div",{className:"flex items-center gap-3",children:e.jsx(P,{name:"crisis-maps",label:`Crisis Maps (${j} images)`,value:g,onChange:t=>T(t),disabled:s})}),e.jsx("div",{className:"flex items-center gap-3",children:e.jsx(P,{name:"drone-images",label:`Drone Images (${z} images)`,value:w,onChange:t=>M(t),disabled:s})})]}),e.jsxs("div",{className:l.ratingWarningButtons,children:[e.jsx(v,{name:"confirm-export",onClick:y,disabled:s,children:s?e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(B,{className:"text-white"}),"Exporting..."]}):"Export Selected"}),e.jsx(v,{name:"cancel-export",variant:"tertiary",onClick:m,disabled:s,children:"Cancel"})]})]})]})}):null}export{Ne as E,Ce as F};
|
py_backend/static/assets/{index-D91JzoMl.js β index-4L12dHQs.js}
RENAMED
@@ -1 +1 @@
|
|
1 |
-
import{r as s,y as nt,t as ht,a as q,c as gt,j as e,o as N,b as se,R as st,z,g as rt,d as ft,m as vt,e as pt,n as Z,A as xt,f as _t,h as Ct,i as yt,k as he,l as bt,p as ye,q as jt,s as Nt,E as wt,C as St,U as Mt,Q as It,u as Dt,N as je,_ as Et,L as Tt}from"./index-C_hAuRbb.js";const kt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("g",{clipPath:"url(#arrow-drop-down-line_svg__a)"},s.createElement("path",{d:"m12 15-4.243-4.243 1.415-1.414L12 12.172l2.828-2.83 1.415 1.415L12 15Z"})),s.createElement("defs",null,s.createElement("clipPath",{id:"arrow-drop-down-line_svg__a"},s.createElement("path",{d:"M0 0h24v24H0z"})))),Lt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("g",{clipPath:"url(#arrow-drop-up-line_svg__a)"},s.createElement("path",{d:"m12 11.828-2.828 2.829-1.415-1.414L12 9l4.243 4.243-1.415 1.414L12 11.828Z"})),s.createElement("defs",null,s.createElement("clipPath",{id:"arrow-drop-up-line_svg__a"},s.createElement("path",{d:"M0 0h24v24H0z"})))),Pt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("g",{clipPath:"url(#information-line_svg__a)"},s.createElement("path",{d:"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10Zm0-2a8 8 0 1 0 0-16.001A8 8 0 0 0 12 20ZM11 7h2v2h-2V7Zm0 4h2v6h-2v-6Z"})),s.createElement("defs",null,s.createElement("clipPath",{id:"information-line_svg__a"},s.createElement("path",{d:"M0 0h24v24H0z"})))),Rt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("path",{fillRule:"evenodd",d:"m15.063 12 .937.938-4 4-4-4L8.938 12 12 15.063 15.063 12Z",clipRule:"evenodd"}),s.createElement("mask",{id:"table-sorting-line_svg__a",width:8,height:5,x:8,y:12,maskUnits:"userSpaceOnUse",style:{maskType:"luminance"}},s.createElement("path",{fillRule:"evenodd",d:"m15.063 12 .937.938-4 4-4-4L8.938 12 12 15.063 15.063 12Z",clipRule:"evenodd"})),s.createElement("g",{mask:"url(#table-sorting-line_svg__a)"},s.createElement("path",{d:"M-24-22h72v72h-72z"})),s.createElement("path",{fillRule:"evenodd",d:"M8.938 11 8 10.062l4-4 4 4-.938.938L12 7.937 8.937 11Z",clipRule:"evenodd"}),s.createElement("mask",{id:"table-sorting-line_svg__b",width:8,height:5,x:8,y:6,maskUnits:"userSpaceOnUse",style:{maskType:"luminance"}},s.createElement("path",{fillRule:"evenodd",d:"M8.938 11 8 10.062l4-4 4 4-.938.938L12 7.937 8.937 11Z",clipRule:"evenodd"})),s.createElement("g",{mask:"url(#table-sorting-line_svg__b)"},s.createElement("path",{d:"M48 45h-72v-72h72z"}))),$t="_number-output_1blvi_1",Bt={numberOutput:$t};function we(m){const{className:a,invalidText:h=nt,separatorHidden:r,compact:f,currency:g,value:b,tooltip:_,unit:y,prefix:x,suffix:w,maximumFractionDigits:C=1}=m,{currentLanguage:j}=s.useContext(ht),v=s.useMemo(()=>{if(q(b))return h;const S=gt(b,{currency:g,compact:f,separatorHidden:r,maximumFractionDigits:C,unit:y,language:j});return e.jsxs(e.Fragment,{children:[x,S,w]})},[h,b,f,r,g,y,C,x,j,w]);return e.jsx("div",{className:N(Bt.numberOutput,a),title:se(_)?String(_):void 0,children:v})}const At="_tooltip-dummy_rbf3f_1",Ft="_tooltip-content_rbf3f_7",Ot="_pointer_rbf3f_14",Ne={tooltipDummy:At,tooltipContent:Ft,pointer:Ot};function Ht(m){const{className:a,title:h,description:r,preferredWidth:f}=m,[g,b]=s.useState(!1),[_,y]=s.useState(!1),x=s.useRef(),w=s.useRef(null);return s.useEffect(()=>{const C=()=>{y(!0)},j=()=>{y(!1)};if(q(w.current))return;const{current:{parentNode:v}}=w;if(!q(v))return x.current=v,v.addEventListener("mouseover",C),v.addEventListener("mouseout",j),b(!0),()=>{v.removeEventListener("mouseover",C),v.removeEventListener("mouseout",j)}},[]),e.jsxs(e.Fragment,{children:[!g&&e.jsx("div",{className:Ne.tooltipDummy,ref:w}),_&&e.jsx(st,{className:N(Ne.tooltipContent,a),parentRef:x,pointerClassName:Ne.pointer,preferredWidth:f,children:e.jsx(z,{heading:h,withInternalPadding:!0,contentViewType:"vertical",children:r})})]})}function $(m){return m.id}const Vt="common",zt={booleanYesLabel:"Yes",booleanNoLabel:"No"},Ut={namespace:Vt,strings:zt},Wt="_boolean-output_kg1uq_1",Qt={booleanOutput:Wt};function Zt(m){const{className:a,invalidText:h,value:r}=m,f=rt(Ut);let g;return r===!0?g=f.booleanYesLabel:r===!1?g=f.booleanNoLabel:g=h,e.jsx("div",{className:N(Qt.booleanOutput,a),children:g})}const qt="_date-output_4jzjo_1",Gt={dateOutput:qt};function Yt(m){const{value:a,format:h,className:r,invalidText:f}=m,g=s.useMemo(()=>ft(a,h),[a,h]);return e.jsx("div",{className:N(Gt.dateOutput,r),children:g??f})}const Kt="_dropdown-menu_16hml_1",Xt="_icons_16hml_4",Jt="_content_16hml_5",ea="_actions_16hml_6",ta="_dropdown-icon_16hml_10",aa="_dropdown-content_16hml_16",le={dropdownMenu:Kt,icons:Xt,content:Jt,actions:ea,dropdownIcon:ta,dropdownContent:aa};function na(m){const a=s.useRef(null),{className:h,popupClassName:r,children:f,label:g,activeClassName:b,icons:_,variant:y="secondary",actions:x,withoutDropdownIcon:w,componentRef:C,elementRef:j=a,persistent:v,preferredPopupWidth:S}=m,p=s.useRef(null),[k,I]=s.useState(!1);s.useEffect(()=>{C&&(C.current={setShowDropdown:I})},[C,I]);const R=s.useCallback(()=>{I(re=>!re)},[I]),T=s.useCallback((re,ae)=>{ae||re&&v||I(!1)},[I,v]);vt(k,T,p,j);const G=s.useMemo(()=>({setShowDropdown:I}),[I]),U=!!x||!w;return e.jsxs(pt.Provider,{value:G,children:[e.jsx(Z,{name:void 0,className:N(le.dropdownMenu,k&&b,h),elementRef:j,onClick:R,variant:y,actionsContainerClassName:le.actions,iconsContainerClassName:le.icons,childrenContainerClassName:le.content,actions:U?e.jsxs(e.Fragment,{children:[x,!w&&(k?e.jsx(xt,{className:le.dropdownIcon}):e.jsx(_t,{className:le.dropdownIcon}))]}):void 0,icons:_,children:g}),k&&e.jsx(st,{elementRef:p,className:N(le.dropdownContent,r),parentRef:j,preferredWidth:S,children:f})]})}const sa="_info-popup_i3rna_1",ra="_label_i3rna_2",ia="_icon_i3rna_7",oa="_dropdown-container_i3rna_15",la="_content_i3rna_20",ge={infoPopup:sa,label:ra,icon:ia,dropdownContainer:oa,content:la};function ca(m){const{className:a,icon:h=e.jsx(Pt,{}),infoLabel:r,title:f,description:g,withoutIcon:b,popupClassName:_,descriptionClassName:y}=m;return e.jsx(na,{label:e.jsxs("div",{className:ge.label,children:[r,!b&&h&&e.jsx("div",{className:ge.icon,children:h})]}),popupClassName:N(ge.dropdownContainer,_),className:N(ge.infoPopup,a),variant:"tertiary",withoutDropdownIcon:!0,children:e.jsx(z,{heading:f,childrenContainerClassName:N(y,ge.content),withInternalPadding:!0,children:g})})}const da="_progress-wrapper_x340w_1",ua="_title_x340w_7",ma="_total_x340w_11",ha="_progress_x340w_1",fe={progressWrapper:da,title:ua,total:ma,progress:ha};function Ye(m){const{className:a,title:h,description:r,totalValue:f,value:g,showPercentageInTitle:b,children:_,color:y="var(--go-ui-color-primary-red)"}=m,x=se(g)?g:0,w=se(f)?f:0;let C;return w===0?C=0:C=x/w*100,e.jsxs("div",{className:N(fe.progressWrapper,a),children:[(h||b)&&e.jsxs("div",{className:fe.title,children:[h,b&&e.jsx(we,{value:C,suffix:"%"})]}),e.jsx("div",{className:fe.total,children:e.jsx("div",{className:fe.progress,style:{width:`${C}%`,backgroundColor:y}})}),r&&e.jsx("div",{className:fe.description,children:r}),_]})}const ga="_legend-element_1a9ic_1",fa="_color_1a9ic_7",va="_icon-container_1a9ic_14",pa="_icon_1a9ic_14",xa="_label_1a9ic_31",ve={legendElement:ga,color:fa,iconContainer:va,icon:pa,label:xa};function _a(m){const{className:a,colorClassName:h,iconClassName:r,color:f,label:g,iconSrc:b}=m;return e.jsxs("div",{className:N(ve.legendElement,a),children:[b?e.jsx("div",{style:{backgroundColor:f},className:ve.iconContainer,children:e.jsx("img",{className:N(ve.icon,r),src:b,alt:""})}):e.jsx("div",{style:{backgroundColor:f},className:N(ve.color,h)}),e.jsx("div",{className:ve.label,children:g})]})}const Ca="_text-output_10oza_1",ya="_with-background_10oza_6",ba="_label_10oza_11",ja="_with-colon_10oza_12",Na="_value_10oza_17",wa="_text-type_10oza_18",Sa="_strong_10oza_24",ee={textOutput:Ca,withBackground:ya,label:ba,withColon:ja,value:Na,textType:wa,strong:Sa};function Ke(m){const{className:a,label:h,icon:r,description:f,labelClassName:g,descriptionClassName:b,valueClassName:_,strongLabel:y,strongValue:x,strongDescription:w,withoutLabelColon:C,withBackground:j,invalidText:v=nt,...S}=m,{value:p}=m;let k=v;return S.valueType==="number"?k=e.jsx(we,{...S,invalidText:v}):S.valueType==="date"?k=e.jsx(Yt,{...S,invalidText:v}):S.valueType==="boolean"?k=e.jsx(Zt,{...S,invalidText:v}):p instanceof Date||(k=p||v),e.jsxs("div",{className:N(ee.textOutput,j&&ee.withBackground,a),children:[r,h&&e.jsx("div",{className:N(ee.label,y&&ee.strong,g,!C&&ee.withColon),children:h}),e.jsx("div",{className:N(ee.value,x&&ee.strong,S.valueType==="text"&&ee.textType,_),children:k}),f&&e.jsx("div",{className:N(ee.description,w&&ee.strong,b),children:f})]})}const Ma="_pie-chart_pyr7m_1",Ia="_legend_pyr7m_7",Da="_legend-item_pyr7m_13",pe={pieChart:Ma,legend:Ia,legendItem:Da},Ea=70,Ta=40;function Xe(m,a=1){return Math.round(m*10**a)/10**a}function Je(m,a){const h=(a-90)*Math.PI/180;return{x:Xe(m+m*Math.cos(h)),y:Xe(m+m*Math.sin(h))}}function ka(m,a,h){let r=h;const f=r-a===360;f&&(r-=1);const g=Je(m,a),b=Je(m,r),_=r-a<=180?0:1,y=["M",g.x,g.y,"A",m,m,0,_,1,b.x,b.y];return f?y.push("Z"):y.push("L",m,m,"L",g.x,g.y,"Z"),y.join(" ")}function xe(m){const{className:a,data:h,valueSelector:r,labelSelector:f,keySelector:g,colorSelector:b,colors:_,pieRadius:y=Ea,chartPadding:x=Ta,legendClassName:w,showPercentageInLegend:C}=m,j=Ct(h?.map(p=>r(p))),v=q(j)||j===0?1:j,S=s.useMemo(()=>{let p=0;const k=h?.map(I=>{const R=r(I);if(q(R))return;const T=360*(R/v);return p+=T,{key:g(I),value:R,label:f(I),startAngle:p-T,percentage:yt(R,v),endAngle:p,datum:I}}).filter(se)??[];return b?k.map(({datum:I,...R})=>({...R,color:b(I)})):k.map(({datum:I,...R},T)=>({...R,color:_[T%_.length]}))},[h,g,r,f,v,b,_]);return e.jsxs("div",{className:N(pe.pieChart,a),children:[e.jsx("svg",{className:pe.svg,style:{width:`${x+y*2}px`,height:`${x+y*2}px`},children:e.jsx("g",{style:{transform:`translate(${x/2}px, ${x/2}px)`},children:S.map(p=>e.jsx("path",{className:pe.path,d:ka(y,p.startAngle,p.endAngle),fill:p.color,children:e.jsx(Ht,{description:e.jsx(Ke,{label:p.label,value:p.value})})},p.key))})}),e.jsx("div",{className:N(pe.legend,w),children:S.map(p=>e.jsx(_a,{className:pe.legendItem,label:C?e.jsx(Ke,{label:p.label,value:p.percentage,valueType:"number",prefix:"(",suffix:"%)",withoutLabelColon:!0}):p.label,color:p.color},p.key))})]})}const La="_td_1k4cn_1",Pa={td:La};function Ra(m){const{className:a,children:h,...r}=m;return e.jsx("td",{className:N(a,Pa.td),...r,children:h})}function it(m){const{className:a,children:h,...r}=m;return e.jsx("tr",{className:a,...r,children:h})}const $a="_row_1829z_1",Ba="_cell_1829z_2",et={row:$a,cell:Ba};function Aa(m){const{data:a,keySelector:h,columns:r,rowClassName:f,cellClassName:g,rowModifier:b}=m;return e.jsx(e.Fragment,{children:a?.map((_,y)=>{const x=h(_,y),w=r.map(v=>{const{id:S,cellRenderer:p,cellRendererClassName:k,cellRendererParams:I,cellContainerClassName:R}=v,T=I(x,_,y,a),G=e.jsx(p,{className:k,...T,name:S});return e.jsx(Ra,{className:N(et.cell,R,typeof g=="function"?g(x,_,S):g),children:G},S)}),C=e.jsx(it,{className:N(et.row,typeof f=="function"?f(x,_):f),children:w});let j=C;return b&&(j=b({rowKey:x,row:C,cells:w,columns:r,datum:_})),e.jsx(s.Fragment,{children:j},x)})})}const Fa="_th_cdv41_1",Oa="_resize-handle_cdv41_8",tt={th:Fa,resizeHandle:Oa};function Ha(m){const{className:a,children:h,onResize:r,onResizeComplete:f,name:g,...b}=m,_=s.useRef(null),y=s.useRef(),x=s.useRef(),w=s.useRef(),C=s.useCallback(v=>{var S;if(se(y.current)&&_.current&&r){v.preventDefault(),v.stopPropagation();const p=v.clientX-y.current;if(se(x.current)){const k=x.current+p;w.current=k,r(k,g)}else x.current=(S=_.current)==null?void 0:S.offsetWidth}},[r,g]),j=s.useCallback(v=>{var S;v.preventDefault(),y.current=v.clientX,x.current=(S=_.current)==null?void 0:S.offsetWidth,window.addEventListener("mousemove",C,!0)},[C]);return s.useEffect(()=>{const v=()=>{y.current=void 0,x.current=void 0,f&&se(w.current)&&f(w.current,g),window.removeEventListener("mousemove",C,!0)};return window.addEventListener("mouseup",v,!0),()=>{window.removeEventListener("mouseup",v,!0),window.removeEventListener("mousemove",C,!0)}},[C,g,f]),e.jsxs("th",{ref:_,className:N(a,tt.th),...b,children:[r&&e.jsx("div",{role:"presentation",className:tt.resizeHandle,onMouseDown:j}),h]})}const Va="_table_nilhy_1",za="_table-overflow-wrapper_nilhy_8",Ua="_table-element_nilhy_13",Wa="_header-row_nilhy_23",Qa="_header-element_nilhy_24",Za="_header-component_nilhy_29",ce={table:Va,tableOverflowWrapper:za,tableElement:Ua,headerRow:Wa,headerElement:Qa,headerComponent:Za};function qa(m,a){return a??m.columnWidth??wt}function B(m){const{data:a,keySelector:h,columns:r,caption:f,className:g,captionClassName:b,headerRowClassName:_,headerCellClassName:y,rowClassName:x,cellClassName:w,rowModifier:C,fixedColumnWidth:j,resizableColumn:v,headersHidden:S,pending:p,filtered:k,errored:I=!1}=m,R=s.useRef(null),[T]=he.useState(()=>bt()),[G,U]=he.useState({});s.useEffect(()=>{U(W=>{if(q(R.current))return W;const E=R.current.getBoundingClientRect(),{width:O}=E;let A=r.map(M=>({id:M.id,stretch:!!M.columnStretch,width:qa(M,W[M.id])}));const X=ye(A.filter(M=>M.stretch).map(M=>M.width)),ne=ye(A.filter(M=>!M.stretch).map(M=>M.width)),Y=(O-ne)/X;return Y>1&&(A=A.map(M=>({...M,width:M.stretch?M.width*Y:M.width}))),jt(A,M=>M.id,M=>M.width)})},[r]);const re=he.useCallback((W,E)=>{const O=document.getElementById(`${T}-${E}`),A=Math.max(W,80);if(q(O)||(O.style.width=`${A}px`,!j))return;const X=document.getElementById(T);if(q(X))return;const ne=ye(r.map(Y=>Y.id===E?A:G[Y.id]));X.style.width=`${ne}px`},[T,G,r,j]),ae=he.useCallback((W,E)=>{se(E)&&U(O=>({...O,[E]:Math.max(W,80)}))},[U]),be=he.useMemo(()=>ye(r.map(W=>G[W.id])),[G,r]),K=q(a)||a.length===0||Object.keys(G).length===0;return e.jsxs("div",{ref:R,className:N(ce.table,g),children:[!K&&e.jsx("div",{className:ce.tableOverflowWrapper,children:e.jsxs("table",{className:ce.tableElement,style:j?{width:`${be}px`}:void 0,id:T,children:[f&&e.jsx("caption",{className:b,children:f}),e.jsx("colgroup",{children:r.map(W=>{const{id:E,columnClassName:O}=W,A=G[E],X=j?{width:`${A}px`}:void 0;return e.jsx("col",{id:`${T}-${E}`,style:X,className:N(ce.column,O)},E)})}),!S&&e.jsx("thead",{children:e.jsx(it,{className:N(ce.headerRow,_),children:r.map((W,E)=>{const{id:O,title:A,headerCellRenderer:X,headerCellRendererClassName:ne,headerCellRendererParams:Y,headerContainerClassName:M}=W,ie=e.jsx(X,{...Y,name:O,title:A,index:E,className:N(ne,ce.headerComponent)});return e.jsx(Ha,{scope:"col",name:O,onResize:v?re:void 0,onResizeComplete:v?ae:void 0,className:N(ce.headerElement,typeof y=="function"?y(O):y,M),children:ie},O)})})}),e.jsx("tbody",{children:e.jsx(Aa,{data:a,keySelector:h,columns:r,rowClassName:x,cellClassName:w,rowModifier:C})})]})}),e.jsx(Nt,{filtered:k,empty:K,errored:I,pending:p,overlayPending:!0})]})}function Ga(m){const{className:a,value:h}=m;return q(h)?null:e.jsx("div",{className:a,children:h})}const Ya="common",Ka={sortTableButtonTitle:"Sort Table"},Xa={namespace:Ya,strings:Ka},Ja="_header-cell_vn24d_1",en="_sort-button_vn24d_8",tn="_icon_vn24d_12",an="_info-popup-icon_vn24d_17",de={headerCell:Ja,sortButton:en,icon:tn,infoPopupIcon:an};function ot(m){const{className:a,titleClassName:h,title:r,name:f,sortable:g,defaultSortDirection:b="asc",infoTitle:_,infoDescription:y}=m,{sorting:x,setSorting:w}=s.useContext(St),C=rt(Xa),j=x?.name===f?x.direction:void 0,v=s.useRef(null),S=s.useCallback(()=>{if(q(w))return;let p;q(j)?p=b:j==="asc"?p="dsc":j==="dsc"&&(p="asc"),w(p?{name:f,direction:p}:void 0)},[f,w,j,b]);return e.jsxs("div",{ref:v,className:N(a,de.headerCell),children:[g&&e.jsxs(Z,{name:void 0,variant:"tertiary",onClick:S,title:C.sortTableButtonTitle,className:de.sortButton,children:[q(j)&&e.jsx(Rt,{className:de.icon}),j==="asc"&&e.jsx(Lt,{className:de.icon}),j==="dsc"&&e.jsx(kt,{className:de.icon})]}),e.jsx("div",{className:N(h,de.title),children:r}),_&&y&&e.jsx(ca,{className:de.infoPopupIcon,title:_,description:y})]})}const at={};function Q(m,a,h,r){return{id:m,title:a,columnClassName:r?.columnClassName,headerCellRenderer:ot,headerCellRendererClassName:r?.headerCellRendererClassName,headerContainerClassName:r?.headerContainerClassName,headerCellRendererParams:{sortable:r?.sortable,infoTitle:r?.headerInfoTitle,infoDescription:r?.headerInfoDescription},cellRendererClassName:r?.cellRendererClassName,cellContainerClassName:r?.cellContainerClassName,cellRenderer:Ga,cellRendererParams:(f,g)=>({value:h(g)||"--"}),valueSelector:h,valueComparator:(f,g)=>Mt(h(f),h(g)),columnWidth:r?.columnWidth,columnStretch:r?.columnStretch,columnStyle:r?.columnStyle}}function D(m,a,h,r){return{id:m,title:a,columnClassName:r?.columnClassName,headerCellRenderer:ot,headerCellRendererClassName:N(at.numberCellHeader,r?.headerCellRendererClassName),headerContainerClassName:r?.headerContainerClassName,headerCellRendererParams:{sortable:r?.sortable,infoTitle:r?.headerInfoTitle,infoDescription:r?.headerInfoDescription},cellRendererClassName:N(at.numberCell,r?.cellRendererClassName),cellContainerClassName:r?.cellContainerClassName,cellRenderer:we,cellRendererParams:(f,g)=>({value:h(g),suffix:r?.suffix,maximumFractionDigits:r?.maximumFractionDigits,invalidText:"--"}),valueSelector:h,valueComparator:(f,g)=>It(h(f),h(g)),columnWidth:r?.columnWidth,columnStretch:r?.columnStretch,columnStyle:r?.columnStyle}}const nn="_tabSelector_vlxoe_1",sn="_progressSection_vlxoe_14",rn="_progressLabel_vlxoe_20",on="_chartGrid_vlxoe_28",ln="_chartContainer_vlxoe_40",cn="_tableContainer_vlxoe_51",dn="_modelPerformance_vlxoe_59",un="_loadingContainer_vlxoe_67",mn="_errorContainer_vlxoe_77",hn="_userInteractionCards_vlxoe_96",gn="_userInteractionCard_vlxoe_96",fn="_userInteractionCardValue_vlxoe_116",vn="_userInteractionCardLabel_vlxoe_123",pn="_userInteractionCardButton_vlxoe_130",xn="_summaryStatsCards_vlxoe_148",_n="_summaryStatsCard_vlxoe_148",Cn="_summaryStatsCardValue_vlxoe_169",yn="_summaryStatsCardLabel_vlxoe_176",c={tabSelector:nn,progressSection:sn,progressLabel:rn,chartGrid:on,chartContainer:ln,tableContainer:cn,modelPerformance:dn,loadingContainer:un,errorContainer:mn,userInteractionCards:hn,userInteractionCard:gn,userInteractionCardValue:fn,userInteractionCardLabel:vn,userInteractionCardButton:pn,summaryStatsCards:xn,summaryStatsCard:_n,summaryStatsCardValue:Cn,summaryStatsCardLabel:yn};function jn(){const[m]=Dt(),[a,h]=s.useState(null),[r,f]=s.useState(!0),[g,b]=s.useState("crisis_maps"),[_,y]=s.useState([]),[x,w]=s.useState([]),[C,j]=s.useState([]),[v,S]=s.useState([]),[p,k]=s.useState(!1),[I,R]=s.useState(!1),[T,G]=s.useState(!1),[U,re]=s.useState(!1),[ae,be]=s.useState(!1),[K,W]=s.useState(!1),E=t=>{k(t==="editTime"),R(t==="percentage"),G(t==="delete"),re(t==="regions"),be(t==="sources"),W(t==="types")},O=[{key:"crisis_maps",label:"Crisis Maps"},{key:"drone_images",label:"Drone Images"}],A=s.useCallback((t,l)=>{if(!t||!l)return 0;const i=t.toLowerCase().replace(/[^\w\s]/g,"").split(/\s+/).filter(u=>u.length>0),n=l.toLowerCase().replace(/[^\w\s]/g,"").split(/\s+/).filter(u=>u.length>0);if(i.length===0&&n.length===0)return 1;if(i.length===0||n.length===0)return 0;const o=new Set(i),d=new Set(n),P=new Set([...o].filter(u=>d.has(u))),F=new Set([...o,...d]);return P.size/F.size},[]),X=s.useCallback(async()=>{f(!0);try{const l=await(await fetch("/api/images")).json(),i={},n=l.filter(u=>u.image_type==="crisis_map"),o=l.filter(u=>u.image_type==="drone_image"),d={totalCaptions:l.length,sources:{},types:{},regions:{},models:{},modelEditTimes:i,percentageModified:0,modelPercentageData:{},totalDeleteCount:0,deleteRate:0,crisisMaps:n,droneImages:o};l.forEach(u=>{if(u.source&&(d.sources[u.source]=(d.sources[u.source]||0)+1),u.event_type&&(d.types[u.event_type]=(d.types[u.event_type]||0)+1),u.countries&&u.countries.forEach(L=>{L.r_code&&(d.regions[L.r_code]=(d.regions[L.r_code]||0)+1)}),u.model){const L=u.model,V=d.models[L]||={count:0,avgAccuracy:0,avgContext:0,avgUsability:0,totalScore:0,deleteCount:0};if(V.count++,u.accuracy!=null&&(V.avgAccuracy+=u.accuracy),u.context!=null&&(V.avgContext+=u.context),u.usability!=null&&(V.avgUsability+=u.usability),u.created_at&&u.updated_at){const te=new Date(u.created_at).getTime(),Ce=new Date(u.updated_at).getTime()-te;Ce>0&&(i[L]||(i[L]=[]),i[L].push(Ce))}}}),_.forEach(u=>{u.s_code&&!d.sources[u.s_code]&&(d.sources[u.s_code]=0)}),x.forEach(u=>{u.t_code&&!d.types[u.t_code]&&(d.types[u.t_code]=0)}),C.forEach(u=>{u.r_code&&!d.regions[u.r_code]&&(d.regions[u.r_code]=0)}),["GPT-4","Claude","Gemini","Llama","Other"].forEach(u=>{d.models[u]||(d.models[u]={count:0,avgAccuracy:0,avgContext:0,avgUsability:0,totalScore:0,deleteCount:0})}),Object.values(d.models).forEach(u=>{u.count>0&&(u.avgAccuracy=Math.round(u.avgAccuracy/u.count),u.avgContext=Math.round(u.avgContext/u.count),u.avgUsability=Math.round(u.avgUsability/u.count),u.totalScore=Math.round((u.avgAccuracy+u.avgContext+u.avgUsability)/3))});const F=l.filter(u=>u.generated&&u.edited);if(F.length>0){const L=[...F.map(oe=>A(oe.generated,oe.edited))].sort((oe,Ce)=>oe-Ce),V=Math.floor(L.length/2),te=L.length%2===0?(L[V-1]+L[V])/2:L[V];d.percentageModified=Math.round((1-te)*100)}const H={};l.forEach(u=>{if(u.model&&u.generated&&u.edited){const L=A(u.generated,u.edited),V=Math.round((1-L)*100);H[u.model]||(H[u.model]=[]),H[u.model].push(V)}}),d.modelPercentageData=H;try{const u=await fetch("/api/models");if(u.ok){const L=await u.json();if(L.models){L.models.forEach(te=>{d.models[te.m_code]&&(d.models[te.m_code].deleteCount=te.delete_count||0)});const V=L.models.reduce((te,oe)=>te+(oe.delete_count||0),0);d.totalDeleteCount=V,d.deleteRate=V>0?Math.round(V/(V+l.length)*100):0}}}catch(u){console.log("Could not fetch model delete counts:",u)}h(d)}catch{h(null)}finally{f(!1)}},[_,x,C,A]),ne=s.useCallback(async()=>{try{const[t,l,i,n]=await Promise.all([fetch("/api/sources"),fetch("/api/types"),fetch("/api/regions"),fetch("/api/models")]),o=await t.json(),d=await l.json(),P=await i.json(),F=await n.json();y(o),w(d),j(P),S(F.models||[])}catch(t){console.log("Could not fetch lookup data:",t)}},[]);s.useEffect(()=>{const t=m.get("view");(t==="crisis_maps"||t==="drone_images")&&b(t)},[m]),s.useEffect(()=>{ne()},[ne]),s.useEffect(()=>{_.length>0&&x.length>0&&C.length>0&&v.length>0&&X()},[_,x,C,v,X]);const Y=s.useCallback(t=>{const l=_.find(i=>i.s_code===t);return l?l.label:t},[_]),M=s.useCallback(t=>{if(t.length===0)return 0;const l=[...t].sort((n,o)=>n-o),i=Math.floor(l.length/2);return l.length%2===0?Math.round((l[i-1]+l[i])/2):l[i]},[]),ie=s.useCallback(t=>{const l=Math.floor(t/1e3),i=Math.floor(l/60),n=Math.floor(i/60);return n>0?`${n}h ${i%60}m`:i>0?`${i}m ${l%60}s`:`${l}s`},[]),_e=s.useCallback(t=>{const l=x.find(i=>i.t_code===t);return l?l.label:t},[x]),J=s.useCallback(t=>{const l=v.find(i=>i.m_code===t);return l?l.label:t},[v]),Se=s.useMemo(()=>a?Object.entries(a.modelEditTimes||{}).filter(([,t])=>t.length>0).sort(([,t],[,l])=>M(l)-M(t)).map(([t,l],i)=>({id:i+1,name:J(t),count:l.length,avgEditTime:M(l),minEditTime:Math.min(...l),maxEditTime:Math.max(...l)})):[],[a,M,J]),Me=s.useMemo(()=>a?Object.entries(a.modelPercentageData||{}).filter(([,t])=>t.length>0).sort(([,t],[,l])=>{const i=[...t].sort((H,u)=>H-u),n=[...l].sort((H,u)=>H-u),o=Math.floor(i.length/2),d=Math.floor(n.length/2),P=i.length%2===0?(i[o-1]+i[o])/2:i[o];return(n.length%2===0?(n[d-1]+n[d])/2:n[d])-P}).map(([t,l],i)=>{const n=[...l].sort((P,F)=>P-F),o=Math.floor(n.length/2),d=n.length%2===0?Math.round((n[o-1]+n[o])/2):n[o];return{id:i+1,name:J(t),count:l.length,avgPercentageModified:d,minPercentageModified:Math.min(...l),maxPercentageModified:Math.max(...l)}}):[],[a,J]),Ie=s.useMemo(()=>a?Object.entries(a.models).filter(([,t])=>t.count>0).map(([t,l],i)=>{const n=[l.avgAccuracy,l.avgContext,l.avgUsability],o=n.reduce((F,H)=>F+H,0)/n.length,d=n.reduce((F,H)=>F+Math.pow(H-o,2),0)/n.length,P=Math.round(100-Math.sqrt(d));return{id:i+1,name:J(t),consistency:Math.max(0,P),avgScore:Math.round(o),count:l.count}}).sort((t,l)=>l.consistency-t.consistency):[],[a,J]),De=s.useMemo(()=>[Q("name","Region",t=>t.name),D("count","Count",t=>t.count),D("percentage","% of Total",t=>t.percentage,{suffix:"%",maximumFractionDigits:0})],[]),Ee=s.useMemo(()=>[Q("name","Type",t=>t.name),D("count","Count",t=>t.count),D("percentage","% of Total",t=>t.percentage,{suffix:"%",maximumFractionDigits:0})],[]),lt=s.useMemo(()=>[Q("name","Source",t=>t.name),D("count","Count",t=>t.count),D("percentage","% of Total",t=>t.percentage,{suffix:"%",maximumFractionDigits:0})],[]),Te=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Count",t=>t.count),D("accuracy","Accuracy",t=>t.accuracy,{suffix:"%",maximumFractionDigits:0}),D("context","Context",t=>t.context,{suffix:"%",maximumFractionDigits:0}),D("usability","Usability",t=>t.usability,{suffix:"%",maximumFractionDigits:0}),D("totalScore","Total Score",t=>t.totalScore,{suffix:"%",maximumFractionDigits:0})],[]),ke=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Count",t=>t.count),Q("avgEditTime","Median Edit Time",t=>ie(t.avgEditTime)),Q("minEditTime","Min Edit Time",t=>ie(t.minEditTime)),Q("maxEditTime","Max Edit Time",t=>ie(t.maxEditTime))],[]),Le=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Count",t=>t.count),D("avgPercentageModified","Median % Modified",t=>t.avgPercentageModified,{suffix:"%",maximumFractionDigits:0}),D("minPercentageModified","Min % Modified",t=>t.minPercentageModified,{suffix:"%",maximumFractionDigits:0}),D("maxPercentageModified","Max % Modified",t=>t.maxPercentageModified,{suffix:"%",maximumFractionDigits:0})],[]),Pe=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Total Count",t=>t.count),D("deleteCount","Delete Count",t=>t.deleteCount),D("deleteRate","Delete Rate",t=>t.deleteRate,{suffix:"%",maximumFractionDigits:1})],[]),ct=s.useMemo(()=>[Q("source","Source",t=>t.source),D("avgQuality","Average Quality",t=>t.avgQuality,{suffix:"%",maximumFractionDigits:0}),D("count","Count",t=>t.count)],[]),Re=s.useMemo(()=>[Q("eventType","Event Type",t=>t.eventType),D("avgQuality","Average Quality",t=>t.avgQuality,{suffix:"%",maximumFractionDigits:0}),D("count","Count",t=>t.count)],[]),$e=s.useMemo(()=>[Q("name","Model",t=>t.name),D("consistency","Consistency",t=>t.consistency,{suffix:"%",maximumFractionDigits:0}),D("avgScore","Average Score",t=>t.avgScore,{suffix:"%",maximumFractionDigits:0}),D("count","Count",t=>t.count)],[]),ue=s.useCallback(t=>a?t==="crisis_map"?a.crisisMaps.length:t==="drone_image"?a.droneImages.length:0:0,[a]),Be=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.countries&&n.countries.forEach(o=>{o.r_code&&(i[o.r_code]=(i[o.r_code]||0)+1)})}),Object.entries(i).filter(([,n])=>n>0).map(([n,o])=>({name:C.find(d=>d.r_code===n)?.label||n,value:o}))},[a,C]),Ae=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};l.forEach(o=>{o.countries&&o.countries.forEach(d=>{d.r_code&&(i[d.r_code]=(i[d.r_code]||0)+1)})});const n=C.reduce((o,d)=>(d.r_code&&(o[d.r_code]={name:d.label,count:i[d.r_code]||0}),o),{});return Object.entries(n).sort(([,o],[,d])=>d.count-o.count).map(([,{name:o,count:d}],P)=>({id:P+1,name:o,count:d,percentage:l.length>0?Math.round(d/l.length*100):0}))},[a,C]),dt=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.source&&(i[n.source]=(i[n.source]||0)+1)}),Object.entries(i).filter(([,n])=>n>0).map(([n,o])=>({name:_.find(d=>d.s_code===n)?.label||n,value:o}))},[a,_]),ut=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.source&&(i[n.source]=(i[n.source]||0)+1)}),Object.entries(i).sort(([,n],[,o])=>o-n).map(([n,o],d)=>({id:d+1,name:Y(n),count:o,percentage:l.length>0?Math.round(o/l.length*100):0}))},[a,Y]),Fe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.event_type&&(i[n.event_type]=(i[n.event_type]||0)+1)}),Object.entries(i).filter(([,n])=>n>0).map(([n,o])=>({name:x.find(d=>d.t_code===n)?.label||n,value:o}))},[a,x]),Oe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.event_type&&(i[n.event_type]=(i[n.event_type]||0)+1)}),Object.entries(i).sort(([,n],[,o])=>o-n).map(([n,o],d)=>({id:d+1,name:_e(n),count:o,percentage:l.length>0?Math.round(o/l.length*100):0}))},[a,_e]),He=s.useCallback(t=>{if(!a)return"No data available";const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;l.forEach(d=>{d.model&&i.add(d.model)}),console.log(`Debug ${t}:`,{totalImages:l.length,usedModels:Array.from(i),availableEditTimes:Object.keys(a.modelEditTimes),modelEditTimesData:a.modelEditTimes});const o=Object.entries(a.modelEditTimes).filter(([d])=>i.has(d)).flatMap(([,d])=>d);return o.length===0?"No data available":ie(M(o))},[a,ie,M]),Ve=s.useCallback(()=>{if(!a)return"No data available";const t=a.totalCaptions||0,l=a.percentageModified||0;return t>0?Math.round(l/t*100):0},[a]),ze=s.useCallback(()=>a&&a.deleteRate>=0?`${a.deleteRate}%`:"No data available",[a]),Ue=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;return l.forEach(o=>{o.model&&i.add(o.model)}),Se.filter(o=>{const d=v.find(P=>P.label===o.name)?.m_code;return d&&i.has(d)})},[a,Se,v]),We=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;return l.forEach(o=>{o.model&&i.add(o.model)}),Me.filter(o=>{const d=v.find(P=>P.label===o.name)?.m_code;return d&&i.has(d)})},[a,Me,v]),Qe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.model&&(i[n.model]||(i[n.model]={count:0,deleteCount:0}),i[n.model].count++)}),Object.entries(i).map(([n,o],d)=>{const F=a.models?.[n]?.deleteCount||0,H=o.count>0?Math.round(F/o.count*100*10)/10:0;return{id:d+1,name:J(n),count:o.count,deleteCount:F,deleteRate:H}}).sort((n,o)=>o.count-n.count)},[a,J]),Ze=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.model&&(i[n.model]||(i[n.model]={count:0,totalAccuracy:0,totalContext:0,totalUsability:0}),i[n.model].count++,n.accuracy!=null&&(i[n.model].totalAccuracy+=n.accuracy),n.context!=null&&(i[n.model].totalContext+=n.context),n.usability!=null&&(i[n.model].totalUsability+=n.usability))}),Object.entries(i).map(([n,o],d)=>({id:d+1,name:J(n),count:o.count,accuracy:o.count>0?Math.round(o.totalAccuracy/o.count):0,context:o.count>0?Math.round(o.totalContext/o.count):0,usability:o.count>0?Math.round(o.totalUsability/o.count):0,totalScore:o.count>0?Math.round((o.totalAccuracy+o.totalContext+o.totalUsability)/(3*o.count)):0})).sort((n,o)=>o.totalScore-n.totalScore)},[a,J]),mt=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.source&&(i[n.source]||(i[n.source]={total:0,count:0,totalImages:0}),i[n.source].totalImages+=1,n.accuracy!=null&&(i[n.source].total+=n.accuracy,i[n.source].count+=1))}),Object.entries(i).map(([n,o],d)=>({id:d+1,source:Y(n),avgQuality:o.count>0?Math.round(o.total/o.count):0,count:o.totalImages}))},[a,Y]),qe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.event_type&&(i[n.event_type]||(i[n.event_type]={total:0,count:0,totalImages:0}),i[n.event_type].totalImages+=1,n.accuracy!=null&&(i[n.event_type].total+=n.accuracy,i[n.event_type].count+=1))}),Object.entries(i).map(([n,o],d)=>({id:d+1,eventType:_e(n),avgQuality:o.count>0?Math.round(o.total/o.count):0,count:o.totalImages}))},[a,_e]),Ge=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;return l.forEach(o=>{o.model&&i.add(o.model)}),Ie.filter(o=>{const d=v.find(P=>P.label===o.name)?.m_code;return d&&i.has(d)})},[a,Ie,v]);if(r)return e.jsx(je,{children:e.jsx("div",{className:c.loadingContainer,children:e.jsx(Et,{})})});if(!a)return e.jsx(je,{children:e.jsx("div",{className:c.errorContainer,children:e.jsx("div",{className:"text-red-500",children:"Failed to load analytics data. Please try again."})})});const me=["#F5333F","#F64752","#F75C65","#F87079","#F9858C","#FA999F","#FBADB2","#FCC2C5"];return e.jsx(je,{children:e.jsxs("div",{className:"max-w-7xl mx-auto",children:[e.jsx("div",{className:c.tabSelector,children:e.jsx(Tt,{name:"analytics-view",value:g,onChange:t=>{(t==="crisis_maps"||t==="drone_images")&&b(t)},options:O,keySelector:t=>t.key,labelSelector:t=>t.label})}),g==="crisis_maps"?e.jsxs("div",{className:c.chartGrid,children:[e.jsxs(z,{heading:"Summary Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.summaryStatsCards,children:[e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:ue("crisis_map")}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Total Crisis Maps"})]}),e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:"2000"}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Target Amount"})]})]}),e.jsxs("div",{className:c.progressSection,children:[e.jsxs("div",{className:c.progressLabel,children:[e.jsx("span",{children:"Progress towards target"}),e.jsxs("span",{children:[Math.round(ue("crisis_map")/2e3*100),"%"]})]}),e.jsx(Ye,{value:ue("crisis_map"),totalValue:2e3})]})]}),e.jsxs(z,{heading:"Distribution Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Regions Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Be("crisis_map"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-regions-details",variant:U?"primary":"secondary",onClick:()=>E(U?"none":"regions"),className:c.userInteractionCardButton,children:U?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Sources Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:dt("crisis_map"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-sources-details",variant:ae?"primary":"secondary",onClick:()=>E(ae?"none":"sources"),className:c.userInteractionCardButton,children:ae?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Types Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Fe("crisis_map"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-types-details",variant:K?"primary":"secondary",onClick:()=>E(K?"none":"types"),className:c.userInteractionCardButton,children:K?"Hide Details":"View Details"})]})]}),U&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ae("crisis_map"),columns:De,keySelector:$,filtered:!1,pending:!1})}),ae&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:ut("crisis_map"),columns:lt,keySelector:$,filtered:!1,pending:!1})}),K&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Oe("crisis_map"),columns:Ee,keySelector:$,filtered:!1,pending:!1})})]}),e.jsxs(z,{heading:"User Interaction Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:He("crisis_map")}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median Edit Time"}),e.jsx(Z,{name:"view-edit-time-details",variant:p?"primary":"secondary",onClick:()=>E(p?"none":"editTime"),className:c.userInteractionCardButton,children:p?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:Ve()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median % Modified"}),e.jsx(Z,{name:"view-percentage-details",variant:I?"primary":"secondary",onClick:()=>E(I?"none":"percentage"),className:c.userInteractionCardButton,children:I?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:ze()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Delete Rate"}),e.jsx(Z,{name:"view-delete-details",variant:T?"primary":"secondary",onClick:()=>E(T?"none":"delete"),className:c.userInteractionCardButton,children:T?"Hide Details":"View Details"})]})]}),p&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ue("crisis_map"),columns:ke,keySelector:$,filtered:!1,pending:!1})}),I&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:We("crisis_map"),columns:Le,keySelector:$,filtered:!1,pending:!1})}),T&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Qe("crisis_map"),columns:Pe,keySelector:$,filtered:!1,pending:!1})})]}),e.jsx(z,{heading:"Model Performance",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ze("crisis_map"),columns:Te,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Quality-Source Correlation",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:mt("crisis_map"),columns:ct,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Quality-Event Type Correlation",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:qe("crisis_map"),columns:Re,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Model Consistency Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:Ge("crisis_map"),columns:$e,keySelector:$,filtered:!1,pending:!1})})})]}):e.jsxs("div",{className:c.chartGrid,children:[e.jsxs(z,{heading:"Summary Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.summaryStatsCards,children:[e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:ue("drone_image")}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Total Drone Images"})]}),e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:"2000"}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Target Amount"})]})]}),e.jsxs("div",{className:c.progressSection,children:[e.jsxs("div",{className:c.progressLabel,children:[e.jsx("span",{children:"Progress towards target"}),e.jsxs("span",{children:[Math.round(ue("drone_image")/2e3*100),"%"]})]}),e.jsx(Ye,{value:ue("drone_image"),totalValue:2e3})]})]}),e.jsxs(z,{heading:"Distribution Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Regions Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Be("drone_image"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-regions-details",variant:U?"primary":"secondary",onClick:()=>E(U?"none":"regions"),className:c.userInteractionCardButton,children:U?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Types Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Fe("drone_image"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-types-details",variant:K?"primary":"secondary",onClick:()=>E(K?"none":"types"),className:c.userInteractionCardButton,children:K?"Hide Details":"View Details"})]})]}),U&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ae("drone_image"),columns:De,keySelector:$,filtered:!1,pending:!1})}),K&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Oe("drone_image"),columns:Ee,keySelector:$,filtered:!1,pending:!1})})]}),e.jsxs(z,{heading:"User Interaction Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:He("drone_image")}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median Edit Time"}),e.jsx(Z,{name:"view-edit-time-details",variant:p?"primary":"secondary",onClick:()=>E(p?"none":"editTime"),className:c.userInteractionCardButton,children:p?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:Ve()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median % Modified"}),e.jsx(Z,{name:"view-percentage-details",variant:I?"primary":"secondary",onClick:()=>E(I?"none":"percentage"),className:c.userInteractionCardButton,children:I?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:ze()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Delete Rate"}),e.jsx(Z,{name:"view-delete-details",variant:T?"primary":"secondary",onClick:()=>E(T?"none":"delete"),className:c.userInteractionCardButton,children:T?"Hide Details":"View Details"})]})]}),p&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ue("drone_image"),columns:ke,keySelector:$,filtered:!1,pending:!1})}),I&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:We("drone_image"),columns:Le,keySelector:$,filtered:!1,pending:!1})}),T&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Qe("drone_image"),columns:Pe,keySelector:$,filtered:!1,pending:!1})})]}),e.jsx(z,{heading:"Model Performance",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ze("drone_image"),columns:Te,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Quality-Event Type Correlation",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:qe("drone_image"),columns:Re,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Model Consistency Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:Ge("drone_image"),columns:$e,keySelector:$,filtered:!1,pending:!1})})})]})]})})}export{jn as default};
|
|
|
1 |
+
import{r as s,y as nt,t as ht,a as q,c as gt,j as e,o as N,b as se,R as st,z,g as rt,d as ft,m as vt,e as pt,n as Z,A as xt,f as _t,h as Ct,i as yt,k as he,l as bt,p as ye,q as jt,s as Nt,E as wt,C as St,U as Mt,Q as It,u as Dt,N as je,_ as Et,L as Tt}from"./index-BCz4bkWK.js";const kt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("g",{clipPath:"url(#arrow-drop-down-line_svg__a)"},s.createElement("path",{d:"m12 15-4.243-4.243 1.415-1.414L12 12.172l2.828-2.83 1.415 1.415L12 15Z"})),s.createElement("defs",null,s.createElement("clipPath",{id:"arrow-drop-down-line_svg__a"},s.createElement("path",{d:"M0 0h24v24H0z"})))),Lt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("g",{clipPath:"url(#arrow-drop-up-line_svg__a)"},s.createElement("path",{d:"m12 11.828-2.828 2.829-1.415-1.414L12 9l4.243 4.243-1.415 1.414L12 11.828Z"})),s.createElement("defs",null,s.createElement("clipPath",{id:"arrow-drop-up-line_svg__a"},s.createElement("path",{d:"M0 0h24v24H0z"})))),Pt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("g",{clipPath:"url(#information-line_svg__a)"},s.createElement("path",{d:"M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10Zm0-2a8 8 0 1 0 0-16.001A8 8 0 0 0 12 20ZM11 7h2v2h-2V7Zm0 4h2v6h-2v-6Z"})),s.createElement("defs",null,s.createElement("clipPath",{id:"information-line_svg__a"},s.createElement("path",{d:"M0 0h24v24H0z"})))),Rt=({title:m,titleId:a,...h})=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",viewBox:"0 0 24 24",fill:"currentColor",width:"1em",height:"1em","aria-labelledby":a},h),m?s.createElement("title",{id:a},m):null,s.createElement("path",{fillRule:"evenodd",d:"m15.063 12 .937.938-4 4-4-4L8.938 12 12 15.063 15.063 12Z",clipRule:"evenodd"}),s.createElement("mask",{id:"table-sorting-line_svg__a",width:8,height:5,x:8,y:12,maskUnits:"userSpaceOnUse",style:{maskType:"luminance"}},s.createElement("path",{fillRule:"evenodd",d:"m15.063 12 .937.938-4 4-4-4L8.938 12 12 15.063 15.063 12Z",clipRule:"evenodd"})),s.createElement("g",{mask:"url(#table-sorting-line_svg__a)"},s.createElement("path",{d:"M-24-22h72v72h-72z"})),s.createElement("path",{fillRule:"evenodd",d:"M8.938 11 8 10.062l4-4 4 4-.938.938L12 7.937 8.937 11Z",clipRule:"evenodd"}),s.createElement("mask",{id:"table-sorting-line_svg__b",width:8,height:5,x:8,y:6,maskUnits:"userSpaceOnUse",style:{maskType:"luminance"}},s.createElement("path",{fillRule:"evenodd",d:"M8.938 11 8 10.062l4-4 4 4-.938.938L12 7.937 8.937 11Z",clipRule:"evenodd"})),s.createElement("g",{mask:"url(#table-sorting-line_svg__b)"},s.createElement("path",{d:"M48 45h-72v-72h72z"}))),$t="_number-output_1blvi_1",Bt={numberOutput:$t};function we(m){const{className:a,invalidText:h=nt,separatorHidden:r,compact:f,currency:g,value:b,tooltip:_,unit:y,prefix:x,suffix:w,maximumFractionDigits:C=1}=m,{currentLanguage:j}=s.useContext(ht),v=s.useMemo(()=>{if(q(b))return h;const S=gt(b,{currency:g,compact:f,separatorHidden:r,maximumFractionDigits:C,unit:y,language:j});return e.jsxs(e.Fragment,{children:[x,S,w]})},[h,b,f,r,g,y,C,x,j,w]);return e.jsx("div",{className:N(Bt.numberOutput,a),title:se(_)?String(_):void 0,children:v})}const At="_tooltip-dummy_rbf3f_1",Ft="_tooltip-content_rbf3f_7",Ot="_pointer_rbf3f_14",Ne={tooltipDummy:At,tooltipContent:Ft,pointer:Ot};function Ht(m){const{className:a,title:h,description:r,preferredWidth:f}=m,[g,b]=s.useState(!1),[_,y]=s.useState(!1),x=s.useRef(),w=s.useRef(null);return s.useEffect(()=>{const C=()=>{y(!0)},j=()=>{y(!1)};if(q(w.current))return;const{current:{parentNode:v}}=w;if(!q(v))return x.current=v,v.addEventListener("mouseover",C),v.addEventListener("mouseout",j),b(!0),()=>{v.removeEventListener("mouseover",C),v.removeEventListener("mouseout",j)}},[]),e.jsxs(e.Fragment,{children:[!g&&e.jsx("div",{className:Ne.tooltipDummy,ref:w}),_&&e.jsx(st,{className:N(Ne.tooltipContent,a),parentRef:x,pointerClassName:Ne.pointer,preferredWidth:f,children:e.jsx(z,{heading:h,withInternalPadding:!0,contentViewType:"vertical",children:r})})]})}function $(m){return m.id}const Vt="common",zt={booleanYesLabel:"Yes",booleanNoLabel:"No"},Ut={namespace:Vt,strings:zt},Wt="_boolean-output_kg1uq_1",Qt={booleanOutput:Wt};function Zt(m){const{className:a,invalidText:h,value:r}=m,f=rt(Ut);let g;return r===!0?g=f.booleanYesLabel:r===!1?g=f.booleanNoLabel:g=h,e.jsx("div",{className:N(Qt.booleanOutput,a),children:g})}const qt="_date-output_4jzjo_1",Gt={dateOutput:qt};function Yt(m){const{value:a,format:h,className:r,invalidText:f}=m,g=s.useMemo(()=>ft(a,h),[a,h]);return e.jsx("div",{className:N(Gt.dateOutput,r),children:g??f})}const Kt="_dropdown-menu_16hml_1",Xt="_icons_16hml_4",Jt="_content_16hml_5",ea="_actions_16hml_6",ta="_dropdown-icon_16hml_10",aa="_dropdown-content_16hml_16",le={dropdownMenu:Kt,icons:Xt,content:Jt,actions:ea,dropdownIcon:ta,dropdownContent:aa};function na(m){const a=s.useRef(null),{className:h,popupClassName:r,children:f,label:g,activeClassName:b,icons:_,variant:y="secondary",actions:x,withoutDropdownIcon:w,componentRef:C,elementRef:j=a,persistent:v,preferredPopupWidth:S}=m,p=s.useRef(null),[k,I]=s.useState(!1);s.useEffect(()=>{C&&(C.current={setShowDropdown:I})},[C,I]);const R=s.useCallback(()=>{I(re=>!re)},[I]),T=s.useCallback((re,ae)=>{ae||re&&v||I(!1)},[I,v]);vt(k,T,p,j);const G=s.useMemo(()=>({setShowDropdown:I}),[I]),U=!!x||!w;return e.jsxs(pt.Provider,{value:G,children:[e.jsx(Z,{name:void 0,className:N(le.dropdownMenu,k&&b,h),elementRef:j,onClick:R,variant:y,actionsContainerClassName:le.actions,iconsContainerClassName:le.icons,childrenContainerClassName:le.content,actions:U?e.jsxs(e.Fragment,{children:[x,!w&&(k?e.jsx(xt,{className:le.dropdownIcon}):e.jsx(_t,{className:le.dropdownIcon}))]}):void 0,icons:_,children:g}),k&&e.jsx(st,{elementRef:p,className:N(le.dropdownContent,r),parentRef:j,preferredWidth:S,children:f})]})}const sa="_info-popup_i3rna_1",ra="_label_i3rna_2",ia="_icon_i3rna_7",oa="_dropdown-container_i3rna_15",la="_content_i3rna_20",ge={infoPopup:sa,label:ra,icon:ia,dropdownContainer:oa,content:la};function ca(m){const{className:a,icon:h=e.jsx(Pt,{}),infoLabel:r,title:f,description:g,withoutIcon:b,popupClassName:_,descriptionClassName:y}=m;return e.jsx(na,{label:e.jsxs("div",{className:ge.label,children:[r,!b&&h&&e.jsx("div",{className:ge.icon,children:h})]}),popupClassName:N(ge.dropdownContainer,_),className:N(ge.infoPopup,a),variant:"tertiary",withoutDropdownIcon:!0,children:e.jsx(z,{heading:f,childrenContainerClassName:N(y,ge.content),withInternalPadding:!0,children:g})})}const da="_progress-wrapper_x340w_1",ua="_title_x340w_7",ma="_total_x340w_11",ha="_progress_x340w_1",fe={progressWrapper:da,title:ua,total:ma,progress:ha};function Ye(m){const{className:a,title:h,description:r,totalValue:f,value:g,showPercentageInTitle:b,children:_,color:y="var(--go-ui-color-primary-red)"}=m,x=se(g)?g:0,w=se(f)?f:0;let C;return w===0?C=0:C=x/w*100,e.jsxs("div",{className:N(fe.progressWrapper,a),children:[(h||b)&&e.jsxs("div",{className:fe.title,children:[h,b&&e.jsx(we,{value:C,suffix:"%"})]}),e.jsx("div",{className:fe.total,children:e.jsx("div",{className:fe.progress,style:{width:`${C}%`,backgroundColor:y}})}),r&&e.jsx("div",{className:fe.description,children:r}),_]})}const ga="_legend-element_1a9ic_1",fa="_color_1a9ic_7",va="_icon-container_1a9ic_14",pa="_icon_1a9ic_14",xa="_label_1a9ic_31",ve={legendElement:ga,color:fa,iconContainer:va,icon:pa,label:xa};function _a(m){const{className:a,colorClassName:h,iconClassName:r,color:f,label:g,iconSrc:b}=m;return e.jsxs("div",{className:N(ve.legendElement,a),children:[b?e.jsx("div",{style:{backgroundColor:f},className:ve.iconContainer,children:e.jsx("img",{className:N(ve.icon,r),src:b,alt:""})}):e.jsx("div",{style:{backgroundColor:f},className:N(ve.color,h)}),e.jsx("div",{className:ve.label,children:g})]})}const Ca="_text-output_10oza_1",ya="_with-background_10oza_6",ba="_label_10oza_11",ja="_with-colon_10oza_12",Na="_value_10oza_17",wa="_text-type_10oza_18",Sa="_strong_10oza_24",ee={textOutput:Ca,withBackground:ya,label:ba,withColon:ja,value:Na,textType:wa,strong:Sa};function Ke(m){const{className:a,label:h,icon:r,description:f,labelClassName:g,descriptionClassName:b,valueClassName:_,strongLabel:y,strongValue:x,strongDescription:w,withoutLabelColon:C,withBackground:j,invalidText:v=nt,...S}=m,{value:p}=m;let k=v;return S.valueType==="number"?k=e.jsx(we,{...S,invalidText:v}):S.valueType==="date"?k=e.jsx(Yt,{...S,invalidText:v}):S.valueType==="boolean"?k=e.jsx(Zt,{...S,invalidText:v}):p instanceof Date||(k=p||v),e.jsxs("div",{className:N(ee.textOutput,j&&ee.withBackground,a),children:[r,h&&e.jsx("div",{className:N(ee.label,y&&ee.strong,g,!C&&ee.withColon),children:h}),e.jsx("div",{className:N(ee.value,x&&ee.strong,S.valueType==="text"&&ee.textType,_),children:k}),f&&e.jsx("div",{className:N(ee.description,w&&ee.strong,b),children:f})]})}const Ma="_pie-chart_pyr7m_1",Ia="_legend_pyr7m_7",Da="_legend-item_pyr7m_13",pe={pieChart:Ma,legend:Ia,legendItem:Da},Ea=70,Ta=40;function Xe(m,a=1){return Math.round(m*10**a)/10**a}function Je(m,a){const h=(a-90)*Math.PI/180;return{x:Xe(m+m*Math.cos(h)),y:Xe(m+m*Math.sin(h))}}function ka(m,a,h){let r=h;const f=r-a===360;f&&(r-=1);const g=Je(m,a),b=Je(m,r),_=r-a<=180?0:1,y=["M",g.x,g.y,"A",m,m,0,_,1,b.x,b.y];return f?y.push("Z"):y.push("L",m,m,"L",g.x,g.y,"Z"),y.join(" ")}function xe(m){const{className:a,data:h,valueSelector:r,labelSelector:f,keySelector:g,colorSelector:b,colors:_,pieRadius:y=Ea,chartPadding:x=Ta,legendClassName:w,showPercentageInLegend:C}=m,j=Ct(h?.map(p=>r(p))),v=q(j)||j===0?1:j,S=s.useMemo(()=>{let p=0;const k=h?.map(I=>{const R=r(I);if(q(R))return;const T=360*(R/v);return p+=T,{key:g(I),value:R,label:f(I),startAngle:p-T,percentage:yt(R,v),endAngle:p,datum:I}}).filter(se)??[];return b?k.map(({datum:I,...R})=>({...R,color:b(I)})):k.map(({datum:I,...R},T)=>({...R,color:_[T%_.length]}))},[h,g,r,f,v,b,_]);return e.jsxs("div",{className:N(pe.pieChart,a),children:[e.jsx("svg",{className:pe.svg,style:{width:`${x+y*2}px`,height:`${x+y*2}px`},children:e.jsx("g",{style:{transform:`translate(${x/2}px, ${x/2}px)`},children:S.map(p=>e.jsx("path",{className:pe.path,d:ka(y,p.startAngle,p.endAngle),fill:p.color,children:e.jsx(Ht,{description:e.jsx(Ke,{label:p.label,value:p.value})})},p.key))})}),e.jsx("div",{className:N(pe.legend,w),children:S.map(p=>e.jsx(_a,{className:pe.legendItem,label:C?e.jsx(Ke,{label:p.label,value:p.percentage,valueType:"number",prefix:"(",suffix:"%)",withoutLabelColon:!0}):p.label,color:p.color},p.key))})]})}const La="_td_1k4cn_1",Pa={td:La};function Ra(m){const{className:a,children:h,...r}=m;return e.jsx("td",{className:N(a,Pa.td),...r,children:h})}function it(m){const{className:a,children:h,...r}=m;return e.jsx("tr",{className:a,...r,children:h})}const $a="_row_1829z_1",Ba="_cell_1829z_2",et={row:$a,cell:Ba};function Aa(m){const{data:a,keySelector:h,columns:r,rowClassName:f,cellClassName:g,rowModifier:b}=m;return e.jsx(e.Fragment,{children:a?.map((_,y)=>{const x=h(_,y),w=r.map(v=>{const{id:S,cellRenderer:p,cellRendererClassName:k,cellRendererParams:I,cellContainerClassName:R}=v,T=I(x,_,y,a),G=e.jsx(p,{className:k,...T,name:S});return e.jsx(Ra,{className:N(et.cell,R,typeof g=="function"?g(x,_,S):g),children:G},S)}),C=e.jsx(it,{className:N(et.row,typeof f=="function"?f(x,_):f),children:w});let j=C;return b&&(j=b({rowKey:x,row:C,cells:w,columns:r,datum:_})),e.jsx(s.Fragment,{children:j},x)})})}const Fa="_th_cdv41_1",Oa="_resize-handle_cdv41_8",tt={th:Fa,resizeHandle:Oa};function Ha(m){const{className:a,children:h,onResize:r,onResizeComplete:f,name:g,...b}=m,_=s.useRef(null),y=s.useRef(),x=s.useRef(),w=s.useRef(),C=s.useCallback(v=>{var S;if(se(y.current)&&_.current&&r){v.preventDefault(),v.stopPropagation();const p=v.clientX-y.current;if(se(x.current)){const k=x.current+p;w.current=k,r(k,g)}else x.current=(S=_.current)==null?void 0:S.offsetWidth}},[r,g]),j=s.useCallback(v=>{var S;v.preventDefault(),y.current=v.clientX,x.current=(S=_.current)==null?void 0:S.offsetWidth,window.addEventListener("mousemove",C,!0)},[C]);return s.useEffect(()=>{const v=()=>{y.current=void 0,x.current=void 0,f&&se(w.current)&&f(w.current,g),window.removeEventListener("mousemove",C,!0)};return window.addEventListener("mouseup",v,!0),()=>{window.removeEventListener("mouseup",v,!0),window.removeEventListener("mousemove",C,!0)}},[C,g,f]),e.jsxs("th",{ref:_,className:N(a,tt.th),...b,children:[r&&e.jsx("div",{role:"presentation",className:tt.resizeHandle,onMouseDown:j}),h]})}const Va="_table_nilhy_1",za="_table-overflow-wrapper_nilhy_8",Ua="_table-element_nilhy_13",Wa="_header-row_nilhy_23",Qa="_header-element_nilhy_24",Za="_header-component_nilhy_29",ce={table:Va,tableOverflowWrapper:za,tableElement:Ua,headerRow:Wa,headerElement:Qa,headerComponent:Za};function qa(m,a){return a??m.columnWidth??wt}function B(m){const{data:a,keySelector:h,columns:r,caption:f,className:g,captionClassName:b,headerRowClassName:_,headerCellClassName:y,rowClassName:x,cellClassName:w,rowModifier:C,fixedColumnWidth:j,resizableColumn:v,headersHidden:S,pending:p,filtered:k,errored:I=!1}=m,R=s.useRef(null),[T]=he.useState(()=>bt()),[G,U]=he.useState({});s.useEffect(()=>{U(W=>{if(q(R.current))return W;const E=R.current.getBoundingClientRect(),{width:O}=E;let A=r.map(M=>({id:M.id,stretch:!!M.columnStretch,width:qa(M,W[M.id])}));const X=ye(A.filter(M=>M.stretch).map(M=>M.width)),ne=ye(A.filter(M=>!M.stretch).map(M=>M.width)),Y=(O-ne)/X;return Y>1&&(A=A.map(M=>({...M,width:M.stretch?M.width*Y:M.width}))),jt(A,M=>M.id,M=>M.width)})},[r]);const re=he.useCallback((W,E)=>{const O=document.getElementById(`${T}-${E}`),A=Math.max(W,80);if(q(O)||(O.style.width=`${A}px`,!j))return;const X=document.getElementById(T);if(q(X))return;const ne=ye(r.map(Y=>Y.id===E?A:G[Y.id]));X.style.width=`${ne}px`},[T,G,r,j]),ae=he.useCallback((W,E)=>{se(E)&&U(O=>({...O,[E]:Math.max(W,80)}))},[U]),be=he.useMemo(()=>ye(r.map(W=>G[W.id])),[G,r]),K=q(a)||a.length===0||Object.keys(G).length===0;return e.jsxs("div",{ref:R,className:N(ce.table,g),children:[!K&&e.jsx("div",{className:ce.tableOverflowWrapper,children:e.jsxs("table",{className:ce.tableElement,style:j?{width:`${be}px`}:void 0,id:T,children:[f&&e.jsx("caption",{className:b,children:f}),e.jsx("colgroup",{children:r.map(W=>{const{id:E,columnClassName:O}=W,A=G[E],X=j?{width:`${A}px`}:void 0;return e.jsx("col",{id:`${T}-${E}`,style:X,className:N(ce.column,O)},E)})}),!S&&e.jsx("thead",{children:e.jsx(it,{className:N(ce.headerRow,_),children:r.map((W,E)=>{const{id:O,title:A,headerCellRenderer:X,headerCellRendererClassName:ne,headerCellRendererParams:Y,headerContainerClassName:M}=W,ie=e.jsx(X,{...Y,name:O,title:A,index:E,className:N(ne,ce.headerComponent)});return e.jsx(Ha,{scope:"col",name:O,onResize:v?re:void 0,onResizeComplete:v?ae:void 0,className:N(ce.headerElement,typeof y=="function"?y(O):y,M),children:ie},O)})})}),e.jsx("tbody",{children:e.jsx(Aa,{data:a,keySelector:h,columns:r,rowClassName:x,cellClassName:w,rowModifier:C})})]})}),e.jsx(Nt,{filtered:k,empty:K,errored:I,pending:p,overlayPending:!0})]})}function Ga(m){const{className:a,value:h}=m;return q(h)?null:e.jsx("div",{className:a,children:h})}const Ya="common",Ka={sortTableButtonTitle:"Sort Table"},Xa={namespace:Ya,strings:Ka},Ja="_header-cell_vn24d_1",en="_sort-button_vn24d_8",tn="_icon_vn24d_12",an="_info-popup-icon_vn24d_17",de={headerCell:Ja,sortButton:en,icon:tn,infoPopupIcon:an};function ot(m){const{className:a,titleClassName:h,title:r,name:f,sortable:g,defaultSortDirection:b="asc",infoTitle:_,infoDescription:y}=m,{sorting:x,setSorting:w}=s.useContext(St),C=rt(Xa),j=x?.name===f?x.direction:void 0,v=s.useRef(null),S=s.useCallback(()=>{if(q(w))return;let p;q(j)?p=b:j==="asc"?p="dsc":j==="dsc"&&(p="asc"),w(p?{name:f,direction:p}:void 0)},[f,w,j,b]);return e.jsxs("div",{ref:v,className:N(a,de.headerCell),children:[g&&e.jsxs(Z,{name:void 0,variant:"tertiary",onClick:S,title:C.sortTableButtonTitle,className:de.sortButton,children:[q(j)&&e.jsx(Rt,{className:de.icon}),j==="asc"&&e.jsx(Lt,{className:de.icon}),j==="dsc"&&e.jsx(kt,{className:de.icon})]}),e.jsx("div",{className:N(h,de.title),children:r}),_&&y&&e.jsx(ca,{className:de.infoPopupIcon,title:_,description:y})]})}const at={};function Q(m,a,h,r){return{id:m,title:a,columnClassName:r?.columnClassName,headerCellRenderer:ot,headerCellRendererClassName:r?.headerCellRendererClassName,headerContainerClassName:r?.headerContainerClassName,headerCellRendererParams:{sortable:r?.sortable,infoTitle:r?.headerInfoTitle,infoDescription:r?.headerInfoDescription},cellRendererClassName:r?.cellRendererClassName,cellContainerClassName:r?.cellContainerClassName,cellRenderer:Ga,cellRendererParams:(f,g)=>({value:h(g)||"--"}),valueSelector:h,valueComparator:(f,g)=>Mt(h(f),h(g)),columnWidth:r?.columnWidth,columnStretch:r?.columnStretch,columnStyle:r?.columnStyle}}function D(m,a,h,r){return{id:m,title:a,columnClassName:r?.columnClassName,headerCellRenderer:ot,headerCellRendererClassName:N(at.numberCellHeader,r?.headerCellRendererClassName),headerContainerClassName:r?.headerContainerClassName,headerCellRendererParams:{sortable:r?.sortable,infoTitle:r?.headerInfoTitle,infoDescription:r?.headerInfoDescription},cellRendererClassName:N(at.numberCell,r?.cellRendererClassName),cellContainerClassName:r?.cellContainerClassName,cellRenderer:we,cellRendererParams:(f,g)=>({value:h(g),suffix:r?.suffix,maximumFractionDigits:r?.maximumFractionDigits,invalidText:"--"}),valueSelector:h,valueComparator:(f,g)=>It(h(f),h(g)),columnWidth:r?.columnWidth,columnStretch:r?.columnStretch,columnStyle:r?.columnStyle}}const nn="_tabSelector_vlxoe_1",sn="_progressSection_vlxoe_14",rn="_progressLabel_vlxoe_20",on="_chartGrid_vlxoe_28",ln="_chartContainer_vlxoe_40",cn="_tableContainer_vlxoe_51",dn="_modelPerformance_vlxoe_59",un="_loadingContainer_vlxoe_67",mn="_errorContainer_vlxoe_77",hn="_userInteractionCards_vlxoe_96",gn="_userInteractionCard_vlxoe_96",fn="_userInteractionCardValue_vlxoe_116",vn="_userInteractionCardLabel_vlxoe_123",pn="_userInteractionCardButton_vlxoe_130",xn="_summaryStatsCards_vlxoe_148",_n="_summaryStatsCard_vlxoe_148",Cn="_summaryStatsCardValue_vlxoe_169",yn="_summaryStatsCardLabel_vlxoe_176",c={tabSelector:nn,progressSection:sn,progressLabel:rn,chartGrid:on,chartContainer:ln,tableContainer:cn,modelPerformance:dn,loadingContainer:un,errorContainer:mn,userInteractionCards:hn,userInteractionCard:gn,userInteractionCardValue:fn,userInteractionCardLabel:vn,userInteractionCardButton:pn,summaryStatsCards:xn,summaryStatsCard:_n,summaryStatsCardValue:Cn,summaryStatsCardLabel:yn};function jn(){const[m]=Dt(),[a,h]=s.useState(null),[r,f]=s.useState(!0),[g,b]=s.useState("crisis_maps"),[_,y]=s.useState([]),[x,w]=s.useState([]),[C,j]=s.useState([]),[v,S]=s.useState([]),[p,k]=s.useState(!1),[I,R]=s.useState(!1),[T,G]=s.useState(!1),[U,re]=s.useState(!1),[ae,be]=s.useState(!1),[K,W]=s.useState(!1),E=t=>{k(t==="editTime"),R(t==="percentage"),G(t==="delete"),re(t==="regions"),be(t==="sources"),W(t==="types")},O=[{key:"crisis_maps",label:"Crisis Maps"},{key:"drone_images",label:"Drone Images"}],A=s.useCallback((t,l)=>{if(!t||!l)return 0;const i=t.toLowerCase().replace(/[^\w\s]/g,"").split(/\s+/).filter(u=>u.length>0),n=l.toLowerCase().replace(/[^\w\s]/g,"").split(/\s+/).filter(u=>u.length>0);if(i.length===0&&n.length===0)return 1;if(i.length===0||n.length===0)return 0;const o=new Set(i),d=new Set(n),P=new Set([...o].filter(u=>d.has(u))),F=new Set([...o,...d]);return P.size/F.size},[]),X=s.useCallback(async()=>{f(!0);try{const l=await(await fetch("/api/images")).json(),i={},n=l.filter(u=>u.image_type==="crisis_map"),o=l.filter(u=>u.image_type==="drone_image"),d={totalCaptions:l.length,sources:{},types:{},regions:{},models:{},modelEditTimes:i,percentageModified:0,modelPercentageData:{},totalDeleteCount:0,deleteRate:0,crisisMaps:n,droneImages:o};l.forEach(u=>{if(u.source&&(d.sources[u.source]=(d.sources[u.source]||0)+1),u.event_type&&(d.types[u.event_type]=(d.types[u.event_type]||0)+1),u.countries&&u.countries.forEach(L=>{L.r_code&&(d.regions[L.r_code]=(d.regions[L.r_code]||0)+1)}),u.model){const L=u.model,V=d.models[L]||={count:0,avgAccuracy:0,avgContext:0,avgUsability:0,totalScore:0,deleteCount:0};if(V.count++,u.accuracy!=null&&(V.avgAccuracy+=u.accuracy),u.context!=null&&(V.avgContext+=u.context),u.usability!=null&&(V.avgUsability+=u.usability),u.created_at&&u.updated_at){const te=new Date(u.created_at).getTime(),Ce=new Date(u.updated_at).getTime()-te;Ce>0&&(i[L]||(i[L]=[]),i[L].push(Ce))}}}),_.forEach(u=>{u.s_code&&!d.sources[u.s_code]&&(d.sources[u.s_code]=0)}),x.forEach(u=>{u.t_code&&!d.types[u.t_code]&&(d.types[u.t_code]=0)}),C.forEach(u=>{u.r_code&&!d.regions[u.r_code]&&(d.regions[u.r_code]=0)}),["GPT-4","Claude","Gemini","Llama","Other"].forEach(u=>{d.models[u]||(d.models[u]={count:0,avgAccuracy:0,avgContext:0,avgUsability:0,totalScore:0,deleteCount:0})}),Object.values(d.models).forEach(u=>{u.count>0&&(u.avgAccuracy=Math.round(u.avgAccuracy/u.count),u.avgContext=Math.round(u.avgContext/u.count),u.avgUsability=Math.round(u.avgUsability/u.count),u.totalScore=Math.round((u.avgAccuracy+u.avgContext+u.avgUsability)/3))});const F=l.filter(u=>u.generated&&u.edited);if(F.length>0){const L=[...F.map(oe=>A(oe.generated,oe.edited))].sort((oe,Ce)=>oe-Ce),V=Math.floor(L.length/2),te=L.length%2===0?(L[V-1]+L[V])/2:L[V];d.percentageModified=Math.round((1-te)*100)}const H={};l.forEach(u=>{if(u.model&&u.generated&&u.edited){const L=A(u.generated,u.edited),V=Math.round((1-L)*100);H[u.model]||(H[u.model]=[]),H[u.model].push(V)}}),d.modelPercentageData=H;try{const u=await fetch("/api/models");if(u.ok){const L=await u.json();if(L.models){L.models.forEach(te=>{d.models[te.m_code]&&(d.models[te.m_code].deleteCount=te.delete_count||0)});const V=L.models.reduce((te,oe)=>te+(oe.delete_count||0),0);d.totalDeleteCount=V,d.deleteRate=V>0?Math.round(V/(V+l.length)*100):0}}}catch(u){console.log("Could not fetch model delete counts:",u)}h(d)}catch{h(null)}finally{f(!1)}},[_,x,C,A]),ne=s.useCallback(async()=>{try{const[t,l,i,n]=await Promise.all([fetch("/api/sources"),fetch("/api/types"),fetch("/api/regions"),fetch("/api/models")]),o=await t.json(),d=await l.json(),P=await i.json(),F=await n.json();y(o),w(d),j(P),S(F.models||[])}catch(t){console.log("Could not fetch lookup data:",t)}},[]);s.useEffect(()=>{const t=m.get("view");(t==="crisis_maps"||t==="drone_images")&&b(t)},[m]),s.useEffect(()=>{ne()},[ne]),s.useEffect(()=>{_.length>0&&x.length>0&&C.length>0&&v.length>0&&X()},[_,x,C,v,X]);const Y=s.useCallback(t=>{const l=_.find(i=>i.s_code===t);return l?l.label:t},[_]),M=s.useCallback(t=>{if(t.length===0)return 0;const l=[...t].sort((n,o)=>n-o),i=Math.floor(l.length/2);return l.length%2===0?Math.round((l[i-1]+l[i])/2):l[i]},[]),ie=s.useCallback(t=>{const l=Math.floor(t/1e3),i=Math.floor(l/60),n=Math.floor(i/60);return n>0?`${n}h ${i%60}m`:i>0?`${i}m ${l%60}s`:`${l}s`},[]),_e=s.useCallback(t=>{const l=x.find(i=>i.t_code===t);return l?l.label:t},[x]),J=s.useCallback(t=>{const l=v.find(i=>i.m_code===t);return l?l.label:t},[v]),Se=s.useMemo(()=>a?Object.entries(a.modelEditTimes||{}).filter(([,t])=>t.length>0).sort(([,t],[,l])=>M(l)-M(t)).map(([t,l],i)=>({id:i+1,name:J(t),count:l.length,avgEditTime:M(l),minEditTime:Math.min(...l),maxEditTime:Math.max(...l)})):[],[a,M,J]),Me=s.useMemo(()=>a?Object.entries(a.modelPercentageData||{}).filter(([,t])=>t.length>0).sort(([,t],[,l])=>{const i=[...t].sort((H,u)=>H-u),n=[...l].sort((H,u)=>H-u),o=Math.floor(i.length/2),d=Math.floor(n.length/2),P=i.length%2===0?(i[o-1]+i[o])/2:i[o];return(n.length%2===0?(n[d-1]+n[d])/2:n[d])-P}).map(([t,l],i)=>{const n=[...l].sort((P,F)=>P-F),o=Math.floor(n.length/2),d=n.length%2===0?Math.round((n[o-1]+n[o])/2):n[o];return{id:i+1,name:J(t),count:l.length,avgPercentageModified:d,minPercentageModified:Math.min(...l),maxPercentageModified:Math.max(...l)}}):[],[a,J]),Ie=s.useMemo(()=>a?Object.entries(a.models).filter(([,t])=>t.count>0).map(([t,l],i)=>{const n=[l.avgAccuracy,l.avgContext,l.avgUsability],o=n.reduce((F,H)=>F+H,0)/n.length,d=n.reduce((F,H)=>F+Math.pow(H-o,2),0)/n.length,P=Math.round(100-Math.sqrt(d));return{id:i+1,name:J(t),consistency:Math.max(0,P),avgScore:Math.round(o),count:l.count}}).sort((t,l)=>l.consistency-t.consistency):[],[a,J]),De=s.useMemo(()=>[Q("name","Region",t=>t.name),D("count","Count",t=>t.count),D("percentage","% of Total",t=>t.percentage,{suffix:"%",maximumFractionDigits:0})],[]),Ee=s.useMemo(()=>[Q("name","Type",t=>t.name),D("count","Count",t=>t.count),D("percentage","% of Total",t=>t.percentage,{suffix:"%",maximumFractionDigits:0})],[]),lt=s.useMemo(()=>[Q("name","Source",t=>t.name),D("count","Count",t=>t.count),D("percentage","% of Total",t=>t.percentage,{suffix:"%",maximumFractionDigits:0})],[]),Te=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Count",t=>t.count),D("accuracy","Accuracy",t=>t.accuracy,{suffix:"%",maximumFractionDigits:0}),D("context","Context",t=>t.context,{suffix:"%",maximumFractionDigits:0}),D("usability","Usability",t=>t.usability,{suffix:"%",maximumFractionDigits:0}),D("totalScore","Total Score",t=>t.totalScore,{suffix:"%",maximumFractionDigits:0})],[]),ke=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Count",t=>t.count),Q("avgEditTime","Median Edit Time",t=>ie(t.avgEditTime)),Q("minEditTime","Min Edit Time",t=>ie(t.minEditTime)),Q("maxEditTime","Max Edit Time",t=>ie(t.maxEditTime))],[]),Le=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Count",t=>t.count),D("avgPercentageModified","Median % Modified",t=>t.avgPercentageModified,{suffix:"%",maximumFractionDigits:0}),D("minPercentageModified","Min % Modified",t=>t.minPercentageModified,{suffix:"%",maximumFractionDigits:0}),D("maxPercentageModified","Max % Modified",t=>t.maxPercentageModified,{suffix:"%",maximumFractionDigits:0})],[]),Pe=s.useMemo(()=>[Q("name","Model",t=>t.name),D("count","Total Count",t=>t.count),D("deleteCount","Delete Count",t=>t.deleteCount),D("deleteRate","Delete Rate",t=>t.deleteRate,{suffix:"%",maximumFractionDigits:1})],[]),ct=s.useMemo(()=>[Q("source","Source",t=>t.source),D("avgQuality","Average Quality",t=>t.avgQuality,{suffix:"%",maximumFractionDigits:0}),D("count","Count",t=>t.count)],[]),Re=s.useMemo(()=>[Q("eventType","Event Type",t=>t.eventType),D("avgQuality","Average Quality",t=>t.avgQuality,{suffix:"%",maximumFractionDigits:0}),D("count","Count",t=>t.count)],[]),$e=s.useMemo(()=>[Q("name","Model",t=>t.name),D("consistency","Consistency",t=>t.consistency,{suffix:"%",maximumFractionDigits:0}),D("avgScore","Average Score",t=>t.avgScore,{suffix:"%",maximumFractionDigits:0}),D("count","Count",t=>t.count)],[]),ue=s.useCallback(t=>a?t==="crisis_map"?a.crisisMaps.length:t==="drone_image"?a.droneImages.length:0:0,[a]),Be=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.countries&&n.countries.forEach(o=>{o.r_code&&(i[o.r_code]=(i[o.r_code]||0)+1)})}),Object.entries(i).filter(([,n])=>n>0).map(([n,o])=>({name:C.find(d=>d.r_code===n)?.label||n,value:o}))},[a,C]),Ae=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};l.forEach(o=>{o.countries&&o.countries.forEach(d=>{d.r_code&&(i[d.r_code]=(i[d.r_code]||0)+1)})});const n=C.reduce((o,d)=>(d.r_code&&(o[d.r_code]={name:d.label,count:i[d.r_code]||0}),o),{});return Object.entries(n).sort(([,o],[,d])=>d.count-o.count).map(([,{name:o,count:d}],P)=>({id:P+1,name:o,count:d,percentage:l.length>0?Math.round(d/l.length*100):0}))},[a,C]),dt=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.source&&(i[n.source]=(i[n.source]||0)+1)}),Object.entries(i).filter(([,n])=>n>0).map(([n,o])=>({name:_.find(d=>d.s_code===n)?.label||n,value:o}))},[a,_]),ut=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.source&&(i[n.source]=(i[n.source]||0)+1)}),Object.entries(i).sort(([,n],[,o])=>o-n).map(([n,o],d)=>({id:d+1,name:Y(n),count:o,percentage:l.length>0?Math.round(o/l.length*100):0}))},[a,Y]),Fe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.event_type&&(i[n.event_type]=(i[n.event_type]||0)+1)}),Object.entries(i).filter(([,n])=>n>0).map(([n,o])=>({name:x.find(d=>d.t_code===n)?.label||n,value:o}))},[a,x]),Oe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.event_type&&(i[n.event_type]=(i[n.event_type]||0)+1)}),Object.entries(i).sort(([,n],[,o])=>o-n).map(([n,o],d)=>({id:d+1,name:_e(n),count:o,percentage:l.length>0?Math.round(o/l.length*100):0}))},[a,_e]),He=s.useCallback(t=>{if(!a)return"No data available";const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;l.forEach(d=>{d.model&&i.add(d.model)}),console.log(`Debug ${t}:`,{totalImages:l.length,usedModels:Array.from(i),availableEditTimes:Object.keys(a.modelEditTimes),modelEditTimesData:a.modelEditTimes});const o=Object.entries(a.modelEditTimes).filter(([d])=>i.has(d)).flatMap(([,d])=>d);return o.length===0?"No data available":ie(M(o))},[a,ie,M]),Ve=s.useCallback(()=>{if(!a)return"No data available";const t=a.totalCaptions||0,l=a.percentageModified||0;return t>0?Math.round(l/t*100):0},[a]),ze=s.useCallback(()=>a&&a.deleteRate>=0?`${a.deleteRate}%`:"No data available",[a]),Ue=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;return l.forEach(o=>{o.model&&i.add(o.model)}),Se.filter(o=>{const d=v.find(P=>P.label===o.name)?.m_code;return d&&i.has(d)})},[a,Se,v]),We=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;return l.forEach(o=>{o.model&&i.add(o.model)}),Me.filter(o=>{const d=v.find(P=>P.label===o.name)?.m_code;return d&&i.has(d)})},[a,Me,v]),Qe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.model&&(i[n.model]||(i[n.model]={count:0,deleteCount:0}),i[n.model].count++)}),Object.entries(i).map(([n,o],d)=>{const F=a.models?.[n]?.deleteCount||0,H=o.count>0?Math.round(F/o.count*100*10)/10:0;return{id:d+1,name:J(n),count:o.count,deleteCount:F,deleteRate:H}}).sort((n,o)=>o.count-n.count)},[a,J]),Ze=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.model&&(i[n.model]||(i[n.model]={count:0,totalAccuracy:0,totalContext:0,totalUsability:0}),i[n.model].count++,n.accuracy!=null&&(i[n.model].totalAccuracy+=n.accuracy),n.context!=null&&(i[n.model].totalContext+=n.context),n.usability!=null&&(i[n.model].totalUsability+=n.usability))}),Object.entries(i).map(([n,o],d)=>({id:d+1,name:J(n),count:o.count,accuracy:o.count>0?Math.round(o.totalAccuracy/o.count):0,context:o.count>0?Math.round(o.totalContext/o.count):0,usability:o.count>0?Math.round(o.totalUsability/o.count):0,totalScore:o.count>0?Math.round((o.totalAccuracy+o.totalContext+o.totalUsability)/(3*o.count)):0})).sort((n,o)=>o.totalScore-n.totalScore)},[a,J]),mt=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.source&&(i[n.source]||(i[n.source]={total:0,count:0,totalImages:0}),i[n.source].totalImages+=1,n.accuracy!=null&&(i[n.source].total+=n.accuracy,i[n.source].count+=1))}),Object.entries(i).map(([n,o],d)=>({id:d+1,source:Y(n),avgQuality:o.count>0?Math.round(o.total/o.count):0,count:o.totalImages}))},[a,Y]),qe=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i={};return l.forEach(n=>{n.event_type&&(i[n.event_type]||(i[n.event_type]={total:0,count:0,totalImages:0}),i[n.event_type].totalImages+=1,n.accuracy!=null&&(i[n.event_type].total+=n.accuracy,i[n.event_type].count+=1))}),Object.entries(i).map(([n,o],d)=>({id:d+1,eventType:_e(n),avgQuality:o.count>0?Math.round(o.total/o.count):0,count:o.totalImages}))},[a,_e]),Ge=s.useCallback(t=>{if(!a)return[];const l=t==="crisis_map"?a.crisisMaps:a.droneImages,i=new Set;return l.forEach(o=>{o.model&&i.add(o.model)}),Ie.filter(o=>{const d=v.find(P=>P.label===o.name)?.m_code;return d&&i.has(d)})},[a,Ie,v]);if(r)return e.jsx(je,{children:e.jsx("div",{className:c.loadingContainer,children:e.jsx(Et,{})})});if(!a)return e.jsx(je,{children:e.jsx("div",{className:c.errorContainer,children:e.jsx("div",{className:"text-red-500",children:"Failed to load analytics data. Please try again."})})});const me=["#F5333F","#F64752","#F75C65","#F87079","#F9858C","#FA999F","#FBADB2","#FCC2C5"];return e.jsx(je,{children:e.jsxs("div",{className:"max-w-7xl mx-auto",children:[e.jsx("div",{className:c.tabSelector,children:e.jsx(Tt,{name:"analytics-view",value:g,onChange:t=>{(t==="crisis_maps"||t==="drone_images")&&b(t)},options:O,keySelector:t=>t.key,labelSelector:t=>t.label})}),g==="crisis_maps"?e.jsxs("div",{className:c.chartGrid,children:[e.jsxs(z,{heading:"Summary Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.summaryStatsCards,children:[e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:ue("crisis_map")}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Total Crisis Maps"})]}),e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:"2000"}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Target Amount"})]})]}),e.jsxs("div",{className:c.progressSection,children:[e.jsxs("div",{className:c.progressLabel,children:[e.jsx("span",{children:"Progress towards target"}),e.jsxs("span",{children:[Math.round(ue("crisis_map")/2e3*100),"%"]})]}),e.jsx(Ye,{value:ue("crisis_map"),totalValue:2e3})]})]}),e.jsxs(z,{heading:"Distribution Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Regions Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Be("crisis_map"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-regions-details",variant:U?"primary":"secondary",onClick:()=>E(U?"none":"regions"),className:c.userInteractionCardButton,children:U?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Sources Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:dt("crisis_map"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-sources-details",variant:ae?"primary":"secondary",onClick:()=>E(ae?"none":"sources"),className:c.userInteractionCardButton,children:ae?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Types Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Fe("crisis_map"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-types-details",variant:K?"primary":"secondary",onClick:()=>E(K?"none":"types"),className:c.userInteractionCardButton,children:K?"Hide Details":"View Details"})]})]}),U&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ae("crisis_map"),columns:De,keySelector:$,filtered:!1,pending:!1})}),ae&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:ut("crisis_map"),columns:lt,keySelector:$,filtered:!1,pending:!1})}),K&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Oe("crisis_map"),columns:Ee,keySelector:$,filtered:!1,pending:!1})})]}),e.jsxs(z,{heading:"User Interaction Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:He("crisis_map")}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median Edit Time"}),e.jsx(Z,{name:"view-edit-time-details",variant:p?"primary":"secondary",onClick:()=>E(p?"none":"editTime"),className:c.userInteractionCardButton,children:p?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:Ve()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median % Modified"}),e.jsx(Z,{name:"view-percentage-details",variant:I?"primary":"secondary",onClick:()=>E(I?"none":"percentage"),className:c.userInteractionCardButton,children:I?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:ze()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Delete Rate"}),e.jsx(Z,{name:"view-delete-details",variant:T?"primary":"secondary",onClick:()=>E(T?"none":"delete"),className:c.userInteractionCardButton,children:T?"Hide Details":"View Details"})]})]}),p&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ue("crisis_map"),columns:ke,keySelector:$,filtered:!1,pending:!1})}),I&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:We("crisis_map"),columns:Le,keySelector:$,filtered:!1,pending:!1})}),T&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Qe("crisis_map"),columns:Pe,keySelector:$,filtered:!1,pending:!1})})]}),e.jsx(z,{heading:"Model Performance",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ze("crisis_map"),columns:Te,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Quality-Source Correlation",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:mt("crisis_map"),columns:ct,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Quality-Event Type Correlation",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:qe("crisis_map"),columns:Re,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Model Consistency Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:Ge("crisis_map"),columns:$e,keySelector:$,filtered:!1,pending:!1})})})]}):e.jsxs("div",{className:c.chartGrid,children:[e.jsxs(z,{heading:"Summary Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.summaryStatsCards,children:[e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:ue("drone_image")}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Total Drone Images"})]}),e.jsxs("div",{className:c.summaryStatsCard,children:[e.jsx("div",{className:c.summaryStatsCardValue,children:"2000"}),e.jsx("div",{className:c.summaryStatsCardLabel,children:"Target Amount"})]})]}),e.jsxs("div",{className:c.progressSection,children:[e.jsxs("div",{className:c.progressLabel,children:[e.jsx("span",{children:"Progress towards target"}),e.jsxs("span",{children:[Math.round(ue("drone_image")/2e3*100),"%"]})]}),e.jsx(Ye,{value:ue("drone_image"),totalValue:2e3})]})]}),e.jsxs(z,{heading:"Distribution Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Regions Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Be("drone_image"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-regions-details",variant:U?"primary":"secondary",onClick:()=>E(U?"none":"regions"),className:c.userInteractionCardButton,children:U?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardLabel,children:"Types Distribution"}),e.jsx("div",{className:c.chartContainer,children:e.jsx(xe,{data:Fe("drone_image"),valueSelector:t=>t.value,labelSelector:t=>t.name,keySelector:t=>t.name,colors:me,showPercentageInLegend:!0})}),e.jsx(Z,{name:"view-types-details",variant:K?"primary":"secondary",onClick:()=>E(K?"none":"types"),className:c.userInteractionCardButton,children:K?"Hide Details":"View Details"})]})]}),U&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ae("drone_image"),columns:De,keySelector:$,filtered:!1,pending:!1})}),K&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Oe("drone_image"),columns:Ee,keySelector:$,filtered:!1,pending:!1})})]}),e.jsxs(z,{heading:"User Interaction Statistics",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:[e.jsxs("div",{className:c.userInteractionCards,children:[e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:He("drone_image")}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median Edit Time"}),e.jsx(Z,{name:"view-edit-time-details",variant:p?"primary":"secondary",onClick:()=>E(p?"none":"editTime"),className:c.userInteractionCardButton,children:p?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:Ve()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Median % Modified"}),e.jsx(Z,{name:"view-percentage-details",variant:I?"primary":"secondary",onClick:()=>E(I?"none":"percentage"),className:c.userInteractionCardButton,children:I?"Hide Details":"View Details"})]}),e.jsxs("div",{className:c.userInteractionCard,children:[e.jsx("div",{className:c.userInteractionCardValue,children:ze()}),e.jsx("div",{className:c.userInteractionCardLabel,children:"Delete Rate"}),e.jsx(Z,{name:"view-delete-details",variant:T?"primary":"secondary",onClick:()=>E(T?"none":"delete"),className:c.userInteractionCardButton,children:T?"Hide Details":"View Details"})]})]}),p&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ue("drone_image"),columns:ke,keySelector:$,filtered:!1,pending:!1})}),I&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:We("drone_image"),columns:Le,keySelector:$,filtered:!1,pending:!1})}),T&&e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Qe("drone_image"),columns:Pe,keySelector:$,filtered:!1,pending:!1})})]}),e.jsx(z,{heading:"Model Performance",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.modelPerformance,children:e.jsx(B,{data:Ze("drone_image"),columns:Te,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Quality-Event Type Correlation",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:qe("drone_image"),columns:Re,keySelector:$,filtered:!1,pending:!1})})}),e.jsx(z,{heading:"Model Consistency Analysis",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,children:e.jsx("div",{className:c.tableContainer,children:e.jsx(B,{data:Ge("drone_image"),columns:$e,keySelector:$,filtered:!1,pending:!1})})})]})]})})}export{jn as default};
|
py_backend/static/assets/{index-C_hAuRbb.js β index-BCz4bkWK.js}
RENAMED
The diff for this file is too large to render.
See raw diff
|
|
py_backend/static/assets/{index-Bc-_2bce.js β index-Cj53JezQ.js}
RENAMED
@@ -1,2 +1,2 @@
|
|
1 |
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/jszip.min-
|
2 |
-
import{j as t,z as b,n as w,v as Fe,w as De,x as Oe,B as Le,r as o,D as Me,N as Re,_ as de,L as Je,F as ze,G as We}from"./index-C_hAuRbb.js";import{u as Ue}from"./useAdmin-B_kWg5HW.js";import{F as Be,E as He}from"./ExportModal-DJ-UV1s7.js";const Ve="_paginatorContainer_1l5ti_1",Ae="_paginationControls_1l5ti_19",me={paginatorContainer:Ve,paginationControls:Ae};function Ge({currentPage:N,totalPages:h,totalItems:X,itemsPerPage:U,onPageChange:C,className:S=""}){if(h<=1)return null;const u=(()=>{const g=[];if(h<=5)for(let f=1;f<=h;f++)g.push(f);else{let f=Math.max(1,N-2),_=Math.min(h,f+5-1);_===h&&(f=Math.max(1,_-5+1));for(let y=f;y<=_;y++)g.push(y)}return g})();return t.jsx("div",{className:`${me.paginatorContainer} ${S}`,children:t.jsxs("div",{className:me.paginationControls,children:[t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsxs(w,{name:"prev-page",variant:"tertiary",size:1,onClick:()=>C(Math.max(1,N-1)),disabled:N===1,title:"Previous page",children:[t.jsx(Fe,{className:"w-4 h-4"}),t.jsx("span",{className:"hidden sm:inline",children:"Previous"})]})}),t.jsxs("div",{className:"flex items-center gap-1",children:[u[0]>1&&t.jsxs(t.Fragment,{children:[t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:"page-1",variant:"tertiary",size:1,onClick:()=>C(1),children:"1"})}),u[0]>2&&t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx("span",{className:"px-2 text-gray-500",children:"..."})})]}),u.map(g=>t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:`page-${g}`,variant:N===g?"primary":"tertiary",size:1,onClick:()=>C(g),children:g})},g)),u[u.length-1]<h&&t.jsxs(t.Fragment,{children:[u[u.length-1]<h-1&&t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx("span",{className:"px-2 text-gray-500",children:"..."})}),t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:`page-${h}`,variant:"tertiary",size:1,onClick:()=>C(h),children:h})})]})]}),t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsxs(w,{name:"next-page",variant:"tertiary",size:1,onClick:()=>C(Math.min(h,N+1)),disabled:N===h,title:"Next page",children:[t.jsx("span",{className:"hidden sm:inline",children:"Next"}),t.jsx(De,{className:"w-4 h-4"})]})})]})})}const Ze="_tabSelector_o9y1f_1",qe="_metadataTags_o9y1f_8",Ke="_metadataTag_o9y1f_8",Qe="_metadataTagSource_o9y1f_32",Xe="_metadataTagType_o9y1f_43",Ye="_mapItem_o9y1f_54",et="_mapItemImage_o9y1f_72",tt="_mapItemContent_o9y1f_92",at="_mapItemTitle_o9y1f_97",st="_mapItemMetadata_o9y1f_105",it="_fullSizeModalOverlay_o9y1f_134",nt="_fullSizeModalContent_o9y1f_148",lt="_ratingWarningContent_o9y1f_159",rt="_ratingWarningTitle_o9y1f_165",ot="_ratingWarningText_o9y1f_172",ct="_ratingWarningButtons_o9y1f_179",m={tabSelector:Ze,metadataTags:qe,metadataTag:Ke,metadataTagSource:Qe,metadataTagType:Xe,mapItem:Ye,mapItemImage:et,mapItemContent:tt,mapItemTitle:at,mapItemMetadata:st,fullSizeModalOverlay:it,fullSizeModalContent:nt,ratingWarningContent:lt,ratingWarningTitle:rt,ratingWarningText:ot,ratingWarningButtons:ct};function pt(){const N=Oe(),h=Le(),{isAuthenticated:X}=Ue(),[U,C]=o.useState("explore"),[S,M]=o.useState([]),{search:u,srcFilter:g,catFilter:T,regionFilter:f,countryFilter:_,imageTypeFilter:y,uploadTypeFilter:$,showReferenceExamples:E,setShowReferenceExamples:ge}=Me(),[B,pe]=o.useState([]),[H,ue]=o.useState([]),[Y,fe]=o.useState([]),[he,xe]=o.useState([]),[ee,_e]=o.useState([]),[ye,te]=o.useState(!0),[R,ae]=o.useState(!0),[je,V]=o.useState(!1),[Ne,A]=o.useState(!1),[ve,G]=o.useState(!1),[be,J]=o.useState(!1),[z,se]=o.useState(""),[Z,ie]=o.useState(!1),[W,ne]=o.useState(1),[q]=o.useState(10),[K,le]=o.useState(0),[we,re]=o.useState(0),Se=[{key:"explore",label:"List"},{key:"mapDetails",label:"Carousel"}],oe=()=>{ae(!0);const e=new URLSearchParams({page:W.toString(),limit:q.toString()});u&&e.append("search",u),g&&e.append("source",g),T&&e.append("event_type",T),f&&e.append("region",f),_&&e.append("country",_),y&&e.append("image_type",y),$&&e.append("upload_type",$),E&&e.append("starred_only","true"),fetch(`/api/images/grouped?${e.toString()}`).then(s=>s.ok?s.json():(console.error("ExplorePage: Grouped endpoint failed, trying legacy endpoint"),fetch("/api/captions/legacy").then(c=>c.ok?c.json():(console.error("ExplorePage: Legacy endpoint failed, trying regular images endpoint"),fetch("/api/images").then(j=>{if(!j.ok)throw new Error(`HTTP ${j.status}: ${j.statusText}`);return j.json()}))))).then(s=>{console.log("ExplorePage: Fetched captions:",s),M(s)}).catch(s=>{console.error("ExplorePage: Error fetching captions:",s),M([])}).finally(()=>{ae(!1)})},Te=()=>{const e=new URLSearchParams;u&&e.append("search",u),g&&e.append("source",g),T&&e.append("event_type",T),f&&e.append("region",f),_&&e.append("country",_),y&&e.append("image_type",y),$&&e.append("upload_type",$),E&&e.append("starred_only","true"),fetch(`/api/images/grouped/count?${e.toString()}`).then(s=>s.ok?s.json():(console.error("ExplorePage: Count endpoint failed"),{total_count:0})).then(s=>{console.log("ExplorePage: Total count:",s.total_count),le(s.total_count),re(Math.ceil(s.total_count/q))}).catch(s=>{console.error("ExplorePage: Error fetching total count:",s),le(0),re(0)})};o.useEffect(()=>{oe(),Te()},[W,u,g,T,f,_,y,$,E]),o.useEffect(()=>{W!==1&&ne(1)},[u,g,T,f,_,y,$,E]),o.useEffect(()=>{const e=()=>{document.hidden||oe()};return document.addEventListener("visibilitychange",e),()=>{document.removeEventListener("visibilitychange",e)}},[]),o.useEffect(()=>{new URLSearchParams(h.search).get("export")==="true"&&(V(!0),N("/explore",{replace:!0}))},[h.search,N,u,g,T,f,_,y,E]),o.useEffect(()=>{te(!0),Promise.all([fetch("/api/sources").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/types").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/regions").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/countries").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/image-types").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()})]).then(([e,s,c,j,P])=>{pe(e),ue(s),fe(c),xe(j),_e(P)}).catch(()=>{}).finally(()=>{te(!1)})},[]);const I=S,Ee=async(e,s="fine-tuning")=>{if(e.length===0){alert("No images to export");return}A(!0),G(!1);try{const c=(await We(async()=>{const{default:i}=await import("./jszip.min-B2-9j7XO.js").then(k=>k.j);return{default:i}},__vite__mapDeps([0,1,2]))).default,j=new c,P=e.filter(i=>i.image_type==="crisis_map"),O=e.filter(i=>i.image_type==="drone_image");if(P.length>0){const i=j.folder("crisis_maps_dataset"),k=i?.folder("images");if(k){let F=1;for(const a of P)try{const v=a.image_count&&a.image_count>1?a.all_image_ids||[a.image_id]:[a.image_id],Q=v.map(async(n,x)=>{try{const l=await fetch(`/api/images/${n}/file`);if(!l.ok)throw new Error(`Failed to fetch image ${n}`);const r=await l.blob(),d=a.file_key.split(".").pop()||"jpg",p=`${String(F).padStart(4,"0")}_${String(x+1).padStart(2,"0")}.${d}`;return k.file(p,r),{success:!0,fileName:p,imageId:n}}catch(l){return console.error(`Failed to process image ${n}:`,l),{success:!1,fileName:"",imageId:n}}}),D=(await Promise.all(Q)).filter(n=>n.success);if(D.length>0){if(s==="fine-tuning"){const n=D.map(r=>`images/${r.fileName}`),x=Math.random(),l={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};if(!i)continue;if(x<.8){const r=i.file("train.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("train.jsonl",JSON.stringify(d,null,2))}else i.file("train.jsonl",JSON.stringify([l],null,2))}else if(x<.9){const r=i.file("test.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("test.jsonl",JSON.stringify(d,null,2))}else i.file("test.jsonl",JSON.stringify([l],null,2))}else{const r=i.file("val.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("val.jsonl",JSON.stringify(d,null,2))}else i.file("val.jsonl",JSON.stringify([l],null,2))}}else{const n=D.map(l=>`images/${l.fileName}`),x={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};i&&i.file(`${String(F).padStart(4,"0")}.json`,JSON.stringify(x,null,2))}F++}}catch(v){console.error(`Failed to process caption ${a.image_id}:`,v)}}}if(O.length>0){const i=j.folder("drone_images_dataset"),k=i?.folder("images");if(k){let F=1;for(const a of O)try{const v=a.image_count&&a.image_count>1?a.all_image_ids||[a.image_id]:[a.image_id],Q=v.map(async(n,x)=>{try{const l=await fetch(`/api/images/${n}/file`);if(!l.ok)throw new Error(`Failed to fetch image ${n}`);const r=await l.blob(),d=a.file_key.split(".").pop()||"jpg",p=`${String(F).padStart(4,"0")}_${String(x+1).padStart(2,"0")}.${d}`;return k.file(p,r),{success:!0,fileName:p,imageId:n}}catch(l){return console.error(`Failed to process image ${n}:`,l),{success:!1,fileName:"",imageId:n}}}),D=(await Promise.all(Q)).filter(n=>n.success);if(D.length>0){if(s==="fine-tuning"){const n=D.map(r=>`images/${r.fileName}`),x=Math.random(),l={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};if(!i)continue;if(x<.8){const r=i.file("train.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("train.jsonl",JSON.stringify(d,null,2))}else i.file("train.jsonl",JSON.stringify([l],null,2))}else if(x<.9){const r=i.file("test.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("test.jsonl",JSON.stringify(d,null,2))}else i.file("test.jsonl",JSON.stringify([l],null,2))}else{const r=i.file("val.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("val.jsonl",JSON.stringify(d,null,2))}else i.file("val.jsonl",JSON.stringify([l],null,2))}}else{const n=D.map(l=>`images/${l.fileName}`),x={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};i&&i.file(`${String(F).padStart(4,"0")}.json`,JSON.stringify(x,null,2))}F++}}catch(v){console.error(`Failed to process caption ${a.image_id}:`,v)}}}const $e=await j.generateAsync({type:"blob"}),ce=URL.createObjectURL($e),L=document.createElement("a");L.href=ce,L.download=`datasets_${s}_${new Date().toISOString().split("T")[0]}.zip`,document.body.appendChild(L),L.click(),document.body.removeChild(L),URL.revokeObjectURL(ce);const Pe=(P.length||0)+(O.length||0);console.log(`Exported ${s} datasets with ${Pe} total images:`),P.length>0&&console.log(`- Crisis maps: ${P.length} images`),O.length>0&&console.log(`- Drone images: ${O.length} images`),G(!0)}catch(c){console.error("Export failed:",c),alert("Failed to export dataset. Please try again.")}finally{A(!1)}},Ie=e=>{se(e),J(!0)},Ce=async()=>{if(z){ie(!0);try{console.log("Deleting image with ID:",z),(await fetch(`/api/images/${z}`,{method:"DELETE"})).ok?(M(s=>s.filter(c=>c.image_id!==z)),J(!1),se("")):(console.error("Delete failed"),alert("Failed to delete image. Please try again."))}catch(e){console.error("Delete failed:",e),alert("Failed to delete image. Please try again.")}finally{ie(!1)}}};return t.jsxs(Re,{children:[R?t.jsx("div",{className:"flex flex-col items-center justify-center min-h-[60vh]",children:t.jsxs("div",{className:"flex flex-col items-center gap-4",children:[t.jsx(de,{className:"text-ifrcRed"}),t.jsx("div",{children:"Loading examples..."})]})}):t.jsxs("div",{className:"max-w-7xl mx-auto",children:[t.jsxs("div",{className:m.tabSelector,children:[t.jsx(Je,{name:"explore-view",value:U,onChange:e=>{(e==="explore"||e==="mapDetails")&&(C(e),e==="mapDetails"&&S.length>0&&(S[0]?.image_id&&S[0].image_id!=="undefined"&&S[0].image_id!=="null"?N(`/map/${S[0].image_id}`):console.error("Invalid image_id for navigation:",S[0]?.image_id)))},options:Se,keySelector:e=>e.key,labelSelector:e=>e.label}),t.jsxs("div",{className:"flex items-center gap-2 ml-auto",children:[t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsxs(w,{name:"reference-examples",variant:E?"primary":"secondary",onClick:()=>ge(!E),className:"whitespace-nowrap",children:[t.jsx("span",{className:"mr-2",children:E?t.jsx("span",{className:"text-yellow-400",children:"β
"}):t.jsx("span",{className:"text-yellow-400",children:"β"})}),"Reference Examples"]})}),t.jsx(w,{name:"export-dataset",variant:"secondary",onClick:()=>V(!0),children:"Export"})]})]}),U==="explore"?t.jsxs("div",{className:"space-y-6",children:[t.jsx("div",{className:"mb-6 space-y-4",children:t.jsx("div",{className:"flex flex-wrap items-center gap-4",children:t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2 flex-1 min-w-[300px]",children:t.jsx(Be,{sources:B,types:H,regions:Y,countries:he,imageTypes:ee,isLoadingFilters:ye})})})}),t.jsxs("div",{className:"space-y-4",children:[t.jsx("div",{className:"flex justify-between items-center",children:t.jsxs("p",{className:"text-sm text-gray-600",children:[I.length," of ",K," examples"]})}),R&&t.jsx("div",{className:"text-center py-12",children:t.jsxs("div",{className:"flex flex-col items-center gap-4",children:[t.jsx(de,{className:"text-ifrcRed"}),t.jsx("div",{children:"Loading examples..."})]})}),!R&&t.jsxs("div",{className:"space-y-4",children:[I.map(e=>t.jsxs("div",{className:"flex items-center gap-4",children:[t.jsxs("div",{className:`${m.mapItem} flex-1`,onClick:()=>{console.log("ExplorePage: Clicking on image with ID:",e.image_id),console.log("ExplorePage: Image data:",e),e.image_id&&e.image_id!=="undefined"&&e.image_id!=="null"?(console.log("ExplorePage: Navigating to:",`/map/${e.image_id}`),console.log("ExplorePage: Full navigation URL:",`/#/map/${e.image_id}`),N(`/map/${e.image_id}`)):(console.error("Invalid image_id for navigation:",e.image_id),console.error("Full item data:",JSON.stringify(e,null,2)),alert(`Cannot navigate: Invalid image ID (${e.image_id})`))},children:[t.jsx("div",{className:m.mapItemImage,style:{width:"120px",height:"80px"},children:e.thumbnail_url?t.jsxs(t.Fragment,{children:[console.log("ExplorePage: Using thumbnail for fast loading:",e.thumbnail_url),t.jsx("img",{src:e.thumbnail_url,alt:e.file_key,onError:s=>{console.error("ExplorePage: Thumbnail failed to load, falling back to original:",e.thumbnail_url);const c=s.target;e.image_url?c.src=e.image_url:(c.style.display="none",c.parentElement.innerHTML="Img")},onLoad:()=>console.log("ExplorePage: Thumbnail loaded successfully:",e.thumbnail_url)})]}):e.image_url?t.jsxs(t.Fragment,{children:[console.log("ExplorePage: No thumbnail available, using original image:",e.image_url),t.jsx("img",{src:e.image_url,alt:e.file_key,onError:s=>{console.error("ExplorePage: Original image failed to load:",e.image_url);const c=s.target;c.style.display="none",c.parentElement.innerHTML="Img"},onLoad:()=>console.log("ExplorePage: Original image loaded successfully:",e.image_url)})]}):t.jsxs(t.Fragment,{children:[console.log("ExplorePage: No image_url or thumbnail provided for item:",e),"'Img'"]})}),t.jsxs("div",{className:m.mapItemContent,children:[t.jsx("h3",{className:m.mapItemTitle,children:t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{children:e.title||"Untitled"}),e.starred&&t.jsx("span",{className:"text-red-500 text-lg",title:"Starred image",children:"β
"})]})}),t.jsx("div",{className:m.mapItemMetadata,children:t.jsxs("div",{className:m.metadataTags,children:[e.image_type!=="drone_image"&&t.jsx("span",{className:m.metadataTagSource,children:e.source&&e.source.includes(", ")?e.source.split(", ").map(s=>B.find(c=>c.s_code===s.trim())?.label||s.trim()).join(", "):B.find(s=>s.s_code===e.source)?.label||e.source}),t.jsx("span",{className:m.metadataTagType,children:e.event_type&&e.event_type.includes(", ")?e.event_type.split(", ").map(s=>H.find(c=>c.t_code===s.trim())?.label||s.trim()).join(", "):H.find(s=>s.t_code===e.event_type)?.label||e.event_type}),t.jsx("span",{className:m.metadataTag,children:ee.find(s=>s.image_type===e.image_type)?.label||e.image_type}),e.image_count&&e.image_count>1&&t.jsxs("span",{className:m.metadataTag,title:`Multi-upload with ${e.image_count} images`,children:["π· ",e.image_count]}),(!e.image_count||e.image_count<=1)&&t.jsx("span",{className:m.metadataTag,title:"Single Upload",children:"Single"}),e.countries&&e.countries.length>0&&t.jsxs(t.Fragment,{children:[t.jsx("span",{className:m.metadataTag,children:Y.find(s=>s.r_code===e.countries[0].r_code)?.label||"Unknown Region"}),t.jsx("span",{className:m.metadataTag,children:e.countries.map(s=>s.label).join(", ")})]})]})})]})]}),X&&t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:`delete-${e.image_id}`,variant:"tertiary",size:1,className:"bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 hover:border-red-300",onClick:()=>Ie(e.image_id),title:"Delete","aria-label":"Delete saved image",children:t.jsx(ze,{className:"w-4 h-4"})})})]},e.image_id)),!I.length&&t.jsx("div",{className:"text-center py-12",children:t.jsx("p",{className:"text-gray-500",children:"No examples found."})}),!R&&I.length>0&&t.jsx(Ge,{currentPage:W,totalPages:we,totalItems:K,itemsPerPage:q,onPageChange:ne})]})]})]}):t.jsx("div",{className:"space-y-6",children:t.jsxs("div",{className:"text-center py-12",children:[t.jsx("p",{className:"text-gray-500",children:"Map Details view coming soon..."}),t.jsx("p",{className:"text-sm text-gray-400 mt-2",children:"This will show detailed information about individual maps"})]})})]}),be&&t.jsx("div",{className:m.fullSizeModalOverlay,onClick:()=>J(!1),children:t.jsx("div",{className:m.fullSizeModalContent,onClick:e=>e.stopPropagation(),children:t.jsxs("div",{className:m.ratingWarningContent,children:[t.jsx("h3",{className:m.ratingWarningTitle,children:"Delete Image?"}),t.jsx("p",{className:m.ratingWarningText,children:"This action cannot be undone. Are you sure you want to delete this saved image and all related data?"}),t.jsxs("div",{className:m.ratingWarningButtons,children:[t.jsx(w,{name:"confirm-delete",variant:"secondary",onClick:Ce,disabled:Z,children:Z?"Deleting...":"Delete"}),t.jsx(w,{name:"cancel-delete",variant:"tertiary",onClick:()=>J(!1),disabled:Z,children:"Cancel"})]})]})})}),t.jsx(He,{isOpen:je,onClose:()=>{V(!1),G(!1),A(!1)},onExport:(e,s)=>{const c=I.filter(j=>s.includes(j.image_type));Ee(c,e)},filteredCount:I.length,totalCount:K,hasFilters:!!(u||g||T||f||_||y||$||E),crisisMapsCount:I.filter(e=>e.image_type==="crisis_map").length,droneImagesCount:I.filter(e=>e.image_type==="drone_image").length,isLoading:Ne,exportSuccess:ve})]})}export{pt as default};
|
|
|
1 |
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/jszip.min-C3B0d4UO.js","assets/index-BCz4bkWK.js","assets/index-FBu17hMI.css"])))=>i.map(i=>d[i]);
|
2 |
+
import{j as t,z as b,n as w,v as Fe,w as De,x as Oe,B as Le,r as o,D as Me,N as Re,_ as de,L as Je,F as ze,G as We}from"./index-BCz4bkWK.js";import{u as Ue}from"./useAdmin-LSEnRjVv.js";import{F as Be,E as He}from"./ExportModal-Qf6Y7VAO.js";const Ve="_paginatorContainer_1l5ti_1",Ae="_paginationControls_1l5ti_19",me={paginatorContainer:Ve,paginationControls:Ae};function Ge({currentPage:N,totalPages:h,totalItems:X,itemsPerPage:U,onPageChange:C,className:S=""}){if(h<=1)return null;const u=(()=>{const g=[];if(h<=5)for(let f=1;f<=h;f++)g.push(f);else{let f=Math.max(1,N-2),_=Math.min(h,f+5-1);_===h&&(f=Math.max(1,_-5+1));for(let y=f;y<=_;y++)g.push(y)}return g})();return t.jsx("div",{className:`${me.paginatorContainer} ${S}`,children:t.jsxs("div",{className:me.paginationControls,children:[t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsxs(w,{name:"prev-page",variant:"tertiary",size:1,onClick:()=>C(Math.max(1,N-1)),disabled:N===1,title:"Previous page",children:[t.jsx(Fe,{className:"w-4 h-4"}),t.jsx("span",{className:"hidden sm:inline",children:"Previous"})]})}),t.jsxs("div",{className:"flex items-center gap-1",children:[u[0]>1&&t.jsxs(t.Fragment,{children:[t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:"page-1",variant:"tertiary",size:1,onClick:()=>C(1),children:"1"})}),u[0]>2&&t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx("span",{className:"px-2 text-gray-500",children:"..."})})]}),u.map(g=>t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:`page-${g}`,variant:N===g?"primary":"tertiary",size:1,onClick:()=>C(g),children:g})},g)),u[u.length-1]<h&&t.jsxs(t.Fragment,{children:[u[u.length-1]<h-1&&t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx("span",{className:"px-2 text-gray-500",children:"..."})}),t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:`page-${h}`,variant:"tertiary",size:1,onClick:()=>C(h),children:h})})]})]}),t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsxs(w,{name:"next-page",variant:"tertiary",size:1,onClick:()=>C(Math.min(h,N+1)),disabled:N===h,title:"Next page",children:[t.jsx("span",{className:"hidden sm:inline",children:"Next"}),t.jsx(De,{className:"w-4 h-4"})]})})]})})}const Ze="_tabSelector_o9y1f_1",qe="_metadataTags_o9y1f_8",Ke="_metadataTag_o9y1f_8",Qe="_metadataTagSource_o9y1f_32",Xe="_metadataTagType_o9y1f_43",Ye="_mapItem_o9y1f_54",et="_mapItemImage_o9y1f_72",tt="_mapItemContent_o9y1f_92",at="_mapItemTitle_o9y1f_97",st="_mapItemMetadata_o9y1f_105",it="_fullSizeModalOverlay_o9y1f_134",nt="_fullSizeModalContent_o9y1f_148",lt="_ratingWarningContent_o9y1f_159",rt="_ratingWarningTitle_o9y1f_165",ot="_ratingWarningText_o9y1f_172",ct="_ratingWarningButtons_o9y1f_179",m={tabSelector:Ze,metadataTags:qe,metadataTag:Ke,metadataTagSource:Qe,metadataTagType:Xe,mapItem:Ye,mapItemImage:et,mapItemContent:tt,mapItemTitle:at,mapItemMetadata:st,fullSizeModalOverlay:it,fullSizeModalContent:nt,ratingWarningContent:lt,ratingWarningTitle:rt,ratingWarningText:ot,ratingWarningButtons:ct};function pt(){const N=Oe(),h=Le(),{isAuthenticated:X}=Ue(),[U,C]=o.useState("explore"),[S,M]=o.useState([]),{search:u,srcFilter:g,catFilter:T,regionFilter:f,countryFilter:_,imageTypeFilter:y,uploadTypeFilter:$,showReferenceExamples:E,setShowReferenceExamples:ge}=Me(),[B,pe]=o.useState([]),[H,ue]=o.useState([]),[Y,fe]=o.useState([]),[he,xe]=o.useState([]),[ee,_e]=o.useState([]),[ye,te]=o.useState(!0),[R,ae]=o.useState(!0),[je,V]=o.useState(!1),[Ne,A]=o.useState(!1),[ve,G]=o.useState(!1),[be,J]=o.useState(!1),[z,se]=o.useState(""),[Z,ie]=o.useState(!1),[W,ne]=o.useState(1),[q]=o.useState(10),[K,le]=o.useState(0),[we,re]=o.useState(0),Se=[{key:"explore",label:"List"},{key:"mapDetails",label:"Carousel"}],oe=()=>{ae(!0);const e=new URLSearchParams({page:W.toString(),limit:q.toString()});u&&e.append("search",u),g&&e.append("source",g),T&&e.append("event_type",T),f&&e.append("region",f),_&&e.append("country",_),y&&e.append("image_type",y),$&&e.append("upload_type",$),E&&e.append("starred_only","true"),fetch(`/api/images/grouped?${e.toString()}`).then(s=>s.ok?s.json():(console.error("ExplorePage: Grouped endpoint failed, trying legacy endpoint"),fetch("/api/captions/legacy").then(c=>c.ok?c.json():(console.error("ExplorePage: Legacy endpoint failed, trying regular images endpoint"),fetch("/api/images").then(j=>{if(!j.ok)throw new Error(`HTTP ${j.status}: ${j.statusText}`);return j.json()}))))).then(s=>{console.log("ExplorePage: Fetched captions:",s),M(s)}).catch(s=>{console.error("ExplorePage: Error fetching captions:",s),M([])}).finally(()=>{ae(!1)})},Te=()=>{const e=new URLSearchParams;u&&e.append("search",u),g&&e.append("source",g),T&&e.append("event_type",T),f&&e.append("region",f),_&&e.append("country",_),y&&e.append("image_type",y),$&&e.append("upload_type",$),E&&e.append("starred_only","true"),fetch(`/api/images/grouped/count?${e.toString()}`).then(s=>s.ok?s.json():(console.error("ExplorePage: Count endpoint failed"),{total_count:0})).then(s=>{console.log("ExplorePage: Total count:",s.total_count),le(s.total_count),re(Math.ceil(s.total_count/q))}).catch(s=>{console.error("ExplorePage: Error fetching total count:",s),le(0),re(0)})};o.useEffect(()=>{oe(),Te()},[W,u,g,T,f,_,y,$,E]),o.useEffect(()=>{W!==1&&ne(1)},[u,g,T,f,_,y,$,E]),o.useEffect(()=>{const e=()=>{document.hidden||oe()};return document.addEventListener("visibilitychange",e),()=>{document.removeEventListener("visibilitychange",e)}},[]),o.useEffect(()=>{new URLSearchParams(h.search).get("export")==="true"&&(V(!0),N("/explore",{replace:!0}))},[h.search,N,u,g,T,f,_,y,E]),o.useEffect(()=>{te(!0),Promise.all([fetch("/api/sources").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/types").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/regions").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/countries").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()}),fetch("/api/image-types").then(e=>{if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);return e.json()})]).then(([e,s,c,j,P])=>{pe(e),ue(s),fe(c),xe(j),_e(P)}).catch(()=>{}).finally(()=>{te(!1)})},[]);const I=S,Ee=async(e,s="fine-tuning")=>{if(e.length===0){alert("No images to export");return}A(!0),G(!1);try{const c=(await We(async()=>{const{default:i}=await import("./jszip.min-C3B0d4UO.js").then(k=>k.j);return{default:i}},__vite__mapDeps([0,1,2]))).default,j=new c,P=e.filter(i=>i.image_type==="crisis_map"),O=e.filter(i=>i.image_type==="drone_image");if(P.length>0){const i=j.folder("crisis_maps_dataset"),k=i?.folder("images");if(k){let F=1;for(const a of P)try{const v=a.image_count&&a.image_count>1?a.all_image_ids||[a.image_id]:[a.image_id],Q=v.map(async(n,x)=>{try{const l=await fetch(`/api/images/${n}/file`);if(!l.ok)throw new Error(`Failed to fetch image ${n}`);const r=await l.blob(),d=a.file_key.split(".").pop()||"jpg",p=`${String(F).padStart(4,"0")}_${String(x+1).padStart(2,"0")}.${d}`;return k.file(p,r),{success:!0,fileName:p,imageId:n}}catch(l){return console.error(`Failed to process image ${n}:`,l),{success:!1,fileName:"",imageId:n}}}),D=(await Promise.all(Q)).filter(n=>n.success);if(D.length>0){if(s==="fine-tuning"){const n=D.map(r=>`images/${r.fileName}`),x=Math.random(),l={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};if(!i)continue;if(x<.8){const r=i.file("train.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("train.jsonl",JSON.stringify(d,null,2))}else i.file("train.jsonl",JSON.stringify([l],null,2))}else if(x<.9){const r=i.file("test.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("test.jsonl",JSON.stringify(d,null,2))}else i.file("test.jsonl",JSON.stringify([l],null,2))}else{const r=i.file("val.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("val.jsonl",JSON.stringify(d,null,2))}else i.file("val.jsonl",JSON.stringify([l],null,2))}}else{const n=D.map(l=>`images/${l.fileName}`),x={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};i&&i.file(`${String(F).padStart(4,"0")}.json`,JSON.stringify(x,null,2))}F++}}catch(v){console.error(`Failed to process caption ${a.image_id}:`,v)}}}if(O.length>0){const i=j.folder("drone_images_dataset"),k=i?.folder("images");if(k){let F=1;for(const a of O)try{const v=a.image_count&&a.image_count>1?a.all_image_ids||[a.image_id]:[a.image_id],Q=v.map(async(n,x)=>{try{const l=await fetch(`/api/images/${n}/file`);if(!l.ok)throw new Error(`Failed to fetch image ${n}`);const r=await l.blob(),d=a.file_key.split(".").pop()||"jpg",p=`${String(F).padStart(4,"0")}_${String(x+1).padStart(2,"0")}.${d}`;return k.file(p,r),{success:!0,fileName:p,imageId:n}}catch(l){return console.error(`Failed to process image ${n}:`,l),{success:!1,fileName:"",imageId:n}}}),D=(await Promise.all(Q)).filter(n=>n.success);if(D.length>0){if(s==="fine-tuning"){const n=D.map(r=>`images/${r.fileName}`),x=Math.random(),l={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};if(!i)continue;if(x<.8){const r=i.file("train.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("train.jsonl",JSON.stringify(d,null,2))}else i.file("train.jsonl",JSON.stringify([l],null,2))}else if(x<.9){const r=i.file("test.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("test.jsonl",JSON.stringify(d,null,2))}else i.file("test.jsonl",JSON.stringify([l],null,2))}else{const r=i.file("val.jsonl");if(r){const d=await r.async("string").then(p=>JSON.parse(p||"[]")).catch(()=>[]);d.push(l),i.file("val.jsonl",JSON.stringify(d,null,2))}else i.file("val.jsonl",JSON.stringify([l],null,2))}}else{const n=D.map(l=>`images/${l.fileName}`),x={image:n.length===1?n[0]:n,caption:a.edited||a.generated||"",metadata:{image_id:v,title:a.title,source:a.source,event_type:a.event_type,image_type:a.image_type,countries:a.countries,starred:a.starred,image_count:a.image_count||1}};i&&i.file(`${String(F).padStart(4,"0")}.json`,JSON.stringify(x,null,2))}F++}}catch(v){console.error(`Failed to process caption ${a.image_id}:`,v)}}}const $e=await j.generateAsync({type:"blob"}),ce=URL.createObjectURL($e),L=document.createElement("a");L.href=ce,L.download=`datasets_${s}_${new Date().toISOString().split("T")[0]}.zip`,document.body.appendChild(L),L.click(),document.body.removeChild(L),URL.revokeObjectURL(ce);const Pe=(P.length||0)+(O.length||0);console.log(`Exported ${s} datasets with ${Pe} total images:`),P.length>0&&console.log(`- Crisis maps: ${P.length} images`),O.length>0&&console.log(`- Drone images: ${O.length} images`),G(!0)}catch(c){console.error("Export failed:",c),alert("Failed to export dataset. Please try again.")}finally{A(!1)}},Ie=e=>{se(e),J(!0)},Ce=async()=>{if(z){ie(!0);try{console.log("Deleting image with ID:",z),(await fetch(`/api/images/${z}`,{method:"DELETE"})).ok?(M(s=>s.filter(c=>c.image_id!==z)),J(!1),se("")):(console.error("Delete failed"),alert("Failed to delete image. Please try again."))}catch(e){console.error("Delete failed:",e),alert("Failed to delete image. Please try again.")}finally{ie(!1)}}};return t.jsxs(Re,{children:[R?t.jsx("div",{className:"flex flex-col items-center justify-center min-h-[60vh]",children:t.jsxs("div",{className:"flex flex-col items-center gap-4",children:[t.jsx(de,{className:"text-ifrcRed"}),t.jsx("div",{children:"Loading examples..."})]})}):t.jsxs("div",{className:"max-w-7xl mx-auto",children:[t.jsxs("div",{className:m.tabSelector,children:[t.jsx(Je,{name:"explore-view",value:U,onChange:e=>{(e==="explore"||e==="mapDetails")&&(C(e),e==="mapDetails"&&S.length>0&&(S[0]?.image_id&&S[0].image_id!=="undefined"&&S[0].image_id!=="null"?N(`/map/${S[0].image_id}`):console.error("Invalid image_id for navigation:",S[0]?.image_id)))},options:Se,keySelector:e=>e.key,labelSelector:e=>e.label}),t.jsxs("div",{className:"flex items-center gap-2 ml-auto",children:[t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsxs(w,{name:"reference-examples",variant:E?"primary":"secondary",onClick:()=>ge(!E),className:"whitespace-nowrap",children:[t.jsx("span",{className:"mr-2",children:E?t.jsx("span",{className:"text-yellow-400",children:"β
"}):t.jsx("span",{className:"text-yellow-400",children:"β"})}),"Reference Examples"]})}),t.jsx(w,{name:"export-dataset",variant:"secondary",onClick:()=>V(!0),children:"Export"})]})]}),U==="explore"?t.jsxs("div",{className:"space-y-6",children:[t.jsx("div",{className:"mb-6 space-y-4",children:t.jsx("div",{className:"flex flex-wrap items-center gap-4",children:t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2 flex-1 min-w-[300px]",children:t.jsx(Be,{sources:B,types:H,regions:Y,countries:he,imageTypes:ee,isLoadingFilters:ye})})})}),t.jsxs("div",{className:"space-y-4",children:[t.jsx("div",{className:"flex justify-between items-center",children:t.jsxs("p",{className:"text-sm text-gray-600",children:[I.length," of ",K," examples"]})}),R&&t.jsx("div",{className:"text-center py-12",children:t.jsxs("div",{className:"flex flex-col items-center gap-4",children:[t.jsx(de,{className:"text-ifrcRed"}),t.jsx("div",{children:"Loading examples..."})]})}),!R&&t.jsxs("div",{className:"space-y-4",children:[I.map(e=>t.jsxs("div",{className:"flex items-center gap-4",children:[t.jsxs("div",{className:`${m.mapItem} flex-1`,onClick:()=>{console.log("ExplorePage: Clicking on image with ID:",e.image_id),console.log("ExplorePage: Image data:",e),e.image_id&&e.image_id!=="undefined"&&e.image_id!=="null"?(console.log("ExplorePage: Navigating to:",`/map/${e.image_id}`),console.log("ExplorePage: Full navigation URL:",`/#/map/${e.image_id}`),N(`/map/${e.image_id}`)):(console.error("Invalid image_id for navigation:",e.image_id),console.error("Full item data:",JSON.stringify(e,null,2)),alert(`Cannot navigate: Invalid image ID (${e.image_id})`))},children:[t.jsx("div",{className:m.mapItemImage,style:{width:"120px",height:"80px"},children:e.thumbnail_url?t.jsxs(t.Fragment,{children:[console.log("ExplorePage: Using thumbnail for fast loading:",e.thumbnail_url),t.jsx("img",{src:e.thumbnail_url,alt:e.file_key,onError:s=>{console.error("ExplorePage: Thumbnail failed to load, falling back to original:",e.thumbnail_url);const c=s.target;e.image_url?c.src=e.image_url:(c.style.display="none",c.parentElement.innerHTML="Img")},onLoad:()=>console.log("ExplorePage: Thumbnail loaded successfully:",e.thumbnail_url)})]}):e.image_url?t.jsxs(t.Fragment,{children:[console.log("ExplorePage: No thumbnail available, using original image:",e.image_url),t.jsx("img",{src:e.image_url,alt:e.file_key,onError:s=>{console.error("ExplorePage: Original image failed to load:",e.image_url);const c=s.target;c.style.display="none",c.parentElement.innerHTML="Img"},onLoad:()=>console.log("ExplorePage: Original image loaded successfully:",e.image_url)})]}):t.jsxs(t.Fragment,{children:[console.log("ExplorePage: No image_url or thumbnail provided for item:",e),"'Img'"]})}),t.jsxs("div",{className:m.mapItemContent,children:[t.jsx("h3",{className:m.mapItemTitle,children:t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{children:e.title||"Untitled"}),e.starred&&t.jsx("span",{className:"text-red-500 text-lg",title:"Starred image",children:"β
"})]})}),t.jsx("div",{className:m.mapItemMetadata,children:t.jsxs("div",{className:m.metadataTags,children:[e.image_type!=="drone_image"&&t.jsx("span",{className:m.metadataTagSource,children:e.source&&e.source.includes(", ")?e.source.split(", ").map(s=>B.find(c=>c.s_code===s.trim())?.label||s.trim()).join(", "):B.find(s=>s.s_code===e.source)?.label||e.source}),t.jsx("span",{className:m.metadataTagType,children:e.event_type&&e.event_type.includes(", ")?e.event_type.split(", ").map(s=>H.find(c=>c.t_code===s.trim())?.label||s.trim()).join(", "):H.find(s=>s.t_code===e.event_type)?.label||e.event_type}),t.jsx("span",{className:m.metadataTag,children:ee.find(s=>s.image_type===e.image_type)?.label||e.image_type}),e.image_count&&e.image_count>1&&t.jsxs("span",{className:m.metadataTag,title:`Multi-upload with ${e.image_count} images`,children:["π· ",e.image_count]}),(!e.image_count||e.image_count<=1)&&t.jsx("span",{className:m.metadataTag,title:"Single Upload",children:"Single"}),e.countries&&e.countries.length>0&&t.jsxs(t.Fragment,{children:[t.jsx("span",{className:m.metadataTag,children:Y.find(s=>s.r_code===e.countries[0].r_code)?.label||"Unknown Region"}),t.jsx("span",{className:m.metadataTag,children:e.countries.map(s=>s.label).join(", ")})]})]})})]})]}),X&&t.jsx(b,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:t.jsx(w,{name:`delete-${e.image_id}`,variant:"tertiary",size:1,className:"bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 hover:border-red-300",onClick:()=>Ie(e.image_id),title:"Delete","aria-label":"Delete saved image",children:t.jsx(ze,{className:"w-4 h-4"})})})]},e.image_id)),!I.length&&t.jsx("div",{className:"text-center py-12",children:t.jsx("p",{className:"text-gray-500",children:"No examples found."})}),!R&&I.length>0&&t.jsx(Ge,{currentPage:W,totalPages:we,totalItems:K,itemsPerPage:q,onPageChange:ne})]})]})]}):t.jsx("div",{className:"space-y-6",children:t.jsxs("div",{className:"text-center py-12",children:[t.jsx("p",{className:"text-gray-500",children:"Map Details view coming soon..."}),t.jsx("p",{className:"text-sm text-gray-400 mt-2",children:"This will show detailed information about individual maps"})]})})]}),be&&t.jsx("div",{className:m.fullSizeModalOverlay,onClick:()=>J(!1),children:t.jsx("div",{className:m.fullSizeModalContent,onClick:e=>e.stopPropagation(),children:t.jsxs("div",{className:m.ratingWarningContent,children:[t.jsx("h3",{className:m.ratingWarningTitle,children:"Delete Image?"}),t.jsx("p",{className:m.ratingWarningText,children:"This action cannot be undone. Are you sure you want to delete this saved image and all related data?"}),t.jsxs("div",{className:m.ratingWarningButtons,children:[t.jsx(w,{name:"confirm-delete",variant:"secondary",onClick:Ce,disabled:Z,children:Z?"Deleting...":"Delete"}),t.jsx(w,{name:"cancel-delete",variant:"tertiary",onClick:()=>J(!1),disabled:Z,children:"Cancel"})]})]})})}),t.jsx(He,{isOpen:je,onClose:()=>{V(!1),G(!1),A(!1)},onExport:(e,s)=>{const c=I.filter(j=>s.includes(j.image_type));Ee(c,e)},filteredCount:I.length,totalCount:K,hasFilters:!!(u||g||T||f||_||y||$||E),crisisMapsCount:I.filter(e=>e.image_type==="crisis_map").length,droneImagesCount:I.filter(e=>e.image_type==="drone_image").length,isLoading:Ne,exportSuccess:ve})]})}export{pt as default};
|
py_backend/static/assets/{index-Dw3gbXdi.js β index-D2ncrm5K.js}
RENAMED
@@ -1,3 +1,3 @@
|
|
1 |
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/jszip.min-
|
2 |
-
import{J as ea,x as aa,j as a,N as Y,n as F,r as m,D as ta,_ as we,L as sa,z as P,v as ie,w as oe,F as ia,K as oa,G as na}from"./index-C_hAuRbb.js";import{u as ra}from"./useAdmin-B_kWg5HW.js";import{F as la,E as ca}from"./ExportModal-DJ-UV1s7.js";const da="_tabSelector_usssr_1",ga="_imageContainer_usssr_12",ma="_imagePlaceholder_usssr_33",ua="_metadataTags_usssr_45",fa="_metadataTag_usssr_45",pa="_captionContainer_usssr_67",ha="_captionText_usssr_74",_a="_gridLayout_usssr_131",xa="_detailsSection_usssr_155",ya="_loadingContainer_usssr_161",va="_errorContainer_usssr_171",wa="_fullSizeModalOverlay_usssr_205",Ca="_fullSizeModalContent_usssr_219",ja="_ratingWarningContent_usssr_230",Ia="_ratingWarningTitle_usssr_236",Na="_ratingWarningText_usssr_243",ba="_ratingWarningButtons_usssr_250",Sa="_carouselContainer_usssr_365",La="_carouselImageWrapper_usssr_370",Da="_carouselImage_usssr_370",ka="_carouselNavigation_usssr_393",Ma="_carouselButton_usssr_405",Ta="_carouselIndicators_usssr_429",Fa="_carouselIndicator_usssr_429",Ea="_carouselIndicatorActive_usssr_458",Pa="_singleImageContainer_usssr_488",Ra="_viewImageButtonContainer_usssr_494",p={tabSelector:da,imageContainer:ga,imagePlaceholder:ma,metadataTags:ua,metadataTag:fa,captionContainer:pa,captionText:ha,gridLayout:_a,detailsSection:xa,loadingContainer:ya,errorContainer:va,fullSizeModalOverlay:wa,fullSizeModalContent:Ca,ratingWarningContent:ja,ratingWarningTitle:Ia,ratingWarningText:Na,ratingWarningButtons:ba,carouselContainer:Sa,carouselImageWrapper:La,carouselImage:Da,carouselNavigation:ka,carouselButton:Ma,carouselIndicators:Ta,carouselIndicator:Fa,carouselIndicatorActive:Ea,singleImageContainer:Pa,viewImageButtonContainer:Ra};function st(){const{mapId:h}=ea(),v=aa(),{isAuthenticated:ne}=ra();console.log("MapDetailsPage: Current URL:",window.location.href),console.log("MapDetailsPage: Hash:",window.location.hash),console.log("MapDetailsPage: mapId from useParams:",h),console.log("MapDetailsPage: mapId type:",typeof h),console.log("MapDetailsPage: mapId length:",h?.length),console.log("MapDetailsPage: mapId value:",JSON.stringify(h));const Ce=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;if(!h||h==="undefined"||h==="null"||h.trim()===""||!Ce.test(h))return a.jsx(Y,{children:a.jsxs("div",{className:"flex flex-col items-center gap-4 text-center py-12",children:[a.jsx("div",{className:"text-4xl",children:"β οΈ"}),a.jsx("div",{className:"text-xl font-semibold",children:"Invalid Map ID"}),a.jsx("div",{children:"The map ID provided is not valid."}),a.jsxs("div",{className:"text-sm text-gray-500 mt-2",children:['Debug Info: mapId = "',h,'" (type: ',typeof h,")"]}),a.jsx(F,{name:"back-to-explore",variant:"secondary",onClick:()=>v("/explore"),children:"Return to Explore"})]})});const[re,je]=m.useState("mapDetails"),[e,ee]=m.useState(null),[W,z]=m.useState(!0),[le,O]=m.useState(null),[ce,Ie]=m.useState([]),[de,Ne]=m.useState([]),[ge,be]=m.useState([]),[me,Se]=m.useState([]),[Le,De]=m.useState([]),[ke,Me]=m.useState(!1),[Te,Fe]=m.useState(!1),[B,q]=m.useState(!1),[Ee,K]=m.useState(!1),[ue,Z]=m.useState(!1),[Pe,ae]=m.useState(!1),[Re,te]=m.useState(!1),[$a,za]=m.useState("standard"),[R,Aa]=m.useState(80),[J,Oa]=m.useState(10),[Ua,Wa]=m.useState(10),[Ba,Ja]=m.useState(!0),[Ha,Va]=m.useState(!0),[Q,X]=m.useState(!1),[$e,fe]=m.useState(!1),[ze,pe]=m.useState(null),[Ae,H]=m.useState(!1),[x,V]=m.useState([]),[M,A]=m.useState(0),[G,he]=m.useState(!1),{search:d,setSearch:Ga,srcFilter:w,setSrcFilter:qa,catFilter:C,setCatFilter:Ka,regionFilter:j,setRegionFilter:Za,countryFilter:I,setCountryFilter:Qa,imageTypeFilter:N,setImageTypeFilter:Xa,uploadTypeFilter:b,setUploadTypeFilter:Ya,showReferenceExamples:k,setShowReferenceExamples:Oe,clearAllFilters:Ue}=ta(),We=[{key:"explore",label:"List"},{key:"mapDetails",label:"Carousel"}],_e=m.useCallback(async t=>{if(console.log("fetchMapData called with id:",t),console.log("fetchMapData id type:",typeof t),!t||t==="undefined"||t==="null"||t.trim()===""){console.log("fetchMapData: Invalid ID detected:",t),O("Invalid Map ID"),z(!1);return}if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)){console.log("fetchMapData: Invalid UUID format:",t),O("Invalid Map ID format"),z(!1);return}console.log("fetchMapData: Making API call for id:",t),q(!0),z(!0);try{const u=await fetch(`/api/images/${t}`);if(!u.ok)throw new Error("Map not found");const s=await u.json();if(ee(s),s.all_image_ids&&s.all_image_ids.length>1)await xe(s.all_image_ids);else if(s.image_count&&s.image_count>1){console.log("Multi-upload detected but no all_image_ids, trying grouped endpoint");try{const l=await fetch("/api/images/grouped");if(l.ok){const r=(await l.json()).find(c=>c.all_image_ids&&c.all_image_ids.includes(s.image_id));r&&r.all_image_ids?await xe(r.all_image_ids):(V([s]),A(0))}else V([s]),A(0)}catch(l){console.error("Failed to fetch from grouped endpoint:",l),V([s]),A(0)}}else V([s]),A(0);await se(t)}catch(u){O(u instanceof Error?u.message:"Unknown error occurred")}finally{z(!1),q(!1)}},[]),xe=m.useCallback(async t=>{console.log("fetchAllImages called with imageIds:",t),he(!0);try{const i=t.map(async s=>{const l=await fetch(`/api/images/${s}`);if(!l.ok)throw new Error(`Failed to fetch image ${s}`);return l.json()}),u=await Promise.all(i);V(u),A(0),console.log("fetchAllImages: Loaded",u.length,"images")}catch(i){console.error("fetchAllImages error:",i),O(i instanceof Error?i.message:"Failed to load all images")}finally{he(!1)}},[]),Be=m.useCallback(()=>{x.length>1&&A(t=>t>0?t-1:x.length-1)},[x.length]),Je=m.useCallback(()=>{x.length>1&&A(t=>t<x.length-1?t+1:0)},[x.length]),He=m.useCallback(t=>{t>=0&&t<x.length&&A(t)},[x.length]),ye=m.useCallback(async t=>{const i=t||(x.length>0?x[M]:e);if(i){H(!0),pe(i),fe(!0);try{const u=new Image;u.onload=()=>{H(!1)},u.onerror=()=>{H(!1)},u.src=i.image_url}catch(u){console.error("Error preloading full-size image:",u),H(!1)}}},[x,M,e]),Ve=m.useCallback(()=>{fe(!1),pe(null),H(!1)},[]);m.useEffect(()=>{if(console.log("MapDetailsPage: mapId from useParams:",h),console.log("MapDetailsPage: mapId type:",typeof h),console.log("MapDetailsPage: mapId value:",h),!h||h==="undefined"||h==="null"||h.trim()===""||h===void 0||h===null){console.log("MapDetailsPage: Invalid mapId, setting error"),O("Map ID is required"),z(!1);return}if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(h)){console.log("MapDetailsPage: Invalid UUID format:",h),O("Invalid Map ID format"),z(!1);return}console.log("MapDetailsPage: Fetching data for mapId:",h),_e(h)},[h,_e]),m.useEffect(()=>{if(!e||W||Q)return;if(!h||h==="undefined"||h==="null"||h.trim()===""){console.log("Auto-navigation skipped: Invalid mapId");return}if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(h)){console.log("Auto-navigation skipped: Invalid mapId format");return}(()=>{const u=!d||e.title?.toLowerCase().includes(d.toLowerCase())||e.generated?.toLowerCase().includes(d.toLowerCase())||e.source?.toLowerCase().includes(d.toLowerCase())||e.event_type?.toLowerCase().includes(d.toLowerCase()),s=!w||e.source===w,l=!C||e.event_type===C,o=!j||e.countries.some(T=>T.r_code===j),r=!I||e.countries.some(T=>T.c_code===I),c=!N||e.image_type===N,n=!k||e.starred===!0,y=u&&s&&l&&o&&r&&c&&n;return console.log("Auto-navigation check:",{mapId:h,search:d,srcFilter:w,catFilter:C,regionFilter:j,countryFilter:I,imageTypeFilter:N,showReferenceExamples:k,matchesSearch:u,matchesSource:s,matchesCategory:l,matchesRegion:o,matchesCountry:r,matchesImageType:c,matchesReferenceExamples:n,matches:y}),y})()||(console.log("Current map does not match filters, looking for first matching item"),fetch("/api/images").then(u=>u.json()).then(u=>{console.log("Auto-navigation: Received images from API:",u.length),console.log("Auto-navigation: First few images:",u.slice(0,3).map(l=>({image_id:l.image_id,title:l.title})));const s=u.find(l=>{const o=!d||l.title?.toLowerCase().includes(d.toLowerCase())||l.generated?.toLowerCase().includes(d.toLowerCase())||l.source?.toLowerCase().includes(d.toLowerCase())||l.event_type?.toLowerCase().includes(d.toLowerCase()),r=!w||l.source===w,c=!C||l.event_type===C,n=!j||l.countries?.some(f=>f.r_code===j),y=!I||l.countries?.some(f=>f.c_code===I),T=!N||l.image_type===N,L=!k||l.starred===!0;return o&&r&&c&&n&&y&&T&&L});console.log("Auto-navigation: Found first matching image:",s?{image_id:s.image_id,title:s.title,source:s.source}:"No matching image found"),s&&s.image_id&&s.image_id!=="undefined"&&s.image_id!=="null"&&s.image_id.trim()!==""&&s.image_id!==h&&(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s.image_id)?(console.log("Auto-navigating to:",s.image_id),v(`/map/${s.image_id}`)):console.error("Auto-navigation blocked: Invalid image_id format:",s.image_id))}).catch(console.error))},[e,d,w,C,j,I,N,k,h,v,W,Q]);const se=async t=>{if(!(!t||t==="undefined"||t==="null"||t.trim()===""))try{const i=await fetch("/api/images/grouped");if(i.ok){const s=(await i.json()).filter(o=>{const r=!d||o.title?.toLowerCase().includes(d.toLowerCase())||o.generated?.toLowerCase().includes(d.toLowerCase())||o.source?.toLowerCase().includes(d.toLowerCase())||o.event_type?.toLowerCase().includes(d.toLowerCase()),c=!w||o.source===w,n=!C||o.event_type===C,y=!j||o.countries?.some(_=>_.r_code===j),T=!I||o.countries?.some(_=>_.c_code===I),L=!N||o.image_type===N,f=!b||b==="single"&&(!o.image_count||o.image_count<=1)||b==="multiple"&&o.image_count&&o.image_count>1,S=!k||o.starred===!0;return r&&c&&n&&y&&T&&L&&f&&S}),l=s.findIndex(o=>o.image_id===t);Me(s.length>1&&l>0),Fe(s.length>1&&l<s.length-1)}}catch(i){console.error("Failed to check navigation availability:",i)}},ve=async t=>{if(!B){q(!0);try{const i=await fetch("/api/images/grouped");if(i.ok){const u=await i.json(),s=u.filter(n=>{const y=!d||n.title?.toLowerCase().includes(d.toLowerCase())||n.generated?.toLowerCase().includes(d.toLowerCase())||n.source?.toLowerCase().includes(d.toLowerCase())||n.event_type?.toLowerCase().includes(d.toLowerCase()),T=!w||n.source===w,L=!C||n.event_type===C,f=!j||n.countries?.some($=>$.r_code===j),S=!I||n.countries?.some($=>$.c_code===I),_=!N||n.image_type===N,D=!b||b==="single"&&(!n.image_count||n.image_count<=1)||b==="multiple"&&n.image_count&&n.image_count>1,U=!k||n.starred===!0;return y&&T&&L&&f&&S&&_&&D&&U});if(s.findIndex(n=>n.image_id===h)===-1){const n=u.find(y=>y.image_id===h);n&&s.push(n)}const o=s.findIndex(n=>n.image_id===h);if(o===-1){console.error("Current image not found in filtered list");return}let r;t==="previous"?r=o>0?o-1:s.length-1:r=o<s.length-1?o+1:0;const c=s[r];c&&c.image_id&&c.image_id!=="undefined"&&c.image_id!=="null"&&c.image_id.trim()!==""&&(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(c.image_id)?(console.log("Carousel navigating to:",c.image_id),v(`/map/${c.image_id}`)):console.error("Carousel navigation blocked: Invalid image_id format:",c.image_id))}}catch(i){console.error("Failed to navigate to item:",i)}finally{q(!1)}}};m.useEffect(()=>{e&&h&&!W&&!Q&&se(h)},[e,h,d,w,C,j,I,N,b,k,W,Q,se]),m.useEffect(()=>{Promise.all([fetch("/api/sources").then(t=>t.json()),fetch("/api/types").then(t=>t.json()),fetch("/api/image-types").then(t=>t.json()),fetch("/api/regions").then(t=>t.json()),fetch("/api/countries").then(t=>t.json())]).then(([t,i,u,s,l])=>{Ie(t),Ne(i),be(u),Se(s),De(l)}).catch(console.error)},[]);const Ge=async()=>{e&&K(!0)},qe=async()=>{if(e)try{(await fetch(`/api/images/${e.image_id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({starred:!e.starred})})).ok?ee(i=>i?{...i,starred:!i.starred}:null):console.error("Failed to toggle starred status")}catch(t){console.error("Error toggling starred status:",t)}},Ke=async()=>{if(e){X(!0);try{if(console.log("Deleting image with ID:",e.image_id),(await fetch(`/api/images/${e.image_id}`,{method:"DELETE"})).ok){ee(i=>i?{...i,starred:!i.starred}:null),K(!1);try{const i=await fetch("/api/images/grouped");if(i.ok){const s=(await i.json()).filter(o=>{const r=!d||o.title?.toLowerCase().includes(d.toLowerCase())||o.generated?.toLowerCase().includes(d.toLowerCase())||o.source?.toLowerCase().includes(d.toLowerCase())||o.event_type?.toLowerCase().includes(d.toLowerCase()),c=!w||o.source===w,n=!C||o.event_type===C,y=!j||o.countries?.some(_=>_.r_code===j),T=!I||o.countries?.some(_=>_.c_code===I),L=!N||o.image_type===N,f=!b||b==="single"&&(!o.image_count||o.image_count<=1)||b==="multiple"&&o.image_count&&o.image_count>1,S=!k||o.starred===!0;return r&&c&&n&&y&&T&&L&&f&&S}),l=s.filter(o=>o.image_id!==e.image_id);if(l.length>0){const o=s.findIndex(c=>c.image_id===e.image_id);let r;if(o===s.length-1?r=o-1:r=o,console.log("Navigation target:",{currentIndex:o,targetIndex:r,targetId:l[r]?.image_id}),r>=0&&r<l.length){const c=l[r];c&&c.image_id&&c.image_id!=="undefined"&&c.image_id!=="null"&&c.image_id.trim()!==""?/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(c.image_id)?(console.log("Navigating to:",c.image_id),v(`/map/${c.image_id}`)):(console.error("Navigation blocked: Invalid image_id format:",c.image_id),v("/explore")):(console.error("Navigation blocked: Invalid image_id:",c?.image_id),v("/explore"))}else l[0]&&l[0].image_id&&l[0].image_id!=="undefined"&&l[0].image_id!=="null"&&l[0].image_id.trim()!==""?/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(l[0].image_id)?(console.log("Fallback navigation to first item:",l[0].image_id),v(`/map/${l[0].image_id}`)):(console.error("Fallback navigation blocked: Invalid image_id format:",l[0].image_id),v("/explore")):(console.log("No valid remaining items, going to explore page"),v("/explore"))}else console.log("No remaining items, going to explore page"),v("/explore")}else v("/explore")}catch(i){console.error("Failed to navigate to next item:",i),v("/explore")}finally{X(!1)}}else console.error("Delete failed"),X(!1)}catch(t){console.error("Delete failed:",t),X(!1)}}},g=m.useMemo(()=>{if(!e)return null;if(!d&&!w&&!C&&!j&&!I&&!N&&!b&&!k)return e;const t=!d||e.title?.toLowerCase().includes(d.toLowerCase())||e.generated?.toLowerCase().includes(d.toLowerCase())||e.source?.toLowerCase().includes(d.toLowerCase())||e.event_type?.toLowerCase().includes(d.toLowerCase()),i=!w||e.source===w,u=!C||e.event_type===C,s=!j||e.countries.some(y=>y.r_code===j),l=!I||e.countries.some(y=>y.c_code===I),o=!N||e.image_type===N,r=!b||b==="single"&&(!e.image_count||e.image_count<=1)||b==="multiple"&&e.image_count&&e.image_count>1,c=!k||e.starred===!0,n=t&&i&&u&&s&&l&&o&&r&&c;return!n&&(d||w||C||j||I||N||b||k)?(setTimeout(()=>{Ze()},100),e):n?e:null},[e,d,w,C,j,I,N,b,k]),Ze=m.useCallback(async()=>{z(!0);try{const t=await fetch("/api/images/grouped");if(t.ok){const u=(await t.json()).filter(s=>{const l=!d||s.title?.toLowerCase().includes(d.toLowerCase())||s.generated?.toLowerCase().includes(d.toLowerCase())||s.source?.toLowerCase().includes(d.toLowerCase())||s.event_type?.toLowerCase().includes(d.toLowerCase()),o=!w||s.source===w,r=!C||s.event_type===C,c=!j||s.countries?.some(f=>f.r_code===j),n=!I||s.countries?.some(f=>f.c_code===I),y=!N||s.image_type===N,T=!b||b==="single"&&(!s.image_count||s.image_count<=1)||b==="multiple"&&s.image_count&&s.image_count>1,L=!k||s.starred===!0;return l&&o&&r&&c&&n&&y&&T&&L});if(u.length>0){const s=u[0];s&&s.image_id&&v(`/map/${s.image_id}`)}else v("/explore")}}catch(t){console.error("Failed to navigate to matching image:",t),v("/explore")}finally{z(!1)}},[d,w,C,j,I,N,b,k,v]),Qe=()=>{if(!e)return;if(!e.all_image_ids||e.all_image_ids.length<=1){const s=`/upload?step=1&contribute=true&imageIds=${[e.image_id].join(",")}`;v(s);return}const i=`/upload?step=1&contribute=true&imageIds=${e.all_image_ids.join(",")}`;v(i)},E=(t,i)=>({image:`images/${i}`,caption:t.edited||t.generated||"",metadata:{image_id:t.image_count&&t.image_count>1?t.all_image_ids||[t.image_id]:t.image_id,title:t.title,source:t.source,event_type:t.event_type,image_type:t.image_type,countries:t.countries,starred:t.starred,image_count:t.image_count||1}}),Xe=async t=>{if(e){ae(!0),te(!1);try{const i=(await na(async()=>{const{default:r}=await import("./jszip.min-B2-9j7XO.js").then(c=>c.j);return{default:r}},__vite__mapDeps([0,1,2]))).default,u=new i;if(e.image_type==="crisis_map"){const r=u.folder("crisis_maps_dataset"),c=r?.folder("images");if(c)try{const n=e.image_count&&e.image_count>1?e.all_image_ids||[e.image_id]:[e.image_id],y=n.map(async(f,S)=>{try{const _=await fetch(`/api/images/${f}/file`);if(!_.ok)throw new Error(`Failed to fetch image ${f}`);const D=await _.blob(),U=e.file_key.split(".").pop()||"jpg",$=`0001_${String(S+1).padStart(2,"0")}.${U}`;return c.file($,D),{success:!0,fileName:$,imageId:f}}catch(_){return console.error(`Failed to process image ${f}:`,_),{success:!1,fileName:"",imageId:f}}}),L=(await Promise.all(y)).filter(f=>f.success);if(L.length===0)throw new Error("No images could be processed");if(t==="fine-tuning"){const f=[],S=[],_=[],D=L.map(Ye=>`images/${Ye.fileName}`),U=Math.random(),$={image:D.length===1?D[0]:D,caption:e.edited||e.generated||"",metadata:{image_id:n,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};U<R/100?f.push($):U<(R+J)/100?S.push($):_.push($),r&&(r.file("train.jsonl",JSON.stringify(f,null,2)),r.file("test.jsonl",JSON.stringify(S,null,2)),r.file("val.jsonl",JSON.stringify(_,null,2)))}else{const f=L.map(_=>`images/${_.fileName}`),S={image:f.length===1?f[0]:f,caption:e.edited||e.generated||"",metadata:{image_id:n,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};r&&r.file("0001.json",JSON.stringify(S,null,2))}}catch(n){throw console.error(`Failed to process image ${e.image_id}:`,n),n}}else if(e.image_type==="drone_image"){const r=u.folder("drone_images_dataset"),c=r?.folder("images");if(c)try{const n=await fetch(`/api/images/${e.image_id}/file`);if(!n.ok)throw new Error(`Failed to fetch image ${e.image_id}`);const y=await n.blob(),L=`0001.${e.file_key.split(".").pop()||"jpg"}`;if(c.file(L,y),t==="fine-tuning"){const f=[],S=[],_=[];if(String(e?.image_type)==="crisis_map"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}else if(String(e?.image_type)==="drone_image"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}r&&(r.file("train.jsonl",JSON.stringify(f,null,2)),r.file("test.jsonl",JSON.stringify(S,null,2)),r.file("val.jsonl",JSON.stringify(_,null,2)))}else{const f={image:`images/${L}`,caption:e.edited||e.generated||"",metadata:{image_id:e.image_count&&e.image_count>1?e.all_image_ids||[e.image_id]:e.image_id,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};r&&r.file("0001.json",JSON.stringify(f,null,2))}}catch(n){throw console.error(`Failed to process image ${e.image_id}:`,n),n}}else{const r=u.folder("generic_dataset"),c=r?.folder("images");if(c)try{const n=await fetch(`/api/images/${e.image_id}/file`);if(!n.ok)throw new Error(`Failed to fetch image ${e.image_id}`);const y=await n.blob(),L=`0001.${e.file_key.split(".").pop()||"jpg"}`;if(c.file(L,y),t==="fine-tuning"){const f=[],S=[],_=[];if(String(e?.image_type)==="crisis_map"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}else if(String(e?.image_type)==="drone_image"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}r&&(r.file("train.jsonl",JSON.stringify(f,null,2)),r.file("test.jsonl",JSON.stringify(S,null,2)),r.file("val.jsonl",JSON.stringify(_,null,2)))}else{const f={image:`images/${L}`,caption:e.edited||e.generated||"",metadata:{image_id:e.image_count&&e.image_count>1?e.all_image_ids||[e.image_id]:e.image_id,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};r&&r.file("0001.json",JSON.stringify(f,null,2))}}catch(n){throw console.error(`Failed to process image ${e.image_id}:`,n),n}}const s=await u.generateAsync({type:"blob"}),l=URL.createObjectURL(s),o=document.createElement("a");o.href=l,o.download=`dataset_${e.image_type}_${e.image_id}_${t}_${new Date().toISOString().split("T")[0]}.zip`,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(l),console.log(`Exported ${e.image_type} dataset with 1 image in ${t} mode`),te(!0)}catch(i){console.error("Export failed:",i),alert("Failed to export dataset. Please try again.")}finally{ae(!1)}}};return W?a.jsx(Y,{children:a.jsx("div",{className:p.loadingContainer,children:a.jsxs("div",{className:"flex flex-col items-center gap-4",children:[a.jsx(we,{className:"text-ifrcRed"}),a.jsx("div",{children:"Loading map details..."})]})})}):le||!e?a.jsx(Y,{children:a.jsx("div",{className:p.errorContainer,children:a.jsxs("div",{className:"flex flex-col items-center gap-4 text-center",children:[a.jsx("div",{className:"text-4xl",children:"β οΈ"}),a.jsx("div",{className:"text-xl font-semibold",children:"Unable to load map"}),a.jsx("div",{children:le||"Map not found"}),a.jsx(F,{name:"back-to-explore",variant:"secondary",onClick:()=>v("/explore"),children:"Return to Explore"})]})})}):a.jsxs(Y,{children:[a.jsxs("div",{className:"max-w-7xl mx-auto",children:[a.jsxs("div",{className:p.tabSelector,children:[a.jsx(sa,{name:"map-details-view",value:re,onChange:t=>{(t==="mapDetails"||t==="explore")&&(je(t),t==="explore"&&v("/explore"))},options:We,keySelector:t=>t.key,labelSelector:t=>t.label}),a.jsxs("div",{className:"flex items-center gap-2 ml-auto",children:[a.jsx(P,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:a.jsxs(F,{name:"reference-examples",variant:k?"primary":"secondary",onClick:()=>Oe(!k),className:"whitespace-nowrap",children:[a.jsx("span",{className:"mr-2",children:k?a.jsx("span",{className:"text-yellow-400",children:"β
"}):a.jsx("span",{className:"text-yellow-400",children:"β"})}),"Reference Examples"]})}),a.jsx(F,{name:"export-dataset",variant:"secondary",onClick:()=>Z(!0),children:"Export"})]})]}),a.jsx(la,{sources:ce,types:de,regions:me,countries:Le,imageTypes:ge,isLoadingFilters:!1}),re==="mapDetails"?a.jsx("div",{className:"relative",children:g?a.jsxs(a.Fragment,{children:[a.jsxs("div",{className:p.gridLayout,children:[a.jsxs(P,{heading:a.jsxs("div",{className:"flex items-center gap-2",children:[a.jsx("span",{children:g.title||"Map Image"}),g.starred&&a.jsx("span",{className:"text-red-500 text-xl",title:"Starred image",children:"β
"})]}),headingLevel:2,withHeaderBorder:!0,withInternalPadding:!0,spacing:"comfortable",children:[a.jsx("div",{className:p.imageContainer,children:e?.image_count&&e.image_count>1||x.length>1?a.jsxs("div",{className:p.carouselContainer,children:[a.jsx("div",{className:p.carouselImageWrapper,children:G?a.jsxs("div",{className:p.imagePlaceholder,children:[a.jsx(we,{className:"text-ifrcRed"}),a.jsx("div",{children:"Loading images..."})]}):x[M]?.detail_url?a.jsx("img",{src:x[M].detail_url,alt:x[M].file_key,className:p.carouselImage,onError:t=>{console.log("MapDetailsPage: Detail image failed to load, falling back to original:",x[M].detail_url);const i=t.target;x[M].image_url&&(i.src=x[M].image_url)},onLoad:()=>console.log("MapDetailsPage: Detail image loaded successfully:",x[M].detail_url)}):x[M]?.image_url?a.jsx("img",{src:x[M].image_url,alt:x[M].file_key,className:p.carouselImage,onLoad:()=>console.log("MapDetailsPage: Original image loaded successfully:",x[M].image_url)}):a.jsx("div",{className:p.imagePlaceholder,children:"No image available"})}),a.jsxs("div",{className:p.carouselNavigation,children:[a.jsx(F,{name:"previous-image",variant:"tertiary",size:1,onClick:Be,disabled:G,className:p.carouselButton,children:a.jsx(ie,{className:"w-4 h-4"})}),a.jsx("div",{className:p.carouselIndicators,children:x.map((t,i)=>a.jsx("button",{onClick:()=>He(i),className:`${p.carouselIndicator} ${i===M?p.carouselIndicatorActive:""}`,disabled:G,children:i+1},i))}),a.jsx(F,{name:"next-image",variant:"tertiary",size:1,onClick:Je,disabled:G,className:p.carouselButton,children:a.jsx(oe,{className:"w-4 h-4"})})]}),a.jsx("div",{className:p.viewImageButtonContainer,children:a.jsx(F,{name:"view-full-size-carousel",variant:"secondary",size:1,onClick:()=>ye(x[M]),disabled:G||!x[M]?.image_url,children:"View Image"})})]}):a.jsxs("div",{className:p.singleImageContainer,children:[g.detail_url?a.jsx("img",{src:g.detail_url,alt:g.file_key,onError:t=>{console.log("MapDetailsPage: Detail image failed to load, falling back to original:",g.detail_url);const i=t.target;g.image_url&&(i.src=g.image_url)},onLoad:()=>console.log("MapDetailsPage: Detail image loaded successfully:",g.detail_url)}):g.image_url?a.jsx("img",{src:g.image_url,alt:g.file_key,onLoad:()=>console.log("MapDetailsPage: Original image loaded successfully:",g.image_url)}):a.jsx("div",{className:p.imagePlaceholder,children:"No image available"}),a.jsx("div",{className:p.viewImageButtonContainer,children:a.jsx(F,{name:"view-full-size-single",variant:"secondary",size:1,onClick:()=>ye(g),disabled:!g.image_url,children:"View Image"})})]})}),a.jsx(P,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:a.jsxs("div",{className:p.metadataTags,children:[g.image_type!=="drone_image"&&a.jsx("span",{className:p.metadataTag,children:ce.find(t=>t.s_code===g.source)?.label||g.source}),a.jsx("span",{className:p.metadataTag,children:de.find(t=>t.t_code===g.event_type)?.label||g.event_type}),a.jsx("span",{className:p.metadataTag,children:ge.find(t=>t.image_type===g.image_type)?.label||g.image_type}),g.countries&&g.countries.length>0&&a.jsxs(a.Fragment,{children:[a.jsx("span",{className:p.metadataTag,children:me.find(t=>t.r_code===g.countries[0].r_code)?.label||"Unknown Region"}),a.jsx("span",{className:p.metadataTag,children:g.countries.map(t=>t.label).join(", ")})]}),g.image_count&&g.image_count>1&&a.jsxs("span",{className:p.metadataTag,title:`Multi-upload with ${g.image_count} images`,children:["π· ",g.image_count]}),(!g.image_count||g.image_count<=1)&&a.jsx("span",{className:p.metadataTag,title:"Single Upload",children:"Single"})]})})]}),a.jsx("div",{className:p.detailsSection,children:g.edited&&g.edited.includes("Description:")||g.generated&&g.generated.includes("Description:")?a.jsx(P,{heading:"AI Generated Content",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,spacing:"comfortable",children:a.jsx("div",{className:p.captionContainer,children:a.jsx("div",{className:p.captionText,children:(g.edited||g.generated||"").split(`
|
3 |
`).map((t,i)=>a.jsx("div",{children:t.startsWith("Description:")||t.startsWith("Analysis:")||t.startsWith("Recommended Actions:")?a.jsx("h4",{className:"font-semibold text-gray-800 mt-4 mb-2",children:t}):t.trim()===""?a.jsx("br",{}):a.jsx("p",{className:"mb-2",children:t})},i))})})}):a.jsx(P,{heading:"Description",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,spacing:"comfortable",children:a.jsx("div",{className:p.captionContainer,children:g.generated?a.jsx("div",{className:p.captionText,children:a.jsx("p",{children:g.edited||g.generated})}):a.jsx("p",{children:"β no caption yet β"})})})})]}),a.jsx("div",{className:"flex items-center justify-center mt-8",children:a.jsx(P,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-lg p-4",children:a.jsxs("div",{className:"flex items-center gap-4",children:[ke&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"previous-item",variant:"tertiary",size:1,className:`bg-white/90 hover:bg-white shadow-lg border border-gray-200 ${B?"opacity-50 cursor-not-allowed":"hover:scale-110"}`,onClick:()=>ve("previous"),disabled:B,children:a.jsxs("div",{className:"flex items-center gap-1",children:[a.jsxs("div",{className:"flex -space-x-1",children:[a.jsx(ie,{className:"w-4 h-4"}),a.jsx(ie,{className:"w-4 h-4"})]}),a.jsx("span",{className:"font-semibold",children:"Previous"})]})})}),ne&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"delete",variant:"tertiary",size:1,className:"bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 hover:border-red-300",onClick:Ge,title:"Delete","aria-label":"Delete saved image",children:a.jsx(ia,{className:"w-4 h-4"})})}),a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"contribute",onClick:Qe,children:"Contribute"})}),ne&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"toggle-star",variant:"tertiary",size:1,className:`${e?.starred?"bg-red-100 hover:bg-red-200 text-red-800 border-2 border-red-400":"bg-gray-100 hover:bg-gray-200 text-gray-600 border-2 border-gray-300"} w-16 h-8 rounded-full transition-all duration-200 flex items-center justify-center`,onClick:qe,title:e?.starred?"Unstar image":"Star image","aria-label":e?.starred?"Unstar image":"Star image",children:a.jsx("span",{className:`text-lg transition-all duration-200 ${e?.starred?"text-red-600":"text-gray-500"}`,children:e?.starred?"β
":"β"})})}),Te&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"next-item",variant:"tertiary",size:1,className:`bg-white/90 hover:bg-white shadow-lg border border-gray-200 ${B?"opacity-50 cursor-not-allowed":"hover:scale-110"}`,onClick:()=>ve("next"),disabled:B,children:a.jsxs("div",{className:"flex items-center gap-1",children:[a.jsx("span",{className:"font-semibold",children:"Next"}),a.jsxs("div",{className:"flex -space-x-1",children:[a.jsx(oe,{className:"w-4 h-4"}),a.jsx(oe,{className:"w-4 h-4"})]})]})})})]})})})]}):a.jsxs("div",{className:"text-center py-12",children:[a.jsx("div",{className:"text-xl font-semibold text-gray-600 mb-4",children:"No matches found"}),a.jsx("div",{className:"mt-4",children:a.jsx(F,{name:"clear-filters",variant:"secondary",onClick:Ue,children:"Clear Filters"})})]})}):null]}),Ee&&a.jsx("div",{className:p.fullSizeModalOverlay,onClick:()=>K(!1),children:a.jsx("div",{className:p.fullSizeModalContent,onClick:t=>t.stopPropagation(),children:a.jsxs("div",{className:p.ratingWarningContent,children:[a.jsx("h3",{className:p.ratingWarningTitle,children:"Delete Image?"}),a.jsx("p",{className:p.ratingWarningText,children:"This action cannot be undone. Are you sure you want to delete this saved image and all related data?"}),a.jsxs("div",{className:p.ratingWarningButtons,children:[a.jsx(F,{name:"confirm-delete",variant:"secondary",onClick:Ke,children:"Delete"}),a.jsx(F,{name:"cancel-delete",variant:"tertiary",onClick:()=>K(!1),children:"Cancel"})]})]})})}),ue&&a.jsx(ca,{isOpen:ue,onClose:()=>{Z(!1),te(!1),ae(!1)},onExport:(t,i)=>{i.includes(e.image_type)&&Xe(t)},filteredCount:1,totalCount:1,hasFilters:!1,crisisMapsCount:e.image_type==="crisis_map"?1:0,droneImagesCount:e.image_type==="drone_image"?1:0,isLoading:Pe,exportSuccess:Re,variant:"single",onNavigateToList:()=>{Z(!1),v("/explore")},onNavigateAndExport:()=>{Z(!1),v("/explore?export=true")}}),a.jsx(oa,{isOpen:$e,imageUrl:ze?.image_url||null,preview:null,selectedImageData:null,onClose:Ve,isLoading:Ae})]})}export{st as default};
|
|
|
1 |
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/jszip.min-C3B0d4UO.js","assets/index-BCz4bkWK.js","assets/index-FBu17hMI.css"])))=>i.map(i=>d[i]);
|
2 |
+
import{K as ea,x as aa,j as a,N as Y,n as F,r as m,D as ta,_ as we,L as sa,z as P,v as ie,w as oe,F as ia,M as oa,G as na}from"./index-BCz4bkWK.js";import{u as ra}from"./useAdmin-LSEnRjVv.js";import{F as la,E as ca}from"./ExportModal-Qf6Y7VAO.js";const da="_tabSelector_usssr_1",ga="_imageContainer_usssr_12",ma="_imagePlaceholder_usssr_33",ua="_metadataTags_usssr_45",fa="_metadataTag_usssr_45",pa="_captionContainer_usssr_67",ha="_captionText_usssr_74",_a="_gridLayout_usssr_131",xa="_detailsSection_usssr_155",ya="_loadingContainer_usssr_161",va="_errorContainer_usssr_171",wa="_fullSizeModalOverlay_usssr_205",Ca="_fullSizeModalContent_usssr_219",ja="_ratingWarningContent_usssr_230",Ia="_ratingWarningTitle_usssr_236",Na="_ratingWarningText_usssr_243",ba="_ratingWarningButtons_usssr_250",Sa="_carouselContainer_usssr_365",La="_carouselImageWrapper_usssr_370",Da="_carouselImage_usssr_370",ka="_carouselNavigation_usssr_393",Ma="_carouselButton_usssr_405",Ta="_carouselIndicators_usssr_429",Fa="_carouselIndicator_usssr_429",Ea="_carouselIndicatorActive_usssr_458",Pa="_singleImageContainer_usssr_488",Ra="_viewImageButtonContainer_usssr_494",p={tabSelector:da,imageContainer:ga,imagePlaceholder:ma,metadataTags:ua,metadataTag:fa,captionContainer:pa,captionText:ha,gridLayout:_a,detailsSection:xa,loadingContainer:ya,errorContainer:va,fullSizeModalOverlay:wa,fullSizeModalContent:Ca,ratingWarningContent:ja,ratingWarningTitle:Ia,ratingWarningText:Na,ratingWarningButtons:ba,carouselContainer:Sa,carouselImageWrapper:La,carouselImage:Da,carouselNavigation:ka,carouselButton:Ma,carouselIndicators:Ta,carouselIndicator:Fa,carouselIndicatorActive:Ea,singleImageContainer:Pa,viewImageButtonContainer:Ra};function st(){const{mapId:h}=ea(),v=aa(),{isAuthenticated:ne}=ra();console.log("MapDetailsPage: Current URL:",window.location.href),console.log("MapDetailsPage: Hash:",window.location.hash),console.log("MapDetailsPage: mapId from useParams:",h),console.log("MapDetailsPage: mapId type:",typeof h),console.log("MapDetailsPage: mapId length:",h?.length),console.log("MapDetailsPage: mapId value:",JSON.stringify(h));const Ce=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;if(!h||h==="undefined"||h==="null"||h.trim()===""||!Ce.test(h))return a.jsx(Y,{children:a.jsxs("div",{className:"flex flex-col items-center gap-4 text-center py-12",children:[a.jsx("div",{className:"text-4xl",children:"β οΈ"}),a.jsx("div",{className:"text-xl font-semibold",children:"Invalid Map ID"}),a.jsx("div",{children:"The map ID provided is not valid."}),a.jsxs("div",{className:"text-sm text-gray-500 mt-2",children:['Debug Info: mapId = "',h,'" (type: ',typeof h,")"]}),a.jsx(F,{name:"back-to-explore",variant:"secondary",onClick:()=>v("/explore"),children:"Return to Explore"})]})});const[re,je]=m.useState("mapDetails"),[e,ee]=m.useState(null),[W,z]=m.useState(!0),[le,O]=m.useState(null),[ce,Ie]=m.useState([]),[de,Ne]=m.useState([]),[ge,be]=m.useState([]),[me,Se]=m.useState([]),[Le,De]=m.useState([]),[ke,Me]=m.useState(!1),[Te,Fe]=m.useState(!1),[B,q]=m.useState(!1),[Ee,K]=m.useState(!1),[ue,Z]=m.useState(!1),[Pe,ae]=m.useState(!1),[Re,te]=m.useState(!1),[$a,za]=m.useState("standard"),[R,Aa]=m.useState(80),[J,Oa]=m.useState(10),[Ua,Wa]=m.useState(10),[Ba,Ja]=m.useState(!0),[Ha,Va]=m.useState(!0),[Q,X]=m.useState(!1),[$e,fe]=m.useState(!1),[ze,pe]=m.useState(null),[Ae,H]=m.useState(!1),[x,V]=m.useState([]),[M,A]=m.useState(0),[G,he]=m.useState(!1),{search:d,setSearch:Ga,srcFilter:w,setSrcFilter:qa,catFilter:C,setCatFilter:Ka,regionFilter:j,setRegionFilter:Za,countryFilter:I,setCountryFilter:Qa,imageTypeFilter:N,setImageTypeFilter:Xa,uploadTypeFilter:b,setUploadTypeFilter:Ya,showReferenceExamples:k,setShowReferenceExamples:Oe,clearAllFilters:Ue}=ta(),We=[{key:"explore",label:"List"},{key:"mapDetails",label:"Carousel"}],_e=m.useCallback(async t=>{if(console.log("fetchMapData called with id:",t),console.log("fetchMapData id type:",typeof t),!t||t==="undefined"||t==="null"||t.trim()===""){console.log("fetchMapData: Invalid ID detected:",t),O("Invalid Map ID"),z(!1);return}if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)){console.log("fetchMapData: Invalid UUID format:",t),O("Invalid Map ID format"),z(!1);return}console.log("fetchMapData: Making API call for id:",t),q(!0),z(!0);try{const u=await fetch(`/api/images/${t}`);if(!u.ok)throw new Error("Map not found");const s=await u.json();if(ee(s),s.all_image_ids&&s.all_image_ids.length>1)await xe(s.all_image_ids);else if(s.image_count&&s.image_count>1){console.log("Multi-upload detected but no all_image_ids, trying grouped endpoint");try{const l=await fetch("/api/images/grouped");if(l.ok){const r=(await l.json()).find(c=>c.all_image_ids&&c.all_image_ids.includes(s.image_id));r&&r.all_image_ids?await xe(r.all_image_ids):(V([s]),A(0))}else V([s]),A(0)}catch(l){console.error("Failed to fetch from grouped endpoint:",l),V([s]),A(0)}}else V([s]),A(0);await se(t)}catch(u){O(u instanceof Error?u.message:"Unknown error occurred")}finally{z(!1),q(!1)}},[]),xe=m.useCallback(async t=>{console.log("fetchAllImages called with imageIds:",t),he(!0);try{const i=t.map(async s=>{const l=await fetch(`/api/images/${s}`);if(!l.ok)throw new Error(`Failed to fetch image ${s}`);return l.json()}),u=await Promise.all(i);V(u),A(0),console.log("fetchAllImages: Loaded",u.length,"images")}catch(i){console.error("fetchAllImages error:",i),O(i instanceof Error?i.message:"Failed to load all images")}finally{he(!1)}},[]),Be=m.useCallback(()=>{x.length>1&&A(t=>t>0?t-1:x.length-1)},[x.length]),Je=m.useCallback(()=>{x.length>1&&A(t=>t<x.length-1?t+1:0)},[x.length]),He=m.useCallback(t=>{t>=0&&t<x.length&&A(t)},[x.length]),ye=m.useCallback(async t=>{const i=t||(x.length>0?x[M]:e);if(i){H(!0),pe(i),fe(!0);try{const u=new Image;u.onload=()=>{H(!1)},u.onerror=()=>{H(!1)},u.src=i.image_url}catch(u){console.error("Error preloading full-size image:",u),H(!1)}}},[x,M,e]),Ve=m.useCallback(()=>{fe(!1),pe(null),H(!1)},[]);m.useEffect(()=>{if(console.log("MapDetailsPage: mapId from useParams:",h),console.log("MapDetailsPage: mapId type:",typeof h),console.log("MapDetailsPage: mapId value:",h),!h||h==="undefined"||h==="null"||h.trim()===""||h===void 0||h===null){console.log("MapDetailsPage: Invalid mapId, setting error"),O("Map ID is required"),z(!1);return}if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(h)){console.log("MapDetailsPage: Invalid UUID format:",h),O("Invalid Map ID format"),z(!1);return}console.log("MapDetailsPage: Fetching data for mapId:",h),_e(h)},[h,_e]),m.useEffect(()=>{if(!e||W||Q)return;if(!h||h==="undefined"||h==="null"||h.trim()===""){console.log("Auto-navigation skipped: Invalid mapId");return}if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(h)){console.log("Auto-navigation skipped: Invalid mapId format");return}(()=>{const u=!d||e.title?.toLowerCase().includes(d.toLowerCase())||e.generated?.toLowerCase().includes(d.toLowerCase())||e.source?.toLowerCase().includes(d.toLowerCase())||e.event_type?.toLowerCase().includes(d.toLowerCase()),s=!w||e.source===w,l=!C||e.event_type===C,o=!j||e.countries.some(T=>T.r_code===j),r=!I||e.countries.some(T=>T.c_code===I),c=!N||e.image_type===N,n=!k||e.starred===!0,y=u&&s&&l&&o&&r&&c&&n;return console.log("Auto-navigation check:",{mapId:h,search:d,srcFilter:w,catFilter:C,regionFilter:j,countryFilter:I,imageTypeFilter:N,showReferenceExamples:k,matchesSearch:u,matchesSource:s,matchesCategory:l,matchesRegion:o,matchesCountry:r,matchesImageType:c,matchesReferenceExamples:n,matches:y}),y})()||(console.log("Current map does not match filters, looking for first matching item"),fetch("/api/images").then(u=>u.json()).then(u=>{console.log("Auto-navigation: Received images from API:",u.length),console.log("Auto-navigation: First few images:",u.slice(0,3).map(l=>({image_id:l.image_id,title:l.title})));const s=u.find(l=>{const o=!d||l.title?.toLowerCase().includes(d.toLowerCase())||l.generated?.toLowerCase().includes(d.toLowerCase())||l.source?.toLowerCase().includes(d.toLowerCase())||l.event_type?.toLowerCase().includes(d.toLowerCase()),r=!w||l.source===w,c=!C||l.event_type===C,n=!j||l.countries?.some(f=>f.r_code===j),y=!I||l.countries?.some(f=>f.c_code===I),T=!N||l.image_type===N,L=!k||l.starred===!0;return o&&r&&c&&n&&y&&T&&L});console.log("Auto-navigation: Found first matching image:",s?{image_id:s.image_id,title:s.title,source:s.source}:"No matching image found"),s&&s.image_id&&s.image_id!=="undefined"&&s.image_id!=="null"&&s.image_id.trim()!==""&&s.image_id!==h&&(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s.image_id)?(console.log("Auto-navigating to:",s.image_id),v(`/map/${s.image_id}`)):console.error("Auto-navigation blocked: Invalid image_id format:",s.image_id))}).catch(console.error))},[e,d,w,C,j,I,N,k,h,v,W,Q]);const se=async t=>{if(!(!t||t==="undefined"||t==="null"||t.trim()===""))try{const i=await fetch("/api/images/grouped");if(i.ok){const s=(await i.json()).filter(o=>{const r=!d||o.title?.toLowerCase().includes(d.toLowerCase())||o.generated?.toLowerCase().includes(d.toLowerCase())||o.source?.toLowerCase().includes(d.toLowerCase())||o.event_type?.toLowerCase().includes(d.toLowerCase()),c=!w||o.source===w,n=!C||o.event_type===C,y=!j||o.countries?.some(_=>_.r_code===j),T=!I||o.countries?.some(_=>_.c_code===I),L=!N||o.image_type===N,f=!b||b==="single"&&(!o.image_count||o.image_count<=1)||b==="multiple"&&o.image_count&&o.image_count>1,S=!k||o.starred===!0;return r&&c&&n&&y&&T&&L&&f&&S}),l=s.findIndex(o=>o.image_id===t);Me(s.length>1&&l>0),Fe(s.length>1&&l<s.length-1)}}catch(i){console.error("Failed to check navigation availability:",i)}},ve=async t=>{if(!B){q(!0);try{const i=await fetch("/api/images/grouped");if(i.ok){const u=await i.json(),s=u.filter(n=>{const y=!d||n.title?.toLowerCase().includes(d.toLowerCase())||n.generated?.toLowerCase().includes(d.toLowerCase())||n.source?.toLowerCase().includes(d.toLowerCase())||n.event_type?.toLowerCase().includes(d.toLowerCase()),T=!w||n.source===w,L=!C||n.event_type===C,f=!j||n.countries?.some($=>$.r_code===j),S=!I||n.countries?.some($=>$.c_code===I),_=!N||n.image_type===N,D=!b||b==="single"&&(!n.image_count||n.image_count<=1)||b==="multiple"&&n.image_count&&n.image_count>1,U=!k||n.starred===!0;return y&&T&&L&&f&&S&&_&&D&&U});if(s.findIndex(n=>n.image_id===h)===-1){const n=u.find(y=>y.image_id===h);n&&s.push(n)}const o=s.findIndex(n=>n.image_id===h);if(o===-1){console.error("Current image not found in filtered list");return}let r;t==="previous"?r=o>0?o-1:s.length-1:r=o<s.length-1?o+1:0;const c=s[r];c&&c.image_id&&c.image_id!=="undefined"&&c.image_id!=="null"&&c.image_id.trim()!==""&&(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(c.image_id)?(console.log("Carousel navigating to:",c.image_id),v(`/map/${c.image_id}`)):console.error("Carousel navigation blocked: Invalid image_id format:",c.image_id))}}catch(i){console.error("Failed to navigate to item:",i)}finally{q(!1)}}};m.useEffect(()=>{e&&h&&!W&&!Q&&se(h)},[e,h,d,w,C,j,I,N,b,k,W,Q,se]),m.useEffect(()=>{Promise.all([fetch("/api/sources").then(t=>t.json()),fetch("/api/types").then(t=>t.json()),fetch("/api/image-types").then(t=>t.json()),fetch("/api/regions").then(t=>t.json()),fetch("/api/countries").then(t=>t.json())]).then(([t,i,u,s,l])=>{Ie(t),Ne(i),be(u),Se(s),De(l)}).catch(console.error)},[]);const Ge=async()=>{e&&K(!0)},qe=async()=>{if(e)try{(await fetch(`/api/images/${e.image_id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({starred:!e.starred})})).ok?ee(i=>i?{...i,starred:!i.starred}:null):console.error("Failed to toggle starred status")}catch(t){console.error("Error toggling starred status:",t)}},Ke=async()=>{if(e){X(!0);try{if(console.log("Deleting image with ID:",e.image_id),(await fetch(`/api/images/${e.image_id}`,{method:"DELETE"})).ok){ee(i=>i?{...i,starred:!i.starred}:null),K(!1);try{const i=await fetch("/api/images/grouped");if(i.ok){const s=(await i.json()).filter(o=>{const r=!d||o.title?.toLowerCase().includes(d.toLowerCase())||o.generated?.toLowerCase().includes(d.toLowerCase())||o.source?.toLowerCase().includes(d.toLowerCase())||o.event_type?.toLowerCase().includes(d.toLowerCase()),c=!w||o.source===w,n=!C||o.event_type===C,y=!j||o.countries?.some(_=>_.r_code===j),T=!I||o.countries?.some(_=>_.c_code===I),L=!N||o.image_type===N,f=!b||b==="single"&&(!o.image_count||o.image_count<=1)||b==="multiple"&&o.image_count&&o.image_count>1,S=!k||o.starred===!0;return r&&c&&n&&y&&T&&L&&f&&S}),l=s.filter(o=>o.image_id!==e.image_id);if(l.length>0){const o=s.findIndex(c=>c.image_id===e.image_id);let r;if(o===s.length-1?r=o-1:r=o,console.log("Navigation target:",{currentIndex:o,targetIndex:r,targetId:l[r]?.image_id}),r>=0&&r<l.length){const c=l[r];c&&c.image_id&&c.image_id!=="undefined"&&c.image_id!=="null"&&c.image_id.trim()!==""?/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(c.image_id)?(console.log("Navigating to:",c.image_id),v(`/map/${c.image_id}`)):(console.error("Navigation blocked: Invalid image_id format:",c.image_id),v("/explore")):(console.error("Navigation blocked: Invalid image_id:",c?.image_id),v("/explore"))}else l[0]&&l[0].image_id&&l[0].image_id!=="undefined"&&l[0].image_id!=="null"&&l[0].image_id.trim()!==""?/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(l[0].image_id)?(console.log("Fallback navigation to first item:",l[0].image_id),v(`/map/${l[0].image_id}`)):(console.error("Fallback navigation blocked: Invalid image_id format:",l[0].image_id),v("/explore")):(console.log("No valid remaining items, going to explore page"),v("/explore"))}else console.log("No remaining items, going to explore page"),v("/explore")}else v("/explore")}catch(i){console.error("Failed to navigate to next item:",i),v("/explore")}finally{X(!1)}}else console.error("Delete failed"),X(!1)}catch(t){console.error("Delete failed:",t),X(!1)}}},g=m.useMemo(()=>{if(!e)return null;if(!d&&!w&&!C&&!j&&!I&&!N&&!b&&!k)return e;const t=!d||e.title?.toLowerCase().includes(d.toLowerCase())||e.generated?.toLowerCase().includes(d.toLowerCase())||e.source?.toLowerCase().includes(d.toLowerCase())||e.event_type?.toLowerCase().includes(d.toLowerCase()),i=!w||e.source===w,u=!C||e.event_type===C,s=!j||e.countries.some(y=>y.r_code===j),l=!I||e.countries.some(y=>y.c_code===I),o=!N||e.image_type===N,r=!b||b==="single"&&(!e.image_count||e.image_count<=1)||b==="multiple"&&e.image_count&&e.image_count>1,c=!k||e.starred===!0,n=t&&i&&u&&s&&l&&o&&r&&c;return!n&&(d||w||C||j||I||N||b||k)?(setTimeout(()=>{Ze()},100),e):n?e:null},[e,d,w,C,j,I,N,b,k]),Ze=m.useCallback(async()=>{z(!0);try{const t=await fetch("/api/images/grouped");if(t.ok){const u=(await t.json()).filter(s=>{const l=!d||s.title?.toLowerCase().includes(d.toLowerCase())||s.generated?.toLowerCase().includes(d.toLowerCase())||s.source?.toLowerCase().includes(d.toLowerCase())||s.event_type?.toLowerCase().includes(d.toLowerCase()),o=!w||s.source===w,r=!C||s.event_type===C,c=!j||s.countries?.some(f=>f.r_code===j),n=!I||s.countries?.some(f=>f.c_code===I),y=!N||s.image_type===N,T=!b||b==="single"&&(!s.image_count||s.image_count<=1)||b==="multiple"&&s.image_count&&s.image_count>1,L=!k||s.starred===!0;return l&&o&&r&&c&&n&&y&&T&&L});if(u.length>0){const s=u[0];s&&s.image_id&&v(`/map/${s.image_id}`)}else v("/explore")}}catch(t){console.error("Failed to navigate to matching image:",t),v("/explore")}finally{z(!1)}},[d,w,C,j,I,N,b,k,v]),Qe=()=>{if(!e)return;if(!e.all_image_ids||e.all_image_ids.length<=1){const s=`/upload?step=1&contribute=true&imageIds=${[e.image_id].join(",")}`;v(s);return}const i=`/upload?step=1&contribute=true&imageIds=${e.all_image_ids.join(",")}`;v(i)},E=(t,i)=>({image:`images/${i}`,caption:t.edited||t.generated||"",metadata:{image_id:t.image_count&&t.image_count>1?t.all_image_ids||[t.image_id]:t.image_id,title:t.title,source:t.source,event_type:t.event_type,image_type:t.image_type,countries:t.countries,starred:t.starred,image_count:t.image_count||1}}),Xe=async t=>{if(e){ae(!0),te(!1);try{const i=(await na(async()=>{const{default:r}=await import("./jszip.min-C3B0d4UO.js").then(c=>c.j);return{default:r}},__vite__mapDeps([0,1,2]))).default,u=new i;if(e.image_type==="crisis_map"){const r=u.folder("crisis_maps_dataset"),c=r?.folder("images");if(c)try{const n=e.image_count&&e.image_count>1?e.all_image_ids||[e.image_id]:[e.image_id],y=n.map(async(f,S)=>{try{const _=await fetch(`/api/images/${f}/file`);if(!_.ok)throw new Error(`Failed to fetch image ${f}`);const D=await _.blob(),U=e.file_key.split(".").pop()||"jpg",$=`0001_${String(S+1).padStart(2,"0")}.${U}`;return c.file($,D),{success:!0,fileName:$,imageId:f}}catch(_){return console.error(`Failed to process image ${f}:`,_),{success:!1,fileName:"",imageId:f}}}),L=(await Promise.all(y)).filter(f=>f.success);if(L.length===0)throw new Error("No images could be processed");if(t==="fine-tuning"){const f=[],S=[],_=[],D=L.map(Ye=>`images/${Ye.fileName}`),U=Math.random(),$={image:D.length===1?D[0]:D,caption:e.edited||e.generated||"",metadata:{image_id:n,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};U<R/100?f.push($):U<(R+J)/100?S.push($):_.push($),r&&(r.file("train.jsonl",JSON.stringify(f,null,2)),r.file("test.jsonl",JSON.stringify(S,null,2)),r.file("val.jsonl",JSON.stringify(_,null,2)))}else{const f=L.map(_=>`images/${_.fileName}`),S={image:f.length===1?f[0]:f,caption:e.edited||e.generated||"",metadata:{image_id:n,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};r&&r.file("0001.json",JSON.stringify(S,null,2))}}catch(n){throw console.error(`Failed to process image ${e.image_id}:`,n),n}}else if(e.image_type==="drone_image"){const r=u.folder("drone_images_dataset"),c=r?.folder("images");if(c)try{const n=await fetch(`/api/images/${e.image_id}/file`);if(!n.ok)throw new Error(`Failed to fetch image ${e.image_id}`);const y=await n.blob(),L=`0001.${e.file_key.split(".").pop()||"jpg"}`;if(c.file(L,y),t==="fine-tuning"){const f=[],S=[],_=[];if(String(e?.image_type)==="crisis_map"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}else if(String(e?.image_type)==="drone_image"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}r&&(r.file("train.jsonl",JSON.stringify(f,null,2)),r.file("test.jsonl",JSON.stringify(S,null,2)),r.file("val.jsonl",JSON.stringify(_,null,2)))}else{const f={image:`images/${L}`,caption:e.edited||e.generated||"",metadata:{image_id:e.image_count&&e.image_count>1?e.all_image_ids||[e.image_id]:e.image_id,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};r&&r.file("0001.json",JSON.stringify(f,null,2))}}catch(n){throw console.error(`Failed to process image ${e.image_id}:`,n),n}}else{const r=u.folder("generic_dataset"),c=r?.folder("images");if(c)try{const n=await fetch(`/api/images/${e.image_id}/file`);if(!n.ok)throw new Error(`Failed to fetch image ${e.image_id}`);const y=await n.blob(),L=`0001.${e.file_key.split(".").pop()||"jpg"}`;if(c.file(L,y),t==="fine-tuning"){const f=[],S=[],_=[];if(String(e?.image_type)==="crisis_map"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}else if(String(e?.image_type)==="drone_image"){const D=Math.random();D<R/100?f.push(E(e,"0001")):D<(R+J)/100?S.push(E(e,"0001")):_.push(E(e,"0001"))}r&&(r.file("train.jsonl",JSON.stringify(f,null,2)),r.file("test.jsonl",JSON.stringify(S,null,2)),r.file("val.jsonl",JSON.stringify(_,null,2)))}else{const f={image:`images/${L}`,caption:e.edited||e.generated||"",metadata:{image_id:e.image_count&&e.image_count>1?e.all_image_ids||[e.image_id]:e.image_id,title:e.title,source:e.source,event_type:e.event_type,image_type:e.image_type,countries:e.countries,starred:e.starred,image_count:e.image_count||1}};r&&r.file("0001.json",JSON.stringify(f,null,2))}}catch(n){throw console.error(`Failed to process image ${e.image_id}:`,n),n}}const s=await u.generateAsync({type:"blob"}),l=URL.createObjectURL(s),o=document.createElement("a");o.href=l,o.download=`dataset_${e.image_type}_${e.image_id}_${t}_${new Date().toISOString().split("T")[0]}.zip`,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(l),console.log(`Exported ${e.image_type} dataset with 1 image in ${t} mode`),te(!0)}catch(i){console.error("Export failed:",i),alert("Failed to export dataset. Please try again.")}finally{ae(!1)}}};return W?a.jsx(Y,{children:a.jsx("div",{className:p.loadingContainer,children:a.jsxs("div",{className:"flex flex-col items-center gap-4",children:[a.jsx(we,{className:"text-ifrcRed"}),a.jsx("div",{children:"Loading map details..."})]})})}):le||!e?a.jsx(Y,{children:a.jsx("div",{className:p.errorContainer,children:a.jsxs("div",{className:"flex flex-col items-center gap-4 text-center",children:[a.jsx("div",{className:"text-4xl",children:"β οΈ"}),a.jsx("div",{className:"text-xl font-semibold",children:"Unable to load map"}),a.jsx("div",{children:le||"Map not found"}),a.jsx(F,{name:"back-to-explore",variant:"secondary",onClick:()=>v("/explore"),children:"Return to Explore"})]})})}):a.jsxs(Y,{children:[a.jsxs("div",{className:"max-w-7xl mx-auto",children:[a.jsxs("div",{className:p.tabSelector,children:[a.jsx(sa,{name:"map-details-view",value:re,onChange:t=>{(t==="mapDetails"||t==="explore")&&(je(t),t==="explore"&&v("/explore"))},options:We,keySelector:t=>t.key,labelSelector:t=>t.label}),a.jsxs("div",{className:"flex items-center gap-2 ml-auto",children:[a.jsx(P,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:a.jsxs(F,{name:"reference-examples",variant:k?"primary":"secondary",onClick:()=>Oe(!k),className:"whitespace-nowrap",children:[a.jsx("span",{className:"mr-2",children:k?a.jsx("span",{className:"text-yellow-400",children:"β
"}):a.jsx("span",{className:"text-yellow-400",children:"β"})}),"Reference Examples"]})}),a.jsx(F,{name:"export-dataset",variant:"secondary",onClick:()=>Z(!0),children:"Export"})]})]}),a.jsx(la,{sources:ce,types:de,regions:me,countries:Le,imageTypes:ge,isLoadingFilters:!1}),re==="mapDetails"?a.jsx("div",{className:"relative",children:g?a.jsxs(a.Fragment,{children:[a.jsxs("div",{className:p.gridLayout,children:[a.jsxs(P,{heading:a.jsxs("div",{className:"flex items-center gap-2",children:[a.jsx("span",{children:g.title||"Map Image"}),g.starred&&a.jsx("span",{className:"text-red-500 text-xl",title:"Starred image",children:"β
"})]}),headingLevel:2,withHeaderBorder:!0,withInternalPadding:!0,spacing:"comfortable",children:[a.jsx("div",{className:p.imageContainer,children:e?.image_count&&e.image_count>1||x.length>1?a.jsxs("div",{className:p.carouselContainer,children:[a.jsx("div",{className:p.carouselImageWrapper,children:G?a.jsxs("div",{className:p.imagePlaceholder,children:[a.jsx(we,{className:"text-ifrcRed"}),a.jsx("div",{children:"Loading images..."})]}):x[M]?.detail_url?a.jsx("img",{src:x[M].detail_url,alt:x[M].file_key,className:p.carouselImage,onError:t=>{console.log("MapDetailsPage: Detail image failed to load, falling back to original:",x[M].detail_url);const i=t.target;x[M].image_url&&(i.src=x[M].image_url)},onLoad:()=>console.log("MapDetailsPage: Detail image loaded successfully:",x[M].detail_url)}):x[M]?.image_url?a.jsx("img",{src:x[M].image_url,alt:x[M].file_key,className:p.carouselImage,onLoad:()=>console.log("MapDetailsPage: Original image loaded successfully:",x[M].image_url)}):a.jsx("div",{className:p.imagePlaceholder,children:"No image available"})}),a.jsxs("div",{className:p.carouselNavigation,children:[a.jsx(F,{name:"previous-image",variant:"tertiary",size:1,onClick:Be,disabled:G,className:p.carouselButton,children:a.jsx(ie,{className:"w-4 h-4"})}),a.jsx("div",{className:p.carouselIndicators,children:x.map((t,i)=>a.jsx("button",{onClick:()=>He(i),className:`${p.carouselIndicator} ${i===M?p.carouselIndicatorActive:""}`,disabled:G,children:i+1},i))}),a.jsx(F,{name:"next-image",variant:"tertiary",size:1,onClick:Je,disabled:G,className:p.carouselButton,children:a.jsx(oe,{className:"w-4 h-4"})})]}),a.jsx("div",{className:p.viewImageButtonContainer,children:a.jsx(F,{name:"view-full-size-carousel",variant:"secondary",size:1,onClick:()=>ye(x[M]),disabled:G||!x[M]?.image_url,children:"View Image"})})]}):a.jsxs("div",{className:p.singleImageContainer,children:[g.detail_url?a.jsx("img",{src:g.detail_url,alt:g.file_key,onError:t=>{console.log("MapDetailsPage: Detail image failed to load, falling back to original:",g.detail_url);const i=t.target;g.image_url&&(i.src=g.image_url)},onLoad:()=>console.log("MapDetailsPage: Detail image loaded successfully:",g.detail_url)}):g.image_url?a.jsx("img",{src:g.image_url,alt:g.file_key,onLoad:()=>console.log("MapDetailsPage: Original image loaded successfully:",g.image_url)}):a.jsx("div",{className:p.imagePlaceholder,children:"No image available"}),a.jsx("div",{className:p.viewImageButtonContainer,children:a.jsx(F,{name:"view-full-size-single",variant:"secondary",size:1,onClick:()=>ye(g),disabled:!g.image_url,children:"View Image"})})]})}),a.jsx(P,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-md p-2",children:a.jsxs("div",{className:p.metadataTags,children:[g.image_type!=="drone_image"&&a.jsx("span",{className:p.metadataTag,children:ce.find(t=>t.s_code===g.source)?.label||g.source}),a.jsx("span",{className:p.metadataTag,children:de.find(t=>t.t_code===g.event_type)?.label||g.event_type}),a.jsx("span",{className:p.metadataTag,children:ge.find(t=>t.image_type===g.image_type)?.label||g.image_type}),g.countries&&g.countries.length>0&&a.jsxs(a.Fragment,{children:[a.jsx("span",{className:p.metadataTag,children:me.find(t=>t.r_code===g.countries[0].r_code)?.label||"Unknown Region"}),a.jsx("span",{className:p.metadataTag,children:g.countries.map(t=>t.label).join(", ")})]}),g.image_count&&g.image_count>1&&a.jsxs("span",{className:p.metadataTag,title:`Multi-upload with ${g.image_count} images`,children:["π· ",g.image_count]}),(!g.image_count||g.image_count<=1)&&a.jsx("span",{className:p.metadataTag,title:"Single Upload",children:"Single"})]})})]}),a.jsx("div",{className:p.detailsSection,children:g.edited&&g.edited.includes("Description:")||g.generated&&g.generated.includes("Description:")?a.jsx(P,{heading:"AI Generated Content",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,spacing:"comfortable",children:a.jsx("div",{className:p.captionContainer,children:a.jsx("div",{className:p.captionText,children:(g.edited||g.generated||"").split(`
|
3 |
`).map((t,i)=>a.jsx("div",{children:t.startsWith("Description:")||t.startsWith("Analysis:")||t.startsWith("Recommended Actions:")?a.jsx("h4",{className:"font-semibold text-gray-800 mt-4 mb-2",children:t}):t.trim()===""?a.jsx("br",{}):a.jsx("p",{className:"mb-2",children:t})},i))})})}):a.jsx(P,{heading:"Description",headingLevel:3,withHeaderBorder:!0,withInternalPadding:!0,spacing:"comfortable",children:a.jsx("div",{className:p.captionContainer,children:g.generated?a.jsx("div",{className:p.captionText,children:a.jsx("p",{children:g.edited||g.generated})}):a.jsx("p",{children:"β no caption yet β"})})})})]}),a.jsx("div",{className:"flex items-center justify-center mt-8",children:a.jsx(P,{withInternalPadding:!0,className:"bg-white/20 backdrop-blur-sm rounded-lg p-4",children:a.jsxs("div",{className:"flex items-center gap-4",children:[ke&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"previous-item",variant:"tertiary",size:1,className:`bg-white/90 hover:bg-white shadow-lg border border-gray-200 ${B?"opacity-50 cursor-not-allowed":"hover:scale-110"}`,onClick:()=>ve("previous"),disabled:B,children:a.jsxs("div",{className:"flex items-center gap-1",children:[a.jsxs("div",{className:"flex -space-x-1",children:[a.jsx(ie,{className:"w-4 h-4"}),a.jsx(ie,{className:"w-4 h-4"})]}),a.jsx("span",{className:"font-semibold",children:"Previous"})]})})}),ne&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"delete",variant:"tertiary",size:1,className:"bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 hover:border-red-300",onClick:Ge,title:"Delete","aria-label":"Delete saved image",children:a.jsx(ia,{className:"w-4 h-4"})})}),a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"contribute",onClick:Qe,children:"Contribute"})}),ne&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"toggle-star",variant:"tertiary",size:1,className:`${e?.starred?"bg-red-100 hover:bg-red-200 text-red-800 border-2 border-red-400":"bg-gray-100 hover:bg-gray-200 text-gray-600 border-2 border-gray-300"} w-16 h-8 rounded-full transition-all duration-200 flex items-center justify-center`,onClick:qe,title:e?.starred?"Unstar image":"Star image","aria-label":e?.starred?"Unstar image":"Star image",children:a.jsx("span",{className:`text-lg transition-all duration-200 ${e?.starred?"text-red-600":"text-gray-500"}`,children:e?.starred?"β
":"β"})})}),Te&&a.jsx(P,{withInternalPadding:!0,className:"rounded-md p-2",children:a.jsx(F,{name:"next-item",variant:"tertiary",size:1,className:`bg-white/90 hover:bg-white shadow-lg border border-gray-200 ${B?"opacity-50 cursor-not-allowed":"hover:scale-110"}`,onClick:()=>ve("next"),disabled:B,children:a.jsxs("div",{className:"flex items-center gap-1",children:[a.jsx("span",{className:"font-semibold",children:"Next"}),a.jsxs("div",{className:"flex -space-x-1",children:[a.jsx(oe,{className:"w-4 h-4"}),a.jsx(oe,{className:"w-4 h-4"})]})]})})})]})})})]}):a.jsxs("div",{className:"text-center py-12",children:[a.jsx("div",{className:"text-xl font-semibold text-gray-600 mb-4",children:"No matches found"}),a.jsx("div",{className:"mt-4",children:a.jsx(F,{name:"clear-filters",variant:"secondary",onClick:Ue,children:"Clear Filters"})})]})}):null]}),Ee&&a.jsx("div",{className:p.fullSizeModalOverlay,onClick:()=>K(!1),children:a.jsx("div",{className:p.fullSizeModalContent,onClick:t=>t.stopPropagation(),children:a.jsxs("div",{className:p.ratingWarningContent,children:[a.jsx("h3",{className:p.ratingWarningTitle,children:"Delete Image?"}),a.jsx("p",{className:p.ratingWarningText,children:"This action cannot be undone. Are you sure you want to delete this saved image and all related data?"}),a.jsxs("div",{className:p.ratingWarningButtons,children:[a.jsx(F,{name:"confirm-delete",variant:"secondary",onClick:Ke,children:"Delete"}),a.jsx(F,{name:"cancel-delete",variant:"tertiary",onClick:()=>K(!1),children:"Cancel"})]})]})})}),ue&&a.jsx(ca,{isOpen:ue,onClose:()=>{Z(!1),te(!1),ae(!1)},onExport:(t,i)=>{i.includes(e.image_type)&&Xe(t)},filteredCount:1,totalCount:1,hasFilters:!1,crisisMapsCount:e.image_type==="crisis_map"?1:0,droneImagesCount:e.image_type==="drone_image"?1:0,isLoading:Pe,exportSuccess:Re,variant:"single",onNavigateToList:()=>{Z(!1),v("/explore")},onNavigateAndExport:()=>{Z(!1),v("/explore?export=true")}}),a.jsx(oa,{isOpen:$e,imageUrl:ze?.image_url||null,preview:null,selectedImageData:null,onClose:Ve,isLoading:Ae})]})}export{st as default};
|
py_backend/static/assets/{jszip.min-B2-9j7XO.js β jszip.min-C3B0d4UO.js}
RENAMED
@@ -1,4 +1,4 @@
|
|
1 |
-
import{
|
2 |
|
3 |
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
4 |
<http://stuartk.com/jszip>
|
|
|
1 |
+
import{X as bt,Y as It}from"./index-BCz4bkWK.js";function vt(yt){throw new Error('Could not dynamically require "'+yt+'". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.')}var kt={exports:{}};/*!
|
2 |
|
3 |
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
4 |
<http://stuartk.com/jszip>
|
py_backend/static/assets/{useAdmin-B_kWg5HW.js β useAdmin-LSEnRjVv.js}
RENAMED
@@ -1 +1 @@
|
|
1 |
-
import{r,
|
|
|
1 |
+
import{r,W as e}from"./index-BCz4bkWK.js";const o=()=>{const t=r.useContext(e);if(t===void 0)throw new Error("useAdmin must be used within an AdminProvider");return t};export{o as u};
|
py_backend/static/index.html
CHANGED
@@ -42,7 +42,7 @@
|
|
42 |
});
|
43 |
}
|
44 |
</script>
|
45 |
-
<script type="module" crossorigin src="/assets/index-
|
46 |
<link rel="stylesheet" crossorigin href="/assets/index-FBu17hMI.css">
|
47 |
</head>
|
48 |
<body>
|
|
|
42 |
});
|
43 |
}
|
44 |
</script>
|
45 |
+
<script type="module" crossorigin src="/assets/index-BCz4bkWK.js"></script>
|
46 |
<link rel="stylesheet" crossorigin href="/assets/index-FBu17hMI.css">
|
47 |
</head>
|
48 |
<body>
|
start-local.bat
CHANGED
@@ -13,7 +13,7 @@ echo.
|
|
13 |
echo 3. Starting Backend on port 8000...
|
14 |
cd py_backend
|
15 |
call .venv\Scripts\activate
|
16 |
-
start "Backend" cmd /k "uvicorn app.main:app --
|
17 |
|
18 |
echo.
|
19 |
echo 4. Starting Frontend Dev Server on port 5173...
|
|
|
13 |
echo 3. Starting Backend on port 8000...
|
14 |
cd py_backend
|
15 |
call .venv\Scripts\activate
|
16 |
+
start "Backend" cmd /k "uvicorn app.main:app --host 0.0.0.0 --port 8000"
|
17 |
|
18 |
echo.
|
19 |
echo 4. Starting Frontend Dev Server on port 5173...
|