File size: 4,185 Bytes
e3518f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""
ScamSentry — Gradio UI

Thin presentation layer over the same orchestrator engine cli.py uses —
both call agents.orchestrator.run_pipeline() directly, no duplicated
pipeline logic. The "Agent Trajectory" panel just dumps the same state
dict run_pipeline() already returns (intake_result, reputation_signals,
linguistic_signal, injection_guard_result, draft_verdict,
semantic_check_result, final_verdict, report_text) — no new
instrumentation needed.

Note on types: some agents (url_reputation_agent) have no output_schema,
so their state value is the model's raw JSON text, not a parsed dict —
same reason risk_synthesis_gate.py defensively does
`json.loads(x) if isinstance(x, str) else x` before touching intake_result
or semantic_check_result. This file does the same before rendering.
"""

import json
import mimetypes

import gradio as gr

from agents.orchestrator import run_pipeline

TRAJECTORY_KEYS = [
    "intake_result",
    "reputation_signals",
    "linguistic_signal",
    "injection_guard_result",
    "draft_verdict",
    "semantic_check_result",
    "final_verdict",
]


def _maybe_parse(value):
    """Parse a value as JSON if it's a string; return it unchanged
    otherwise (or if it's a string but not valid JSON, e.g. report_text,
    which is deliberately plain prose)."""
    if isinstance(value, str):
        try:
            return json.loads(value)
        except (json.JSONDecodeError, TypeError):
            return value
    return value


def format_verdict_panel(result: dict) -> str:
    verdict = _maybe_parse(result.get("final_verdict")) or {}
    report = result.get("report_text") or "(no report generated)"

    risk = str(verdict.get("risk_level", "unknown")).upper()
    confidence = verdict.get("confidence", "unknown")
    injection = verdict.get("injection_attempt_detected", False)

    lines = [f"### Risk level: {risk}", f"**Confidence:** {confidence}"]
    if injection:
        lines.append("**⚠️ Prompt-injection attempt detected in the analyzed content.**")
    lines.append("")
    lines.append(report)
    return "\n".join(lines)


def format_trajectory(result: dict) -> str:
    trajectory = {k: _maybe_parse(result.get(k)) for k in TRAJECTORY_KEYS if k in result}
    return json.dumps(trajectory, indent=2, default=str)


async def scan(raw_text, image_path):
    if not raw_text and not image_path:
        return "Please paste a message or upload a screenshot.", "{}"

    image_bytes = None
    image_mime = "image/png"
    if image_path:
        with open(image_path, "rb") as f:
            image_bytes = f.read()
        guessed, _ = mimetypes.guess_type(image_path)
        if guessed:
            image_mime = guessed

    try:
        result = await run_pipeline(
            text=raw_text or None,
            image_bytes=image_bytes,
            image_mime=image_mime,
            verbose=False,
        )
    except Exception as e:
        return f"⚠️ Scan failed: {e}", "{}"

    if not result:
        return "⚠️ Pipeline returned no result — check the logs.", "{}"

    return format_verdict_panel(result), format_trajectory(result)


with gr.Blocks(title="ScamSentry") as demo:
    gr.Markdown(
        "# ScamSentry\n"
        "Paste a suspicious message or upload a screenshot to check it for scam/phishing signals."
    )

    with gr.Row():
        with gr.Column():
            text_input = gr.Textbox(
                label="Message text",
                placeholder="Paste the email, SMS, or WhatsApp message here...",
                lines=8,
            )
            image_input = gr.Image(label="Or upload a screenshot", type="filepath")
            scan_button = gr.Button("Scan", variant="primary")

        with gr.Column():
            verdict_output = gr.Markdown(label="Verdict")
            with gr.Accordion("Agent Trajectory (debug view)", open=False):
                trajectory_output = gr.Code(label="Raw agent state", language="json")

    scan_button.click(
        fn=scan,
        inputs=[text_input, image_input],
        outputs=[verdict_output, trajectory_output],
    )

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)