Spaces:
Sleeping
Fix 3 root cause bugs blocking all 3 export docs
Browse files1. export_core.py: fix broken regex r'|\s+|' → r'\|\s+\|'
- unescaped pipes in regex = OR operator → matched every char position
- ALL content rendered as vertical pipe-table (|E|r|r|o|r|e|...)
- one-char fix eliminates the vertical text bug across all docs
2. export_html.py: sidebar-header + cover use accent not primary
- when club primary = white (#FFFFFF), sidebar was white-on-white invisible
- sidebar-header: var(--primary) → var(--accent) (already dark)
- cover gradient: start from accent (dark) not primary (white)
- _process_section_content: validate content before rendering
shows orange warning box instead of broken content on API error
3. agents.py: on API failure return empty content not error message
- was: return {'content': wrapped.user_message}
- error text 'Errore di connessione.' stored as plan section content
- when exported it caused the vertical text bug (+ fix 1 above)
- now: return {'content': ''} with error info in metadata only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- agents.py +5 -5
- export_core.py +2 -1
- export_html.py +15 -5
|
@@ -1034,7 +1034,7 @@ e soggette a revisione post-allineamento.
|
|
| 1034 |
except Exception as e:
|
| 1035 |
wrapped = handle_exception(e, context=f"agent_{self.spec.name}_ollama")
|
| 1036 |
log_exception(wrapped, context=f"agent_{self.spec.name}")
|
| 1037 |
-
return {'content':
|
| 1038 |
|
| 1039 |
# === OPENROUTER PATH ===
|
| 1040 |
elif self._provider == "openrouter" and self._openrouter_client:
|
|
@@ -1050,7 +1050,7 @@ e soggette a revisione post-allineamento.
|
|
| 1050 |
except Exception as e:
|
| 1051 |
wrapped = handle_exception(e, context=f"agent_{self.spec.name}_openrouter")
|
| 1052 |
log_exception(wrapped, context=f"agent_{self.spec.name}")
|
| 1053 |
-
return {'content':
|
| 1054 |
|
| 1055 |
# === GEMINI PATH (default) ===
|
| 1056 |
else:
|
|
@@ -1094,20 +1094,20 @@ e soggette a revisione post-allineamento.
|
|
| 1094 |
details={"agent": self.spec.name, "fallback_error": str(fallback_e)}
|
| 1095 |
)
|
| 1096 |
log_exception(err, context=f"agent_{self.spec.name}")
|
| 1097 |
-
return {'content':
|
| 1098 |
else:
|
| 1099 |
err = AgentError(
|
| 1100 |
message=f"Agent {self.spec.name} InvalidArgument: {e}",
|
| 1101 |
details={"agent": self.spec.name, "error_type": "InvalidArgument"}
|
| 1102 |
)
|
| 1103 |
log_exception(err, context=f"agent_{self.spec.name}")
|
| 1104 |
-
return {'content':
|
| 1105 |
|
| 1106 |
except Exception as e:
|
| 1107 |
# Map to appropriate error type
|
| 1108 |
wrapped = handle_exception(e, context=f"agent_{self.spec.name}")
|
| 1109 |
log_exception(wrapped, context=f"agent_{self.spec.name}")
|
| 1110 |
-
return {'content':
|
| 1111 |
|
| 1112 |
cleaned = self._post_process(raw_content)
|
| 1113 |
content, sources, unverified = self.sourcer.process_content(cleaned, context=club_data.get('club_name', ''))
|
|
|
|
| 1034 |
except Exception as e:
|
| 1035 |
wrapped = handle_exception(e, context=f"agent_{self.spec.name}_ollama")
|
| 1036 |
log_exception(wrapped, context=f"agent_{self.spec.name}")
|
| 1037 |
+
return {'content': '', 'sources': [], 'unverified_claims': [], 'metadata': {'error_id': wrapped.error_id, 'error_msg': wrapped.user_message}}
|
| 1038 |
|
| 1039 |
# === OPENROUTER PATH ===
|
| 1040 |
elif self._provider == "openrouter" and self._openrouter_client:
|
|
|
|
| 1050 |
except Exception as e:
|
| 1051 |
wrapped = handle_exception(e, context=f"agent_{self.spec.name}_openrouter")
|
| 1052 |
log_exception(wrapped, context=f"agent_{self.spec.name}")
|
| 1053 |
+
return {'content': '', 'sources': [], 'unverified_claims': [], 'metadata': {'error_id': wrapped.error_id, 'error_msg': wrapped.user_message}}
|
| 1054 |
|
| 1055 |
# === GEMINI PATH (default) ===
|
| 1056 |
else:
|
|
|
|
| 1094 |
details={"agent": self.spec.name, "fallback_error": str(fallback_e)}
|
| 1095 |
)
|
| 1096 |
log_exception(err, context=f"agent_{self.spec.name}")
|
| 1097 |
+
return {'content': '', 'sources': [], 'unverified_claims': [], 'metadata': {'error_id': err.error_id, 'error_msg': err.user_message}}
|
| 1098 |
else:
|
| 1099 |
err = AgentError(
|
| 1100 |
message=f"Agent {self.spec.name} InvalidArgument: {e}",
|
| 1101 |
details={"agent": self.spec.name, "error_type": "InvalidArgument"}
|
| 1102 |
)
|
| 1103 |
log_exception(err, context=f"agent_{self.spec.name}")
|
| 1104 |
+
return {'content': '', 'sources': [], 'unverified_claims': [], 'metadata': {'error_id': err.error_id, 'error_msg': err.user_message}}
|
| 1105 |
|
| 1106 |
except Exception as e:
|
| 1107 |
# Map to appropriate error type
|
| 1108 |
wrapped = handle_exception(e, context=f"agent_{self.spec.name}")
|
| 1109 |
log_exception(wrapped, context=f"agent_{self.spec.name}")
|
| 1110 |
+
return {'content': '', 'sources': [], 'unverified_claims': [], 'metadata': {'error_id': wrapped.error_id, 'error_msg': wrapped.user_message}}
|
| 1111 |
|
| 1112 |
cleaned = self._post_process(raw_content)
|
| 1113 |
content, sources, unverified = self.sourcer.process_content(cleaned, context=club_data.get('club_name', ''))
|
|
@@ -171,7 +171,8 @@ class BaseExporter(ABC):
|
|
| 171 |
content = content.replace('\r\n', '\n')
|
| 172 |
|
| 173 |
# TABLES: Separate markdown table rows that are on a single line
|
| 174 |
-
|
|
|
|
| 175 |
|
| 176 |
# Pattern: ### 1. TITLE -> converts to header h3 (removes the number)
|
| 177 |
content = re.sub(r'###\s+\d+[\.\)]\s*', '\n\n### ', content)
|
|
|
|
| 171 |
content = content.replace('\r\n', '\n')
|
| 172 |
|
| 173 |
# TABLES: Separate markdown table rows that are on a single line
|
| 174 |
+
# NOTE: pipes must be escaped with \| — unescaped | in regex means OR
|
| 175 |
+
content = re.sub(r'\|\s+\|', '|\n|', content)
|
| 176 |
|
| 177 |
# Pattern: ### 1. TITLE -> converts to header h3 (removes the number)
|
| 178 |
content = re.sub(r'###\s+\d+[\.\)]\s*', '\n\n### ', content)
|
|
@@ -127,9 +127,19 @@ class ChunkedHTMLExporter(BaseExporter):
|
|
| 127 |
|
| 128 |
def _process_section_content(self, content: str) -> str:
|
| 129 |
"""Converte contenuto markdown-like in HTML usando BaseExporter logic"""
|
| 130 |
-
if
|
| 131 |
-
return "<p><em>Contenuto non disponibile</em></p>"
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
normalized = self._normalize_markdown(content)
|
| 134 |
return self._markdown_to_html(normalized)
|
| 135 |
|
|
@@ -326,7 +336,7 @@ class ChunkedHTMLExporter(BaseExporter):
|
|
| 326 |
display: flex; flex-direction: column; z-index: 1000;
|
| 327 |
}}
|
| 328 |
|
| 329 |
-
.sidebar-header {{ padding: 24px 20px; background: var(--
|
| 330 |
.nav {{ flex: 1; overflow-y: auto; padding: 16px 10px; }}
|
| 331 |
.nav-item {{ display: block; padding: 10px 14px; margin-bottom: 3px; color: #4a5568; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 0.85rem; border-left: 3px solid transparent; }}
|
| 332 |
.nav-item:hover {{ background: var(--bg); color: var(--accent); border-left-color: var(--accent); }}
|
|
@@ -335,7 +345,7 @@ class ChunkedHTMLExporter(BaseExporter):
|
|
| 335 |
.container {{ max-width: 900px; margin: 0 auto; padding: 60px 40px; }}
|
| 336 |
|
| 337 |
.cover {{
|
| 338 |
-
height: 55vh; background: linear-gradient(150deg, var(--
|
| 339 |
color: white; display: flex; flex-direction: column;
|
| 340 |
justify-content: center; align-items: center; text-align: center;
|
| 341 |
position: relative; overflow: hidden;
|
|
|
|
| 127 |
|
| 128 |
def _process_section_content(self, content: str) -> str:
|
| 129 |
"""Converte contenuto markdown-like in HTML usando BaseExporter logic"""
|
| 130 |
+
if not content:
|
| 131 |
+
return "<p><em>Contenuto non disponibile per questa sezione.</em></p>"
|
| 132 |
+
|
| 133 |
+
# Guard: content troppo corto o messaggio d'errore → non renderizzare
|
| 134 |
+
_error_markers = ["errore di connessione", "verifica la connessione", "si è verificato un errore"]
|
| 135 |
+
if len(content.strip()) < 80 or any(m in content.lower() for m in _error_markers):
|
| 136 |
+
return (
|
| 137 |
+
'<div style="border-left:4px solid #F57C00; background:#FFF8E1; padding:14px 18px; '
|
| 138 |
+
'border-radius:0 6px 6px 0; color:#555; font-style:italic;">'
|
| 139 |
+
'⚠ Sezione non generata correttamente. Rigenera il piano per ottenere i contenuti completi.'
|
| 140 |
+
'</div>'
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
normalized = self._normalize_markdown(content)
|
| 144 |
return self._markdown_to_html(normalized)
|
| 145 |
|
|
|
|
| 336 |
display: flex; flex-direction: column; z-index: 1000;
|
| 337 |
}}
|
| 338 |
|
| 339 |
+
.sidebar-header {{ padding: 24px 20px; background: var(--accent); color: var(--text-on-primary); text-align: center; border-bottom: 3px solid var(--primary); }}
|
| 340 |
.nav {{ flex: 1; overflow-y: auto; padding: 16px 10px; }}
|
| 341 |
.nav-item {{ display: block; padding: 10px 14px; margin-bottom: 3px; color: #4a5568; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 0.85rem; border-left: 3px solid transparent; }}
|
| 342 |
.nav-item:hover {{ background: var(--bg); color: var(--accent); border-left-color: var(--accent); }}
|
|
|
|
| 345 |
.container {{ max-width: 900px; margin: 0 auto; padding: 60px 40px; }}
|
| 346 |
|
| 347 |
.cover {{
|
| 348 |
+
height: 55vh; background: linear-gradient(150deg, var(--accent) 0%, var(--primary) 100%);
|
| 349 |
color: white; display: flex; flex-direction: column;
|
| 350 |
justify-content: center; align-items: center; text-align: center;
|
| 351 |
position: relative; overflow: hidden;
|