Skip to content

Commit 71e9a30

Browse files
Asaif AliAsaif Ali
authored andcommitted
Redesigning the UI
1 parent 8eed07f commit 71e9a30

1 file changed

Lines changed: 187 additions & 11 deletions

File tree

streamlit_ui/app.py

Lines changed: 187 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import json
7+
import html
78
import time
89
import uuid
910
from pathlib import Path
@@ -159,6 +160,156 @@ def typewriter(text: str, speed: float = 0.012):
159160
placeholder.markdown(f'<div class="glass-card fade-in">{text}</div>', unsafe_allow_html=True)
160161

161162

163+
def _as_float(value, default=0.0):
164+
try:
165+
return float(value)
166+
except (TypeError, ValueError):
167+
return default
168+
169+
170+
def _release_readiness(migration_name: str, completed_payload: dict) -> dict:
171+
"""Build a factual release-readiness summary from persisted backend evidence."""
172+
cache_key = f"release_readiness::{migration_name}"
173+
cached = st.session_state.get(cache_key)
174+
if isinstance(cached, dict):
175+
return cached
176+
result = {
177+
"release_gate": bool(completed_payload.get("release_ready", False)),
178+
"semantic_status": "not_available",
179+
"security_status": "not_available",
180+
"security_critical": 0,
181+
"verification_score": None,
182+
"confidence": "Review",
183+
}
184+
try:
185+
semantic = client.semantic_verification(migration_name).get("data", {})
186+
result["semantic_status"] = str(semantic.get("status", "not_available"))
187+
result["verification_score"] = semantic.get("score")
188+
except ApiError:
189+
pass
190+
try:
191+
evidence = client.migration_evidence(migration_name).get("data", {})
192+
security = evidence.get("security_review", {}) or {}
193+
result["security_status"] = str(security.get("status", "not_available"))
194+
result["security_critical"] = int(security.get("critical", 0) or 0)
195+
except (ApiError, TypeError, ValueError):
196+
pass
197+
198+
if (
199+
result["release_gate"]
200+
and result["semantic_status"] == "verified"
201+
and result["security_status"] == "passed"
202+
and result["security_critical"] == 0
203+
):
204+
result["confidence"] = "High"
205+
elif result["semantic_status"] in {"verified", "partial"} and result["security_status"] in {"passed", "review"}:
206+
result["confidence"] = "Moderate"
207+
st.session_state[cache_key] = result
208+
return result
209+
210+
211+
def render_release_readiness(migration_name: str, completed_payload: dict) -> None:
212+
"""Render a compact, data-backed release readiness card."""
213+
readiness = _release_readiness(migration_name, completed_payload)
214+
gate_label = "PASS" if readiness["release_gate"] else "BLOCKED"
215+
gate_icon = "✓" if readiness["release_gate"] else "!"
216+
st.markdown(
217+
'<div class="release-grid">'
218+
f'<div class="release-card"><div class="release-label">Release readiness</div>'
219+
f'<div style="display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin:.2rem 0 .45rem">'
220+
f'<div class="release-score">{readiness["confidence"]}</div>'
221+
f'<div class="release-status">{gate_icon} Release gate · {gate_label}</div></div>'
222+
f'<div class="change-copy">Semantic verification: <b>{readiness["semantic_status"].replace("_", " ").title()}</b> · Security review: <b>{readiness["security_status"].replace("_", " ").title()}</b></div></div>'
223+
f'<div class="release-card"><div class="release-label">Evidence snapshot</div>'
224+
f'<div class="delta-row"><span class="delta-label">Verification score</span><span class="delta-value">{readiness["verification_score"] if readiness["verification_score"] is not None else "—"}</span></div>'
225+
f'<div class="delta-row"><span class="delta-label">Critical security findings</span><span class="delta-value">{readiness["security_critical"]}</span></div>'
226+
f'<div class="delta-row"><span class="delta-label">Next action</span><span class="delta-value">{"Review & release" if readiness["release_gate"] else "Resolve release blockers"}</span></div></div>'
227+
'</div>',
228+
unsafe_allow_html=True,
229+
)
230+
231+
232+
def render_change_explorer(migration_name: str) -> None:
233+
"""Show a before/after modernization snapshot using real comparison-report data."""
234+
cache_key = f"change_report::{migration_name}"
235+
report = st.session_state.get(cache_key)
236+
if not isinstance(report, dict):
237+
try:
238+
report = client.report(migration_name, persist=False, include_markdown=False, require_migrated=True)
239+
st.session_state[cache_key] = report
240+
except ApiError:
241+
st.info("Comparison evidence becomes available after the migration report is ready.")
242+
return
243+
244+
if report.get("status") != "ready":
245+
st.info(report.get("message", "Comparison report is not ready yet."))
246+
return
247+
248+
cards = report.get("module_review_cards") or []
249+
mode = report.get("analysis_mode", "unknown")
250+
st.markdown('<div class="panel-kicker">CODE CHANGE EXPLORER</div>', unsafe_allow_html=True)
251+
st.markdown('<div class="panel-title">Before → after modernization snapshot</div>', unsafe_allow_html=True)
252+
st.markdown('<div class="panel-subtitle">A concise comparison built from the persisted migration report — no simulated diff counts.</div>', unsafe_allow_html=True)
253+
254+
if mode == "source_vs_migrated":
255+
total_files = 0
256+
total_functions_delta = 0
257+
total_loc_delta = 0
258+
total_dep_delta = 0
259+
measurable = 0
260+
for card in cards:
261+
delta = card.get("what_changed", {}).get("semantic_delta", {}) or {}
262+
if delta.get("function_delta") is not None:
263+
total_functions_delta += int(delta.get("function_delta") or 0)
264+
total_loc_delta += int(delta.get("loc_delta") or 0)
265+
total_dep_delta += int(delta.get("dependency_delta") or 0)
266+
measurable += 1
267+
m1, m2, m3, m4 = st.columns(4)
268+
m1.metric("Modules compared", len(cards))
269+
m2.metric("Function delta", f"{total_functions_delta:+d}" if measurable else "—")
270+
m3.metric("LOC delta", f"{total_loc_delta:+d}" if measurable else "—")
271+
m4.metric("Dependency delta", f"{total_dep_delta:+d}" if measurable else "—")
272+
273+
preview = cards[:3]
274+
for card in preview:
275+
change = card.get("what_changed", {}) or {}
276+
delta = change.get("semantic_delta", {}) or {}
277+
module = card.get("module", "module")
278+
pattern = change.get("legacy_to_modern_pattern", "Comparison available")
279+
left_copy = f"Legacy pattern: {pattern}"
280+
right_copy = "; ".join(change.get("key_transformations", [])[:2]) or "Modernized structure recorded in the migration report."
281+
st.markdown(
282+
'<div class="change-grid">'
283+
f'<div class="change-card before"><div class="change-title">Before · {html.escape(str(module))}</div><div class="change-copy">{html.escape(str(left_copy))}</div></div>'
284+
f'<div class="change-card after"><div class="change-title">After · {html.escape(str(module))}</div><div class="change-copy">{html.escape(str(right_copy))}</div>'
285+
f'<div class="delta-row"><span class="delta-label">Functions</span><span class="delta-value">{delta.get("function_delta", "—")}</span></div>'
286+
f'<div class="delta-row"><span class="delta-label">LOC</span><span class="delta-value">{delta.get("loc_delta", "—")}</span></div>'
287+
f'<div class="delta-row"><span class="delta-label">Dependencies</span><span class="delta-value">{delta.get("dependency_delta", "—")}</span></div></div>'
288+
'</div>',
289+
unsafe_allow_html=True,
290+
)
291+
else:
292+
# Migrated-only mode: be explicit rather than pretending to have a source diff.
293+
st.warning("Source baseline comparison is not available for this migration, so the explorer is showing migrated-code evidence only.")
294+
for card in cards[:4]:
295+
st.markdown(
296+
f'<div class="change-card after"><div class="change-title">{html.escape(str(card.get("module", "module")))}</div>'
297+
f'<div class="change-copy">{html.escape(str(card.get("reason", "Review the module-level risk profile.")))}</div></div>',
298+
unsafe_allow_html=True,
299+
)
300+
301+
302+
def render_codebase_answer(migration_name: str, answer: dict, is_target: bool) -> None:
303+
"""Render an answer with lightweight evidence metadata, not hidden reasoning."""
304+
route = "Target code" if is_target else "Source code"
305+
st.markdown(
306+
f'<div class="release-status">⌁ Route · {route}</div>',
307+
unsafe_allow_html=True,
308+
)
309+
typewriter(answer.get("answer", ""))
310+
st.caption("Evidence is grounded in the selected migration knowledge base; internal reasoning is not exposed.")
311+
312+
162313
# The backend now streams Agno Workflow step events into the task status API.
163314
# This UI renders those persisted events; it is no longer a time-based approximation.
164315
PIPELINE_STAGES = [
@@ -398,6 +549,8 @@ def count_up(label: str, target: int, duration: float = 0.5, steps: int = 12):
398549
st.session_state.active_migration_name = migration_name
399550
st.session_state.active_task_started_at = time.time()
400551
st.session_state.completion_toast_shown = False
552+
st.session_state.pop(f"release_readiness::{migration_name}", None)
553+
st.session_state.pop(f"change_report::{migration_name}", None)
401554
st.session_state.last_progress = {"stage": "workflow", "percent": 2, "message": "Preparing workflow telemetry"}
402555
st.toast(f"Migration '{migration_name}' queued")
403556
st.success(f"Migration queued · `{result['task_id']}`")
@@ -491,7 +644,10 @@ def count_up(label: str, target: int, duration: float = 0.5, steps: int = 12):
491644
st.download_button("Download release artifact", data=data, file_name=f"{st.session_state.active_migration_name}.zip", mime="application/zip", width="stretch")
492645
except ApiError:
493646
st.caption("Download will be available shortly.")
494-
st.info("Next: open Architecture & Analysis or Semantic Verification to inspect release evidence.")
647+
if st.session_state.active_migration_name:
648+
render_release_readiness(st.session_state.active_migration_name, completed_payload)
649+
render_change_explorer(st.session_state.active_migration_name)
650+
st.info("Next: inspect Architecture & Analysis, Semantic Verification, or Ask the Codebase.")
495651
elif badge_class == "failed":
496652
raw_failed = status.get("result")
497653
failed_payload = {}
@@ -767,28 +923,48 @@ def count_up(label: str, target: int, duration: float = 0.5, steps: int = 12):
767923
st.caption("Complete a migration, then enter its name here to inspect semantic verification evidence.")
768924

769925
# --------------------------------------------------------------------------
770-
# Tab: Chat
926+
# Tab: Ask the Codebase
771927
# --------------------------------------------------------------------------
772928
if active_section == "Ask the Codebase":
773-
st.markdown("#### Ask questions about a migrated codebase")
929+
st.markdown('<div class="panel-kicker">CODE INTELLIGENCE</div>', unsafe_allow_html=True)
930+
st.markdown('<div class="panel-title">Ask the codebase why the migration looks the way it does.</div>', unsafe_allow_html=True)
931+
st.markdown('<div class="panel-subtitle">Use the persisted migration knowledge base to inspect source or target behavior without exposing internal agent reasoning.</div>', unsafe_allow_html=True)
932+
774933
chat_migration = st.text_input(
775-
"Migration name", value=st.session_state.active_migration_name or ""
934+
"Migration name", value=st.session_state.active_migration_name or "", key="analysis_chat_migration"
935+
)
936+
is_target = st.checkbox("Ask about the target (converted) code", value=False)
937+
938+
st.markdown('<div style="margin:.65rem 0 .35rem"><span class="release-label">Suggested questions</span></div>', unsafe_allow_html=True)
939+
prompt_cols = st.columns(3)
940+
prompts = [
941+
"What changed in the authentication flow?",
942+
"Which modules need the most review?",
943+
"How did the REST contract map to the target?",
944+
]
945+
selected_prompt = None
946+
for idx, (col, prompt) in enumerate(zip(prompt_cols, prompts)):
947+
if col.button(prompt, key=f"prompt_{idx}", width="stretch"):
948+
selected_prompt = prompt
949+
question = st.text_area(
950+
"Your question",
951+
value=selected_prompt or "",
952+
placeholder="e.g. Why was the payment service split during migration?",
953+
height=110,
776954
)
777-
question = st.text_area("Your question", placeholder="What does the payment module do?")
778-
is_target = st.checkbox("Ask about the target (converted) code instead of source")
779955

780-
if st.button("Ask", type="primary"):
781-
if not chat_migration or not question:
956+
if st.button("Ask the codebase", type="primary", width="stretch"):
957+
if not chat_migration or not question.strip():
782958
st.warning("Enter a migration name and a question.")
783959
else:
784960
thinking_slot = st.empty()
785961
with thinking_slot:
786962
if not try_lottie(THINKING_LOTTIE_URL, height=120):
787-
st.spinner("Thinking...")
963+
st.spinner("Searching migration evidence...")
788964
try:
789-
answer = client.chat_ask(chat_migration, question, is_target=is_target)
965+
answer = client.chat_ask(chat_migration, question.strip(), is_target=is_target)
790966
thinking_slot.empty()
791-
typewriter(answer["answer"])
967+
render_codebase_answer(chat_migration, answer, is_target)
792968
except ApiError as e:
793969
thinking_slot.empty()
794970
st.error(e.detail)

0 commit comments

Comments
 (0)