199199 padding-top: 0;
200200 }
201201 .history-item strong { color: var(--ink); }
202+ .summary-grid {
203+ display: grid;
204+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
205+ gap: 12px;
206+ }
207+ .summary-card {
208+ padding: 14px;
209+ border-radius: 14px;
210+ border: 1px solid var(--line);
211+ background: #fffdf8;
212+ display: grid;
213+ gap: 6px;
214+ }
215+ .summary-card.ok {
216+ border-color: rgba(15, 118, 110, 0.28);
217+ background: rgba(15, 118, 110, 0.07);
218+ }
219+ .summary-card.warn {
220+ border-color: rgba(180, 83, 9, 0.32);
221+ background: rgba(180, 83, 9, 0.08);
222+ }
223+ .summary-card.error {
224+ border-color: rgba(185, 28, 28, 0.28);
225+ background: rgba(185, 28, 28, 0.08);
226+ }
227+ .summary-label {
228+ color: var(--muted);
229+ font-size: 0.82rem;
230+ text-transform: uppercase;
231+ letter-spacing: 0.08em;
232+ }
233+ .summary-value {
234+ color: var(--ink);
235+ font-size: 1.1rem;
236+ font-weight: 700;
237+ line-height: 1.2;
238+ word-break: break-word;
239+ }
202240 .downloads a {
203241 color: var(--accent);
204242 font-weight: 700;
295333 </div>
296334 <div class="card output">
297335 <div class="banner $status_class">$message</div>
336+ $summary_cards
298337 $download_links
299338 $history_panel
300339 <pre>$output</pre>
@@ -442,6 +481,105 @@ def render_history_panel(history: list[dict[str, Any]] | None = None, recent_dow
442481 return '' .join (sections )
443482
444483
484+ def summarize_action_result (action : str , result : dict [str , Any ] | None ) -> list [dict [str , str ]]:
485+ if not result :
486+ return []
487+ payload = result .get ("result" , {})
488+ cards : list [dict [str , str ]] = []
489+
490+ def add (label : str , value : Any , tone : str = "" ) -> None :
491+ if value is None :
492+ return
493+ rendered = str (value )
494+ if not rendered .strip ():
495+ return
496+ cards .append ({"label" : label , "value" : rendered , "tone" : tone })
497+
498+ if action == "detect" :
499+ matches = payload .get ("matches" , [])
500+ add ("Matches" , len (matches ), "ok" )
501+ if matches :
502+ add ("Top Format" , matches [0 ][0 ], "ok" )
503+ add ("Confidence" , f"{ matches [0 ][1 ]} %" )
504+ return cards
505+
506+ if action == "inspect" :
507+ add ("Package" , payload .get ("package_id" ), "ok" )
508+ add ("Entries" , payload .get ("entry_count" ), "ok" )
509+ add ("Kinds" , len (payload .get ("kinds" , [])))
510+ return cards
511+
512+ if action == "normalize" :
513+ add ("Package" , payload .get ("package_id" ), "ok" )
514+ add ("Entries" , payload .get ("entry_count" ), "ok" )
515+ add ("Output" , Path (payload .get ("output_path" , "" )).name if payload .get ("output_path" ) else None )
516+ return cards
517+
518+ if action == "validate" :
519+ summary = payload .get ("summary" , {})
520+ is_ok = bool (payload .get ("ok" ))
521+ add ("Status" , "Valid" if is_ok else "Invalid" , "ok" if is_ok else "error" )
522+ add ("Errors" , summary .get ("error_count" , 0 ), "error" if summary .get ("error_count" , 0 ) else "ok" )
523+ add ("Warnings" , summary .get ("warning_count" , 0 ), "warn" if summary .get ("warning_count" , 0 ) else "ok" )
524+ add ("Entries" , summary .get ("entry_count" , 0 ))
525+ return cards
526+
527+ if action == "report" :
528+ audit = payload .get ("audit" , {})
529+ add ("Package" , payload .get ("package_id" ), "ok" )
530+ add ("Entries" , payload .get ("entry_count" , 0 ), "ok" )
531+ add ("Issues" , audit .get ("issues_found" , 0 ), "warn" if audit .get ("issues_found" , 0 ) else "ok" )
532+ add ("Formats" , len (payload .get ("source_formats" , [])))
533+ return cards
534+
535+ if action == "doctor" :
536+ summary = payload .get ("doctor_summary" , {})
537+ add ("Health" , summary .get ("health_score" , 0 ), "ok" if summary .get ("health_score" , 0 ) >= 80 else "warn" )
538+ add ("Issues" , summary .get ("issue_count" , 0 ), "warn" if summary .get ("issue_count" , 0 ) else "ok" )
539+ add ("Suggestions" , summary .get ("suggestion_count" , 0 ), "warn" if summary .get ("suggestion_count" , 0 ) else "ok" )
540+ add ("Repairable" , summary .get ("repairable_entry_count" , 0 ))
541+ return cards
542+
543+ if action == "suggest" :
544+ add ("Suggestions" , payload .get ("suggestion_count" , 0 ), "warn" if payload .get ("suggestion_count" , 0 ) else "ok" )
545+ if payload .get ("suggestions" ):
546+ add ("Top Severity" , payload ["suggestions" ][0 ].get ("severity" , "unknown" ))
547+ return cards
548+
549+ if action == "bundle" :
550+ add ("Entries" , payload .get ("export_entry_count" ) or payload .get ("entry_count" ), "ok" )
551+ add ("Target" , payload .get ("output" , {}).get ("target_format" ) if isinstance (payload .get ("output" ), dict ) else payload .get ("target_format" ), "ok" )
552+ doctor_summary = payload .get ("doctor_summary" , {})
553+ add ("Health" , doctor_summary .get ("health_score" ), "warn" if doctor_summary .get ("health_score" , 100 ) < 80 else "ok" )
554+ add ("Downloads" , len (result .get ("downloads" , [])), "ok" )
555+ return cards
556+
557+ if action == "schema" :
558+ add ("Schema" , payload .get ("title" ), "ok" )
559+ add ("Version" , payload .get ("properties" , {}).get ("schema_version" , {}).get ("default" , "1.0" ))
560+ add ("Entry Fields" , len (payload .get ("$defs" , {}).get ("memoryEntry" , {}).get ("required" , [])))
561+ return cards
562+
563+ return cards
564+
565+
566+ def render_summary_cards (cards : list [dict [str , str ]] | None ) -> str :
567+ if not cards :
568+ return ""
569+ rows = ['<div class="summary-grid">' ]
570+ for item in cards :
571+ tone = str (item .get ("tone" , "" )).strip ()
572+ tone_class = f" { tone } " if tone else ""
573+ rows .append (
574+ f'<div class="summary-card{ tone_class } ">'
575+ f'<div class="summary-label">{ _html_escape (item ["label" ])} </div>'
576+ f'<div class="summary-value">{ _html_escape (item ["value" ])} </div>'
577+ '</div>'
578+ )
579+ rows .append ('</div>' )
580+ return '' .join (rows )
581+
582+
445583def render_download_links (downloads : list [dict [str , str ]] | None ) -> str :
446584 if not downloads :
447585 return ""
@@ -565,11 +703,13 @@ def _handle_run(self) -> None:
565703 message = f"Action '{ action } ' completed successfully."
566704 status_class = "ok"
567705 downloads = result .get ("downloads" , [])
706+ summary_cards = summarize_action_result (action , result )
568707 output = json .dumps (result , indent = 2 , ensure_ascii = False )
569708 record_action_history (action , True , input_path , source_format , target_format , output_path , message , downloads )
570709 except Exception as exc :
571710 message = f"Action '{ action } ' failed: { exc } "
572711 status_class = "error"
712+ summary_cards = []
573713 output = json .dumps ({"ok" : False , "action" : action , "error" : str (exc )}, indent = 2 , ensure_ascii = False )
574714 record_action_history (action , False , input_path , source_format , target_format , output_path , message , [])
575715 self ._send_html (
@@ -585,6 +725,7 @@ def _handle_run(self) -> None:
585725 status_class = status_class ,
586726 output = output ,
587727 downloads = downloads ,
728+ summary_cards = summary_cards ,
588729 )
589730 )
590731
@@ -612,6 +753,11 @@ def _handle_upload(self) -> None:
612753 message = message ,
613754 status_class = "ok" ,
614755 output = json .dumps (saved , indent = 2 , ensure_ascii = False ),
756+ summary_cards = [
757+ {"label" : "Upload" , "value" : Path (saved ["zip_path" ]).name , "tone" : "ok" },
758+ {"label" : "Workspace" , "value" : saved ["workspace_id" ]},
759+ {"label" : "Extracted" , "value" : Path (saved ["input_path" ]).name or saved ["input_path" ]},
760+ ],
615761 )
616762 )
617763 except Exception as exc :
@@ -657,6 +803,7 @@ def render_page(
657803 output : str = '{\n "status": "idle"\n }' ,
658804 downloads : list [dict [str , str ]] | None = None ,
659805 history : list [dict [str , Any ]] | None = None ,
806+ summary_cards : list [dict [str , str ]] | None = None ,
660807) -> str :
661808 adapters = sorted (build_registry ())
662809 profiles = sorted (list_profiles ())
@@ -674,6 +821,7 @@ def render_page(
674821 message = _html_escape (message ),
675822 status_class = _html_escape (status_class ),
676823 output = _html_escape (output ),
824+ summary_cards = render_summary_cards (summary_cards ),
677825 download_links = render_download_links (downloads ),
678826 history_panel = render_history_panel (history , _recent_downloads ()),
679827 )
0 commit comments