Spaces:
Build error
fix: page IDs with trailing dots, Reader profile handling, Editor 404 UX
Browse filesThree bugs reported by user fixed:
1. Page IDs ending with a dot (e.g., "beatest-plat_sup."): _sanitize_label
now strips leading/trailing dots from canvas labels before generating
page IDs. IIIF manifests from Gallica can have labels like "plat sup."
which produced invalid IDs breaking URL routing and file paths.
2. Reader shows no image when profile query param is missing: fetchProfile
is now optional — if profileId is empty or the fetch fails, the Reader
loads pages anyway without layer filtering. Previously, the entire
Reader showed an error if the profile couldn't be loaded.
3. Editor shows raw "HTTP 404" for unanalyzed pages: now shows a
user-friendly message "Cette page n'a pas encore ete analysee par l'IA.
Lancez le pipeline depuis Administration."
https://claude.ai/code/session_01UB4he7RdRPHLvNjky4X8Sw
|
@@ -40,8 +40,13 @@ _ALLOWED_MIME_PREFIXES = ("image/",)
|
|
| 40 |
|
| 41 |
|
| 42 |
def _sanitize_label(label: str) -> str:
|
| 43 |
-
"""Nettoie un folio_label : garde uniquement alphanum, -, _, .
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
clean = Path(label).name # retire tout chemin
|
|
|
|
| 45 |
if not _SAFE_LABEL_RE.match(clean) or not clean:
|
| 46 |
clean = re.sub(r"[^\w\-\.]", "_", clean) or "page"
|
| 47 |
return clean
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
def _sanitize_label(label: str) -> str:
|
| 43 |
+
"""Nettoie un folio_label : garde uniquement alphanum, -, _, .
|
| 44 |
+
|
| 45 |
+
Les points en début/fin sont supprimés pour éviter des IDs de page
|
| 46 |
+
se terminant par un point (problèmes de routing et de chemins fichier).
|
| 47 |
+
"""
|
| 48 |
clean = Path(label).name # retire tout chemin
|
| 49 |
+
clean = clean.strip(".") # supprimer les points en début/fin
|
| 50 |
if not _SAFE_LABEL_RE.match(clean) or not clean:
|
| 51 |
clean = re.sub(r"[^\w\-\.]", "_", clean) or "page"
|
| 52 |
return clean
|
|
@@ -67,7 +67,12 @@ export default function Editor() {
|
|
| 67 |
const ext = m.extensions as { region_validations?: Record<string, string> } | undefined
|
| 68 |
setRegionValidations(ext?.region_validations ?? {})
|
| 69 |
} catch (e: unknown) {
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
} finally {
|
| 72 |
setLoading(false)
|
| 73 |
}
|
|
|
|
| 67 |
const ext = m.extensions as { region_validations?: Record<string, string> } | undefined
|
| 68 |
setRegionValidations(ext?.region_validations ?? {})
|
| 69 |
} catch (e: unknown) {
|
| 70 |
+
const msg = (e as Error).message ?? ''
|
| 71 |
+
if (msg.includes('404')) {
|
| 72 |
+
setError('Cette page n\'a pas encore ete analysee par l\'IA. Lancez le pipeline depuis Administration.')
|
| 73 |
+
} else {
|
| 74 |
+
setError(msg)
|
| 75 |
+
}
|
| 76 |
} finally {
|
| 77 |
setLoading(false)
|
| 78 |
}
|
|
@@ -34,12 +34,19 @@ export default function Reader() {
|
|
| 34 |
const [error, setError] = useState<string | null>(null)
|
| 35 |
|
| 36 |
useEffect(() => {
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
.then(([pgs, prof]) => {
|
| 39 |
const sorted = [...pgs].sort((a, b) => a.sequence - b.sequence)
|
| 40 |
setPages(sorted)
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
| 43 |
})
|
| 44 |
.catch((e: Error) => setError(e.message))
|
| 45 |
.finally(() => setLoading(false))
|
|
|
|
| 34 |
const [error, setError] = useState<string | null>(null)
|
| 35 |
|
| 36 |
useEffect(() => {
|
| 37 |
+
const loadPages = fetchPages(manuscriptId)
|
| 38 |
+
const loadProfile = profileId
|
| 39 |
+
? fetchProfile(profileId).catch(() => null)
|
| 40 |
+
: Promise.resolve(null)
|
| 41 |
+
|
| 42 |
+
Promise.all([loadPages, loadProfile])
|
| 43 |
.then(([pgs, prof]) => {
|
| 44 |
const sorted = [...pgs].sort((a, b) => a.sequence - b.sequence)
|
| 45 |
setPages(sorted)
|
| 46 |
+
if (prof) {
|
| 47 |
+
setProfile(prof)
|
| 48 |
+
setVisibleLayers(new Set(prof.active_layers))
|
| 49 |
+
}
|
| 50 |
})
|
| 51 |
.catch((e: Error) => setError(e.message))
|
| 52 |
.finally(() => setLoading(false))
|