""" ๐Ÿ”ฌ NLP Insight Engine โ€” Text Analytics Dashboard Sentiment ยท NER ยท Keywords ยท Summarisation ยท Language Detection ยท Text Comparison Built with HuggingFace Transformers + Gradio """ import gradio as gr import json from utils.pipeline import NLPPipeline from utils.helpers import EXAMPLE_TEXTS nlp = NLPPipeline() ENT_CSS = { "PER": ("#dbeafe", "#1e40af"), "ORG": ("#dcfce7", "#166534"), "LOC": ("#fef3c7", "#92400e"), "MISC": ("#f3e8ff", "#6b21a8"), } LANG_FLAGS = { "English": "๐Ÿ‡ฌ๐Ÿ‡ง", "Spanish": "๐Ÿ‡ช๐Ÿ‡ธ", "French": "๐Ÿ‡ซ๐Ÿ‡ท", "German": "๐Ÿ‡ฉ๐Ÿ‡ช", "Italian": "๐Ÿ‡ฎ๐Ÿ‡น", "Portuguese": "๐Ÿ‡ต๐Ÿ‡น", "Dutch": "๐Ÿ‡ณ๐Ÿ‡ฑ", "Turkish": "๐Ÿ‡น๐Ÿ‡ท", "Arabic": "๐Ÿ‡ธ๐Ÿ‡ฆ", "Urdu": "๐Ÿ‡ต๐Ÿ‡ฐ", "Chinese": "๐Ÿ‡จ๐Ÿ‡ณ", "Japanese": "๐Ÿ‡ฏ๐Ÿ‡ต", "Korean": "๐Ÿ‡ฐ๐Ÿ‡ท", "Cyrillic": "๐Ÿ‡ท๐Ÿ‡บ", "Devanagari": "๐Ÿ‡ฎ๐Ÿ‡ณ", } def make_warning_banner(lang): """Generate a warning banner for non-English text.""" return ( f'
' f'โš ๏ธ' f'' f'Non-English text detected ({lang}). ' f'Sentiment and NER results may be inaccurate as the models are trained on English text.' f'
' ) # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # SINGLE TEXT ANALYSIS # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• def analyse(text, sentiment_mode, do_sentiment, do_ner, do_keywords, do_summary): if not text or len(text.strip()) < 20: msg = "

Please enter at least 20 characters of text.

" return msg, msg, msg, msg, msg, msg, "" text = text.strip() word_count = len(text.split()) sent_count = max(1, text.count(".") + text.count("!") + text.count("?")) mode = "financial" if "Financial" in sentiment_mode else "general" sent_result = None entities = None kw = None summary_text = None # โ”€โ”€ Language Detection (always on) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ lang_result = nlp.detect_language(text) lang = lang_result["language"] lang_conf = lang_result["confidence"] lang_script = lang_result["script"] flag = LANG_FLAGS.get(lang, "๐ŸŒ") is_non_english = lang != "English" and lang != "Unknown" lang_html = ( f'
' f'
{flag}
' f'
{lang}
' f'
Confidence: {lang_conf:.1%}
' f'
Script: {lang_script}
' f'
' ) warning = make_warning_banner(lang) if is_non_english else "" # โ”€โ”€ Sentiment โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if do_sentiment: sent_result = nlp.analyse_sentiment(text, mode=mode) label = sent_result["label"] score = sent_result["score"] emoji = {"POSITIVE": "๐Ÿ˜Š", "NEGATIVE": "๐Ÿ˜Ÿ", "NEUTRAL": "๐Ÿ˜"}.get(label, "๐Ÿค”") if mode == "financial": badge = ('๐Ÿ“Š FinBERT โ€” trained on financial text') else: badge = ('๐Ÿ’ฌ DistilBERT โ€” trained on movie reviews') bars_html = "" if "details" in sent_result: for d in sent_result["details"]: lbl = d["label"].title() s = d["score"] color = {"Positive": "#22c55e", "Negative": "#ef4444", "Neutral": "#64748b"}.get(lbl, "#64748b") width = max(4, int(s * 100)) bars_html += ( f'
' f'{lbl}' f'
' f'
' f'{s:.1%}
' ) sentiment_html = ( f'{warning}' f'
' f'
{emoji} {label.title()}
' f'
Confidence: {score:.1%}
' f'{badge}
{bars_html}' ) else: sentiment_html = "

Sentiment analysis disabled.

" # โ”€โ”€ NER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if do_ner: entities = nlp.extract_entities(text) ner_warning = warning if is_non_english else "" if entities: tags = "" for ent in entities: bg, fg = ENT_CSS.get(ent["entity_group"], ("#f1f5f9", "#334155")) tags += (f'{ent["word"]} ({ent["entity_group"]}) ') rows = "".join( f'' f'{e["word"]}' f'{e["entity_group"]}' f'{e["score"]:.2%}' for e in entities ) table = (f'' f'' f'' f'' f'' f'{rows}
EntityTypeConfidence
') ner_html = f'{ner_warning}
{tags}
{table}' else: ner_html = f'{ner_warning}

No named entities detected.

' else: ner_html = "

NER disabled.

" # โ”€โ”€ Keywords โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if do_keywords: kw = nlp.extract_keywords(text) if kw: max_score = kw[0][1] bars = "" for word, score in kw[:12]: width = max(6, int((score / max_score) * 100)) bars += (f'
' f'{word}' f'' f'{score:.4f}
') keywords_html = bars else: keywords_html = "

No keywords extracted.

" else: keywords_html = "

Keyword extraction disabled.

" # โ”€โ”€ Summary โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if do_summary: summary_text = nlp.summarise(text) summary_len = len(summary_text.split()) compression = (1 - summary_len / word_count) * 100 if word_count else 0 summary_html = ( f'
{summary_text}
' f'
' f'{word_count} โ†’ {summary_len} words ({compression:.0f}% reduction)
' ) else: summary_html = "

Summarisation disabled.

" # โ”€โ”€ Metrics โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ sent_label = sent_result["label"].title() if sent_result else "โ€”" sent_emoji = {"Positive": "๐Ÿ˜Š", "Negative": "๐Ÿ˜Ÿ", "Neutral": "๐Ÿ˜"}.get(sent_label, "โ€”") ent_count = len(entities) if entities else 0 ms = ("background:linear-gradient(145deg,#f8fafc,#f1f5f9);border:1px solid #e2e8f0;" "border-radius:12px;padding:1rem;text-align:center;") vs = "font-family:monospace;font-size:1.6rem;font-weight:700;color:#0f172a;" ls = "font-size:0.78rem;color:#64748b;text-transform:uppercase;" metrics_html = ( f'
' f'
{word_count:,}
Words
' f'
{sent_count}
Sentences
' f'
{sent_emoji} {sent_label}
Sentiment
' f'
{ent_count}
Entities
' f'
{flag} {lang}
Language
' f'
' ) # โ”€โ”€ JSON export โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ export = {"word_count": word_count, "sentence_count": sent_count, "language": lang_result} if sent_result: export["sentiment"] = sent_result if entities: export["entities"] = entities if kw: export["keywords"] = kw if summary_text: export["summary"] = summary_text json_str = json.dumps(export, indent=2, default=str) return metrics_html, sentiment_html, ner_html, keywords_html, summary_html, lang_html, json_str # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # TEXT COMPARISON # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• def compare(text_a, text_b, sentiment_mode): if not text_a or len(text_a.strip()) < 20: return "

Text A needs at least 20 characters.

" if not text_b or len(text_b.strip()) < 20: return "

Text B needs at least 20 characters.

" mode = "financial" if "Financial" in sentiment_mode else "general" result = nlp.compare_texts(text_a.strip(), text_b.strip(), mode=mode) a = result["text_a"] b = result["text_b"] shared = result["shared_keywords"] a_label = a["sentiment"]["label"].title() b_label = b["sentiment"]["label"].title() a_score = a["sentiment"]["score"] b_score = b["sentiment"]["score"] a_emoji = {"Positive": "๐Ÿ˜Š", "Negative": "๐Ÿ˜Ÿ", "Neutral": "๐Ÿ˜"}.get(a_label, "๐Ÿค”") b_emoji = {"Positive": "๐Ÿ˜Š", "Negative": "๐Ÿ˜Ÿ", "Neutral": "๐Ÿ˜"}.get(b_label, "๐Ÿค”") a_lang = a["language"]["language"] b_lang = b["language"]["language"] a_flag = LANG_FLAGS.get(a_lang, "๐ŸŒ") b_flag = LANG_FLAGS.get(b_lang, "๐ŸŒ") a_ents = len(a["entities"]) b_ents = len(b["entities"]) a_ent_words = {e["word"].lower() for e in a["entities"]} b_ent_words = {e["word"].lower() for e in b["entities"]} shared_ents = a_ent_words & b_ent_words # Non-English warnings for comparison comp_warnings = "" non_eng = [] if a_lang != "English" and a_lang != "Unknown": non_eng.append(f"Text A ({a_lang})") if b_lang != "English" and b_lang != "Unknown": non_eng.append(f"Text B ({b_lang})") if non_eng: comp_warnings = ( f'
' f'โš ๏ธ' f'' f'Non-English text detected in {" and ".join(non_eng)}. ' f'Sentiment and NER results may be inaccurate as the models are trained on English text.' f'
' ) cs = ("background:linear-gradient(145deg,#f8fafc,#f1f5f9);border:1px solid #e2e8f0;" "border-radius:12px;padding:1rem;text-align:center;") hdr = "font-size:0.72rem;color:#94a3b8;text-transform:uppercase;letter-spacing:0.05em;margin-bottom:0.5rem;" html = f""" {comp_warnings}
Text A
{a_emoji} {a_label}
Confidence: {a_score:.1%}
{a_flag} {a_lang}
{a["word_count"]}
Words
{a_ents}
Entities
VS
Text B
{b_emoji} {b_label}
Confidence: {b_score:.1%}
{b_flag} {b_lang}
{b["word_count"]}
Words
{b_ents}
Entities
๐Ÿ”— Shared Keywords ({len(shared)})
{"".join(f'{kw}' for kw in shared) if shared else 'No shared keywords found'}
๐Ÿท๏ธ Shared Entities ({len(shared_ents)})
{"".join(f'{e}' for e in shared_ents) if shared_ents else 'No shared entities found'}
""" return html # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• # GRADIO UI # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• with gr.Blocks(title="NLP Insight Engine") as demo: gr.Markdown("# ๐Ÿ”ฌ NLP Insight Engine") gr.Markdown("*Sentiment ยท Entities ยท Keywords ยท Summarisation ยท Language Detection ยท Text Comparison*") with gr.Tabs(): with gr.Tab("๐Ÿ“ Analyse Text"): with gr.Row(): with gr.Column(scale=3): text_input = gr.Textbox( label="Enter text to analyse", placeholder="Paste an article, review, report, or any text hereโ€ฆ", lines=8, max_lines=20, ) examples = gr.Examples( examples=[[v] for v in EXAMPLE_TEXTS.values()], inputs=[text_input], label="Try an example", ) with gr.Column(scale=1): gr.Markdown("### โš™๏ธ Options") sentiment_mode = gr.Radio( choices=["๐Ÿ’ฌ General (movie reviews)", "๐Ÿ“Š Financial (FinBERT)"], value="๐Ÿ’ฌ General (movie reviews)", label="Sentiment Model", ) do_sentiment = gr.Checkbox(label="Sentiment Analysis", value=True) do_ner = gr.Checkbox(label="Named Entity Recognition", value=True) do_keywords = gr.Checkbox(label="Keyword Extraction", value=True) do_summary = gr.Checkbox(label="Extractive Summary", value=True) analyse_btn = gr.Button("๐Ÿš€ Analyse", variant="primary", size="lg") metrics_output = gr.HTML(label="Quick Metrics") with gr.Tabs(): with gr.Tab("๐Ÿ’ฌ Sentiment"): sentiment_output = gr.HTML() with gr.Tab("๐Ÿท๏ธ Entities"): ner_output = gr.HTML() with gr.Tab("๐Ÿ”‘ Keywords"): keywords_output = gr.HTML() with gr.Tab("๐Ÿ“ Summary"): summary_output = gr.HTML() with gr.Tab("๐ŸŒ Language"): lang_output = gr.HTML() with gr.Tab("๐Ÿ“ฅ Export JSON"): json_output = gr.Textbox(label="Analysis Results (JSON)", lines=15) analyse_btn.click( fn=analyse, inputs=[text_input, sentiment_mode, do_sentiment, do_ner, do_keywords, do_summary], outputs=[metrics_output, sentiment_output, ner_output, keywords_output, summary_output, lang_output, json_output], ) with gr.Tab("โš–๏ธ Compare Texts"): gr.Markdown("### Compare two texts side-by-side") gr.Markdown("*Analyse sentiment, entities, keywords, and language across two texts simultaneously.*") with gr.Row(): text_a = gr.Textbox(label="Text A", placeholder="Paste first text hereโ€ฆ", lines=6) text_b = gr.Textbox(label="Text B", placeholder="Paste second text hereโ€ฆ", lines=6) compare_mode = gr.Radio( choices=["๐Ÿ’ฌ General (movie reviews)", "๐Ÿ“Š Financial (FinBERT)"], value="๐Ÿ’ฌ General (movie reviews)", label="Sentiment Model", ) compare_btn = gr.Button("โš–๏ธ Compare", variant="primary", size="lg") compare_output = gr.HTML(label="Comparison Results") compare_btn.click( fn=compare, inputs=[text_a, text_b, compare_mode], outputs=[compare_output], ) gr.Markdown("**๐Ÿ’ก Try comparing:** A positive product review vs a negative one, " "or a bullish financial article vs a bearish one.") gr.Markdown( "
" "NLP Insight Engine ยท Built by Wasif ยท " "Models: distilbert-sst2 ยท ProsusAI/finbert ยท dslim/bert-base-NER ยท " "100% free, no API keys" "
" ) if __name__ == "__main__": demo.launch(ssr_mode=False)