File size: 5,544 Bytes
3087a05
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import joblib
import json
import gradio as gr
import numpy as np
import os

# -----------------------------
# Load models
# -----------------------------
BASE_DIR = "saved_models"

human_ai_model = joblib.load(os.path.join(BASE_DIR, "human_ai_model.joblib"))
attrib_model = joblib.load(os.path.join(BASE_DIR, "attrib_model.joblib"))

# Load config (optional but recommended)
config_path = os.path.join(BASE_DIR, "config.json")
if os.path.exists(config_path):
    with open(config_path) as f:
        CONFIG = json.load(f)
else:
    CONFIG = {
        "min_words": 30,
        "human_confidence_threshold": 0.5,
        "attrib_confidence_gap": 0.15
    }

# -----------------------------
# Helper functions
# -----------------------------

def classify_with_confidence(text, model, top_k=3):
    scores = model.decision_function([text])[0]
    classes = model.classes_
    
    ranked = sorted(
        zip(classes, scores),
        key=lambda x: x[1],
        reverse=True
    )
    return ranked[:top_k]


def safe_attribution(text):
    ranked = classify_with_confidence(text, attrib_model)
    
    best_model, best_score = ranked[0]
    second_model, second_score = ranked[1]
    
    confidence_gap = best_score - second_score
    
    return {
        "predicted_model": best_model,
        "confidence_gap": round(confidence_gap, 3),
        "top_candidates": [
            (m, round(s, 3)) for m, s in ranked
        ]
    }


def human_ai_decision(text):
    score = human_ai_model.decision_function([text])[0]
    label = human_ai_model.predict([text])[0]
    
    return {
        "label": label,
        "confidence": round(abs(score), 3),
        "raw_score": round(score, 3)
    }


def full_classify(text):
    text = text.strip()
    word_count = len(text.split())
    
    # -------- Guardrails --------
    if word_count < CONFIG["min_words"]:
        return {
            "final_label": "uncertain",
            "reason": f"Text too short ({word_count} words)"
        }
    
    # -------- Stage 1: Human vs AI --------
    hvai = human_ai_decision(text)
    
    if hvai["label"] == "human" and hvai["confidence"] >= CONFIG["human_confidence_threshold"]:
        return {
            "final_label": "human",
            "details": hvai
        }
    
    # -------- Stage 2: Attribution --------
    attrib = safe_attribution(text)
    
    if attrib["predicted_model"] == "human_story":
        return {
            "final_label": "human",
            "details": attrib
        }
    
    if attrib["confidence_gap"] < CONFIG["attrib_confidence_gap"]:
        return {
            "final_label": "ai_uncertain",
            "details": attrib
        }
    
    return {
        "final_label": "ai",
        "predicted_model": attrib["predicted_model"],
        "details": attrib
    }

# -----------------------------
# Gradio Interface
# -----------------------------

# -----------------------------
# Gradio UI (Improved)
# -----------------------------

with gr.Blocks() as demo:
    gr.Markdown(
        """
        # AI Text Attribution Demo

        This tool analyzes a piece of text to determine:

        **1.** Whether it is likely **human-written or AI-generated**  
        **2.** If AI-generated, which **language model style it most closely resembles**

        Results are **probabilistic**. Some outputs are marked **uncertain** to avoid overclaiming.
        """
    )

    with gr.Row():
        with gr.Column(scale=2):
            text_input = gr.Textbox(
                lines=12,
                label="Input Text",
                placeholder="Paste text here (minimum ~30 words recommended)..."
            )
            analyze_btn = gr.Button("Analyze Text")

        with gr.Column(scale=1):
            headline = gr.Markdown("### Result will appear here")
            explanation = gr.Markdown("")

    gr.Markdown("### πŸ” Detailed Model Output")
    raw_output = gr.JSON(label="Raw Output")

    def ui_wrapper(text):
        result = full_classify(text)

        # ---------- Headline & explanation ----------
        if result["final_label"] == "human":
            headline_md = "## βœ… Likely Human-Written"
            explanation_md = (
                "The text shows high stylistic variability and does not strongly match "
                "known AI generation patterns."
            )

        elif result["final_label"] == "ai":
            model = result["predicted_model"]
            gap = result["details"]["confidence_gap"]

            headline_md = "## πŸ€– Likely AI-Generated"
            explanation_md = (
                f"The text most closely resembles **{model}**.\n\n"
                f"Confidence gap: **{gap}** β€” higher values indicate stronger attribution confidence."
            )

        elif result["final_label"] == "ai_uncertain":
            headline_md = "## ⚠️ AI-Generated (Uncertain Attribution)"
            explanation_md = (
                "The text appears AI-generated, but its style is similar to multiple models.\n\n"
                "A confident attribution cannot be made."
            )

        else:
            headline_md = "## ❓ Uncertain"
            explanation_md = result.get(
                "reason",
                "The system could not make a reliable determination."
            )

        return headline_md, explanation_md, result

    analyze_btn.click(
        fn=ui_wrapper,
        inputs=text_input,
        outputs=[headline, explanation, raw_output]
    )


if __name__ == "__main__":
    demo.launch()