Spaces:
Sleeping
Sleeping
Commit Β·
ca2b985
1
Parent(s): 8f5b53f
feat: introduce gradio-based interface for courtroom trials
Browse files- src/code_tribunal/ui/__init__.py +3 -0
- src/code_tribunal/ui/app.py +68 -0
- src/code_tribunal/ui/exports.py +46 -0
- src/code_tribunal/ui/helpers.py +47 -0
- src/code_tribunal/ui/pipeline.py +145 -0
- src/code_tribunal/ui/styles.py +32 -0
src/code_tribunal/ui/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CodeTribunal Gradio UI package."""
|
| 2 |
+
|
| 3 |
+
from code_tribunal.ui.app import create_app, main
|
src/code_tribunal/ui/app.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Gradio application layout and launch."""
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
|
| 8 |
+
from code_tribunal.config import TribunalConfig
|
| 9 |
+
from code_tribunal.ui.exports import export_md, export_pdf
|
| 10 |
+
from code_tribunal.ui.pipeline import handle_question, run_courtroom
|
| 11 |
+
from code_tribunal.ui.styles import CSS
|
| 12 |
+
|
| 13 |
+
logging.basicConfig(level=logging.DEBUG, format="[%(asctime)s][%(levelname)s] %(message)s", datefmt="%H:%M:%S")
|
| 14 |
+
logging.getLogger("crewai").setLevel(logging.WARNING)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def create_app() -> gr.Blocks:
|
| 18 |
+
logo = Path(__file__).resolve().parent.parent.parent.parent / "assets" / "logo.png"
|
| 19 |
+
|
| 20 |
+
with gr.Blocks(title="CodeTribunal") as app:
|
| 21 |
+
with gr.Column(visible=True) as hero:
|
| 22 |
+
if logo.exists():
|
| 23 |
+
gr.Image(value=str(logo), show_label=False, height=160, container=False, elem_classes=["hero-logo"])
|
| 24 |
+
gr.Markdown("# CodeTribunal\n### The AI Courtroom That Exposes Bad Freelance Code", elem_classes=["hero-title"])
|
| 25 |
+
gr.Markdown("Upload a .zip of code and watch a multi-agent forensic investigation unfold.", elem_classes=["hero-subtitle"])
|
| 26 |
+
|
| 27 |
+
with gr.Column(visible=True, elem_classes=["upload-area"]) as upload:
|
| 28 |
+
code_input = gr.File(label="Drop your .zip here", file_types=[".zip"], interactive=True)
|
| 29 |
+
|
| 30 |
+
with gr.Column(visible=False) as processing:
|
| 31 |
+
status = gr.Markdown("Initializing...", elem_classes=["status-phase"])
|
| 32 |
+
chatbot = gr.Chatbot(label="Courtroom Transcript", height=600, elem_classes=["chatbot"])
|
| 33 |
+
with gr.Row():
|
| 34 |
+
exp_md = gr.Button("π Export Markdown", visible=False)
|
| 35 |
+
exp_pdf = gr.Button("π Export PDF", visible=False)
|
| 36 |
+
exp_file = gr.File(label="Download", visible=False)
|
| 37 |
+
qa_input = gr.Textbox(label="Ask a follow-up", placeholder="e.g., 'Why was eval() critical?'", visible=False)
|
| 38 |
+
qa_btn = gr.Button("π΅οΈ Ask Expert Witness", variant="primary", visible=False)
|
| 39 |
+
|
| 40 |
+
state = gr.State(value={})
|
| 41 |
+
|
| 42 |
+
code_input.upload(
|
| 43 |
+
fn=run_courtroom, inputs=[code_input],
|
| 44 |
+
outputs=[hero, upload, processing, status, chatbot, exp_md, exp_pdf, exp_file, state],
|
| 45 |
+
).then(
|
| 46 |
+
fn=lambda: (gr.update(visible=True), gr.update(visible=True)),
|
| 47 |
+
outputs=[qa_input, qa_btn],
|
| 48 |
+
)
|
| 49 |
+
exp_md.click(fn=export_md, inputs=[state], outputs=[exp_file])
|
| 50 |
+
exp_pdf.click(fn=export_pdf, inputs=[state], outputs=[exp_file])
|
| 51 |
+
qa_btn.click(fn=handle_question, inputs=[qa_input, chatbot, state], outputs=[chatbot, qa_input])
|
| 52 |
+
qa_input.submit(fn=handle_question, inputs=[qa_input, chatbot, state], outputs=[chatbot, qa_input])
|
| 53 |
+
|
| 54 |
+
return app
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def main() -> None:
|
| 58 |
+
app = create_app()
|
| 59 |
+
app.launch(
|
| 60 |
+
server_name=TribunalConfig().server_host,
|
| 61 |
+
server_port=TribunalConfig().server_port,
|
| 62 |
+
css=CSS,
|
| 63 |
+
theme=gr.themes.Base(primary_hue="amber", secondary_hue="slate", neutral_hue="slate"),
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|
src/code_tribunal/ui/exports.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Report export: Markdown and PDF."""
|
| 2 |
+
|
| 3 |
+
import tempfile
|
| 4 |
+
import time
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from markdown_pdf import MarkdownPdf, Section
|
| 8 |
+
|
| 9 |
+
from code_tribunal.ui.helpers import evidence_html
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def export_md(ctx: dict) -> str:
|
| 13 |
+
"""Export trial context as a Markdown file. Returns the file path."""
|
| 14 |
+
md = [
|
| 15 |
+
"# CodeTribunal β Trial Report\n",
|
| 16 |
+
f"**Generated**: {time.strftime('%Y-%m-%d %H:%M:%S')}\n",
|
| 17 |
+
"---\n",
|
| 18 |
+
]
|
| 19 |
+
for key in ["evidence", "verdict", "report"]:
|
| 20 |
+
value = ctx.get(key, "")
|
| 21 |
+
if value:
|
| 22 |
+
md.append(f"## {key.title()}\n\n{value}\n\n")
|
| 23 |
+
fp = tempfile.mktemp(suffix="_CodeTribunal_Report.md")
|
| 24 |
+
Path(fp).write_text("\n".join(md))
|
| 25 |
+
return fp
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def export_pdf(ctx: dict) -> str:
|
| 29 |
+
"""Export trial context as a PDF file. Returns the file path."""
|
| 30 |
+
sections = []
|
| 31 |
+
for key in ["evidence", "verdict", "report"]:
|
| 32 |
+
value = ctx.get(key, "")
|
| 33 |
+
if value:
|
| 34 |
+
sections.append(f"## {key.title()}\n\n{value}")
|
| 35 |
+
|
| 36 |
+
full_md = (
|
| 37 |
+
f"# CodeTribunal - Trial Report\n\n"
|
| 38 |
+
f"*Generated: {time.strftime('%Y-%m-%d %H:%M:%S')}*\n\n---\n\n"
|
| 39 |
+
+ "\n".join(sections)
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
pdf = MarkdownPdf()
|
| 43 |
+
pdf.add_section(Section(full_md))
|
| 44 |
+
fp = tempfile.mktemp(suffix="_CodeTribunal_Report.pdf")
|
| 45 |
+
pdf.save(fp)
|
| 46 |
+
return fp
|
src/code_tribunal/ui/helpers.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HTML rendering helpers for the Gradio UI."""
|
| 2 |
+
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
from code_tribunal.ui.styles import SEVERITY_COLORS
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def escape_html(text: str) -> str:
|
| 9 |
+
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def severity_badge(severity: str) -> str:
|
| 13 |
+
color = SEVERITY_COLORS.get(severity, "#6b7280")
|
| 14 |
+
return (
|
| 15 |
+
f'<span style="background:{color};color:white;padding:2px 8px;'
|
| 16 |
+
f'border-radius:4px;font-size:12px;font-weight:bold">{severity}</span>'
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def evidence_html(report) -> str:
|
| 21 |
+
"""Render an EvidenceReport as an HTML table."""
|
| 22 |
+
lines = [
|
| 23 |
+
"<h3>Evidence Report</h3>",
|
| 24 |
+
f"<p>Files: <b>{report.file_count}</b> | Findings: <b>{len(report.findings)}</b></p>",
|
| 25 |
+
]
|
| 26 |
+
for domain, findings in report.findings_by_domain.items():
|
| 27 |
+
lines.append(f"<h4>{domain.title()} ({len(findings)})</h4>")
|
| 28 |
+
lines.append('<table style="width:100%;border-collapse:collapse">')
|
| 29 |
+
lines.append(
|
| 30 |
+
'<tr style="border-bottom:1px solid #333">'
|
| 31 |
+
"<th style='text-align:left;padding:4px'>Sev</th>"
|
| 32 |
+
"<th style='text-align:left;padding:4px'>File</th>"
|
| 33 |
+
"<th style='text-align:left;padding:4px'>Line</th>"
|
| 34 |
+
"<th style='text-align:left;padding:4px'>Code</th></tr>"
|
| 35 |
+
)
|
| 36 |
+
for finding in findings:
|
| 37 |
+
lines.append(
|
| 38 |
+
f'<tr style="border-bottom:1px solid #222">'
|
| 39 |
+
f"<td style='padding:4px'>{severity_badge(finding.severity_hint)}</td>"
|
| 40 |
+
f"<td style='padding:4px;font-family:monospace;font-size:13px'>"
|
| 41 |
+
f"{Path(finding.file).name}</td>"
|
| 42 |
+
f"<td style='padding:4px;font-family:monospace'>{finding.line}</td>"
|
| 43 |
+
f"<td style='padding:4px;font-family:monospace;font-size:13px;color:#a0a0a0'>"
|
| 44 |
+
f"{escape_html(finding.code)}</td></tr>"
|
| 45 |
+
)
|
| 46 |
+
lines.append("</table>")
|
| 47 |
+
return "\n".join(lines)
|
src/code_tribunal/ui/pipeline.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pipeline orchestration: runs the courtroom and streams UI updates."""
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
import tempfile
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
|
| 8 |
+
from code_tribunal.config import TribunalConfig
|
| 9 |
+
from code_tribunal.courtroom import Courtroom
|
| 10 |
+
from code_tribunal.evidence import safe_extract_zip
|
| 11 |
+
from code_tribunal.pipeline import Phase
|
| 12 |
+
from code_tribunal.ui.helpers import escape_html, evidence_html
|
| 13 |
+
from code_tribunal.ui.styles import PHASE_LABELS
|
| 14 |
+
|
| 15 |
+
log = logging.getLogger("code_tribunal")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def run_courtroom(code_input, progress=gr.Progress()):
|
| 19 |
+
"""Run the full pipeline, yielding updates to the UI."""
|
| 20 |
+
chat = []
|
| 21 |
+
ev_html = ""
|
| 22 |
+
verdict_text = ""
|
| 23 |
+
report_text = ""
|
| 24 |
+
config = TribunalConfig()
|
| 25 |
+
|
| 26 |
+
if code_input is None or not (hasattr(code_input, "name") and code_input.name.endswith(".zip")):
|
| 27 |
+
yield (
|
| 28 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
|
| 29 |
+
"### Please upload a .zip file.", [],
|
| 30 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), {},
|
| 31 |
+
)
|
| 32 |
+
return
|
| 33 |
+
|
| 34 |
+
yield (
|
| 35 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
|
| 36 |
+
"### Extracting files...", [],
|
| 37 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), {},
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
tmpdir = tempfile.mkdtemp()
|
| 41 |
+
try:
|
| 42 |
+
safe_extract_zip(code_input.name, tmpdir)
|
| 43 |
+
except ValueError as e:
|
| 44 |
+
yield (
|
| 45 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
|
| 46 |
+
f"### Error: {escape_html(str(e))}", [],
|
| 47 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), {},
|
| 48 |
+
)
|
| 49 |
+
return
|
| 50 |
+
|
| 51 |
+
if not config.is_configured:
|
| 52 |
+
yield (
|
| 53 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
|
| 54 |
+
"### ZAI_API_KEY not set.", [],
|
| 55 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), {},
|
| 56 |
+
)
|
| 57 |
+
return
|
| 58 |
+
|
| 59 |
+
courtroom = Courtroom(config)
|
| 60 |
+
courtroom.pipeline.create_run(code_input.name)
|
| 61 |
+
current_phase = Phase.IDLE
|
| 62 |
+
|
| 63 |
+
for event in courtroom.run(tmpdir):
|
| 64 |
+
if event.phase != current_phase:
|
| 65 |
+
current_phase = event.phase
|
| 66 |
+
log.debug("[UI] Phase -> %s", current_phase)
|
| 67 |
+
|
| 68 |
+
if event.data and "report" in event.data and event.data["report"] is not None:
|
| 69 |
+
robj = event.data["report"]
|
| 70 |
+
if hasattr(robj, "findings"):
|
| 71 |
+
ev_html = evidence_html(robj)
|
| 72 |
+
chat.append({
|
| 73 |
+
"role": "user",
|
| 74 |
+
"content": f"**Case Filed**: {robj.file_count} files, **{len(robj.findings)}** findings.",
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
if event.data and "reports" in event.data:
|
| 78 |
+
for domain, text in event.data["reports"].items():
|
| 79 |
+
icon = {"security": "π‘οΈ", "quality": "π", "architecture": "ποΈ"}.get(domain, "π")
|
| 80 |
+
chat.append({
|
| 81 |
+
"role": "assistant",
|
| 82 |
+
"content": f"**{icon} {domain.title()} Investigation**\n\n{text[:3000]}{'...' if len(text) > 3000 else ''}",
|
| 83 |
+
})
|
| 84 |
+
|
| 85 |
+
if event.data and "transcript" in event.data:
|
| 86 |
+
for section in event.data["transcript"].split("\n\n"):
|
| 87 |
+
for rnd in ["PROSECUTION", "DEFENSE", "REBUTTAL"]:
|
| 88 |
+
if section.startswith(f"=== {rnd}"):
|
| 89 |
+
content = section.replace(f"=== {rnd} ===", "").strip()
|
| 90 |
+
icon = {"PROSECUTION": "βοΈ", "DEFENSE": "π‘οΈ", "REBUTTAL": "βοΈ"}.get(rnd, "π")
|
| 91 |
+
chat.append({
|
| 92 |
+
"role": "assistant",
|
| 93 |
+
"content": f"**{icon} {rnd.title()}**\n\n{content[:3000]}{'...' if len(content) > 3000 else ''}",
|
| 94 |
+
})
|
| 95 |
+
break
|
| 96 |
+
|
| 97 |
+
if event.data and "verdict" in event.data:
|
| 98 |
+
verdict_text = event.data["verdict"]
|
| 99 |
+
chat.append({
|
| 100 |
+
"role": "assistant",
|
| 101 |
+
"content": f"**π¨ Judge's Verdict**\n\n{verdict_text[:3000]}{'...' if len(verdict_text) > 3000 else ''}",
|
| 102 |
+
})
|
| 103 |
+
|
| 104 |
+
if event.data and "report" in event.data and isinstance(event.data["report"], str):
|
| 105 |
+
report_text = event.data["report"]
|
| 106 |
+
chat.append({
|
| 107 |
+
"role": "assistant",
|
| 108 |
+
"content": f"**π Final Report**\n\n{report_text[:4000]}{'...' if len(report_text) > 4000 else ''}",
|
| 109 |
+
})
|
| 110 |
+
|
| 111 |
+
show_export = event.phase == Phase.COMPLETE
|
| 112 |
+
ctx = {}
|
| 113 |
+
if show_export:
|
| 114 |
+
log.debug("[UI] COMPLETE β showing export")
|
| 115 |
+
ctx = {"evidence": ev_html, "verdict": verdict_text, "report": report_text}
|
| 116 |
+
|
| 117 |
+
label = PHASE_LABELS.get(current_phase, current_phase.value)
|
| 118 |
+
if current_phase not in (Phase.COMPLETE, Phase.FAILED):
|
| 119 |
+
status = f'<h3 class="phase-active">{label}</h3>\n{event.status}'
|
| 120 |
+
else:
|
| 121 |
+
status = f"### {label}\n{event.status}"
|
| 122 |
+
|
| 123 |
+
log.debug("[UI] yield β chat msgs: %d, status: %s, export: %s", len(chat), label, show_export)
|
| 124 |
+
|
| 125 |
+
yield (
|
| 126 |
+
gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),
|
| 127 |
+
status, chat,
|
| 128 |
+
gr.update(visible=show_export), gr.update(visible=show_export),
|
| 129 |
+
gr.update(visible=show_export), ctx,
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def handle_question(question: str, history: list, ctx: dict) -> tuple:
|
| 134 |
+
"""Handle a follow-up question from the Q&A interface."""
|
| 135 |
+
if not question.strip():
|
| 136 |
+
return history, ""
|
| 137 |
+
config = TribunalConfig()
|
| 138 |
+
courtroom = Courtroom(config)
|
| 139 |
+
answer = courtroom.ask_question(question, ctx)
|
| 140 |
+
history.append({"role": "user", "content": question})
|
| 141 |
+
history.append({
|
| 142 |
+
"role": "assistant",
|
| 143 |
+
"content": f"**π΅οΈ Expert Witness**\n\n{answer[:3000]}{'...' if len(answer) > 3000 else ''}",
|
| 144 |
+
})
|
| 145 |
+
return history, ""
|
src/code_tribunal/ui/styles.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""UI constants: CSS, severity colors, phase labels."""
|
| 2 |
+
|
| 3 |
+
from code_tribunal.pipeline import Phase
|
| 4 |
+
|
| 5 |
+
SEVERITY_COLORS = {"CRITICAL": "#dc2626", "HIGH": "#ea580c", "MEDIUM": "#ca8a04", "LOW": "#2563eb"}
|
| 6 |
+
|
| 7 |
+
CSS = """
|
| 8 |
+
.gradio-container{min-width:1280px!important;max-width:1310px!important;margin:0 auto!important}
|
| 9 |
+
body{background:#0a0a14!important}
|
| 10 |
+
.hero-logo{display:block!important;margin:0 auto 12px auto!important;border-radius:16px!important}
|
| 11 |
+
.hero-title{text-align:center!important;color:#fbbf24!important;font-family:Georgia,serif!important;font-size:2.4em!important}
|
| 12 |
+
.hero-subtitle{text-align:center!important;color:#94a3b8!important;font-size:1.1em!important}
|
| 13 |
+
.upload-area .file-preview{min-height:220px!important;border:2px dashed #fbbf2440!important;border-radius:16px!important;background:#1a1a2e!important}
|
| 14 |
+
.upload-area .file-preview:hover{border-color:#fbbf24!important}
|
| 15 |
+
.chatbot{border:none!important;box-shadow:none!important}
|
| 16 |
+
.contain{border:none!important;box-shadow:none!important;background:transparent!important}
|
| 17 |
+
[data-testid="status-tracker"]{border:none!important;box-shadow:none!important}
|
| 18 |
+
.bot.svelte-1nr59td.message{border:none!important;box-shadow:none!important;background:transparent!important}
|
| 19 |
+
.phase-active{font-family:sans-serif;font-size:1.5rem;font-weight:bold;background:linear-gradient(to left,#333 20%,#888 40%,#eee 50%,#888 60%,#333 80%);background-size:200% auto;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;color:transparent;animation:shine 3s linear infinite}
|
| 20 |
+
@keyframes shine{to{background-position:200% center}}
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
PHASE_LABELS = {
|
| 24 |
+
Phase.EVIDENCE: "Phase 1/7: Forensic Evidence",
|
| 25 |
+
Phase.GRAPH: "Phase 2/7: Code Graph",
|
| 26 |
+
Phase.INVESTIGATION: "Phase 3/7: Investigation",
|
| 27 |
+
Phase.TRIAL: "Phase 4/7: The Trial",
|
| 28 |
+
Phase.VERDICT: "Phase 5/7: Verdict",
|
| 29 |
+
Phase.REPORT: "Phase 6/7: Final Report",
|
| 30 |
+
Phase.COMPLETE: "Trial Complete",
|
| 31 |
+
Phase.FAILED: "Error",
|
| 32 |
+
}
|