anonymous12321 commited on
Commit
576c1ee
·
verified ·
1 Parent(s): 50b5617

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +173 -210
app.py CHANGED
@@ -1,228 +1,191 @@
1
  #!/usr/bin/env python3
2
  # -*- coding: utf-8 -*-
3
  """
4
- Gradio App - Linguistic Intelligence Studio (Dark Mode + Elegant Layout)
 
5
  """
 
6
  import gradio as gr
7
- import numpy as np
8
- import joblib
9
- import re
10
- from pathlib import Path
11
- from sklearn.feature_extraction.text import TfidfVectorizer
12
- from sklearn.preprocessing import MultiLabelBinarizer
13
- from scipy.sparse import hstack, csr_matrix
14
-
15
- try:
16
- import torch
17
- from transformers import AutoTokenizer, AutoModel
18
- TORCH_AVAILABLE = True
19
- except ImportError:
20
- TORCH_AVAILABLE = False
21
-
22
-
23
- # ---------------- MODEL ----------------
24
- class PortugueseClassifier:
25
- def __init__(self):
26
- self.model_path = Path("models")
27
- self.labels = None
28
- self.models_loaded = False
29
- self.tfidf_vectorizer = None
30
- self.meta_learner = None
31
- self.mlb = None
32
- self.optimal_thresholds = None
33
- self.trained_base_models = None
34
-
35
- if TORCH_AVAILABLE:
36
- self.bert_tokenizer = None
37
- self.bert_model = None
38
- self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
39
-
40
- self.load_models()
41
-
42
- def load_models(self):
43
- try:
44
- mlb_path = self.model_path / "int_stacking_mlb_encoder.joblib"
45
- tfidf_path = self.model_path / "int_stacking_tfidf_vectorizer.joblib"
46
- meta_path = self.model_path / "int_stacking_meta_learner.joblib"
47
- thresh_path = self.model_path / "int_stacking_optimal_thresholds.npy"
48
- base_path = self.model_path / "int_stacking_base_models.joblib"
49
-
50
- self.mlb = joblib.load(mlb_path)
51
- self.labels = self.mlb.classes_.tolist()
52
- self.tfidf_vectorizer = joblib.load(tfidf_path)
53
- self.meta_learner = joblib.load(meta_path)
54
- self.optimal_thresholds = np.load(thresh_path)
55
- self.trained_base_models = joblib.load(base_path)
56
-
57
- if TORCH_AVAILABLE:
58
- self.bert_tokenizer = AutoTokenizer.from_pretrained('neuralmind/bert-base-portuguese-cased')
59
- self.bert_model = AutoModel.from_pretrained('neuralmind/bert-base-portuguese-cased')
60
- self.bert_model.eval().to(self.device)
61
-
62
- self.models_loaded = True
63
- except Exception as e:
64
- print(f"❌ Error loading models: {str(e)}")
65
-
66
- def extract_bert_features(self, text):
67
- if not TORCH_AVAILABLE or not self.bert_model:
68
- return np.zeros((1, 768))
69
- try:
70
- inputs = self.bert_tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=512)
71
- inputs = {k: v.to(self.device) for k, v in inputs.items()}
72
- with torch.no_grad():
73
- outputs = self.bert_model(**inputs)
74
- return outputs.last_hidden_state[:, 0, :].cpu().numpy()
75
- except Exception:
76
- return np.zeros((1, 768))
77
-
78
- def predict(self, text):
79
- if not self.models_loaded:
80
- return [{"label": "Error", "probability": 0.0, "confidence": "low"}]
81
-
82
- text = re.sub(r'\s+', ' ', text.strip())
83
- if not text:
84
- return [{"label": "Empty text", "probability": 0.0, "confidence": "low"}]
85
-
86
- tfidf_features = self.tfidf_vectorizer.transform([text])
87
- bert_features = self.extract_bert_features(text)
88
- combined_features = hstack([tfidf_features, csr_matrix(bert_features)])
89
-
90
- base_predictions = np.zeros((1, len(self.labels), 12))
91
- model_idx = 0
92
- feature_sets = [("TF-IDF", tfidf_features), ("BERT", csr_matrix(bert_features)), ("TF-IDF+BERT", combined_features)]
93
-
94
- for feat_name, X_feat in feature_sets:
95
- for algo_name in ["LogReg_C1", "LogReg_C05", "GradBoost", "RandomForest"]:
96
- try:
97
- model_key = f"{feat_name}_{algo_name}"
98
- if model_key in self.trained_base_models:
99
- model = self.trained_base_models[model_key]
100
- pred = model.predict_proba(X_feat)
101
- base_predictions[0, :, model_idx] = pred[0]
102
- else:
103
- base_predictions[0, :, model_idx] = np.random.rand(len(self.labels)) * 0.3
104
- except Exception:
105
- base_predictions[0, :, model_idx] = np.random.rand(len(self.labels)) * 0.2
106
- model_idx += 1
107
-
108
- meta_features = base_predictions.reshape(1, -1)
109
- meta_pred = self.meta_learner.predict_proba(meta_features)[0]
110
- simple_ensemble = np.mean(base_predictions, axis=2)
111
- final_pred = 0.7 * meta_pred + 0.3 * simple_ensemble[0]
112
-
113
- predicted_labels = []
114
- for i, (prob, threshold) in enumerate(zip(final_pred, self.optimal_thresholds)):
115
- if prob > threshold:
116
- confidence = "high" if prob > 0.7 else "medium" if prob > 0.4 else "low"
117
- predicted_labels.append({"label": self.labels[i], "probability": float(prob), "confidence": confidence})
118
-
119
- if not predicted_labels:
120
- max_idx = np.argmax(final_pred)
121
- prob = final_pred[max_idx]
122
- confidence = "high" if prob > 0.7 else "medium" if prob > 0.4 else "low"
123
- predicted_labels.append({"label": self.labels[max_idx], "probability": float(prob), "confidence": confidence})
124
-
125
- predicted_labels.sort(key=lambda x: x["probability"], reverse=True)
126
- return predicted_labels
127
-
128
-
129
- # ---------------- GRADIO UI ----------------
130
- classifier = PortugueseClassifier()
131
-
132
- examples = [
133
- "O Sr. Presidente deu conhecimento do relatório da ROC em relação à Prestação de contas de 2023.",
134
- "Continuou por informar que no dia 19 o executivo participou na apresentação do Festival Soil to Soul, em Lisboa.",
135
- "Pelo Sr. Presidente foram presentes a reunião as informações da contabilidade que se anexam à presente ata.",
136
- "Não houve alteração orçamental permutativa para apresentar.",
137
- "O Executivo Municipal deliberou aprovar a ata n.º 9, de 10.04.2024, por unanimidade.",
138
- "Foi presente o pedido de apoio para obras do Centro Social e Paroquial de Alandroal.",
139
- "O Executivo deliberou aprovar o abate de equipamento informático obsoleto.",
140
- "Foi aprovada a atribuição de subsídios de nascimento pela Secção de Serviço Social.",
141
- "Foi aprovada a atribuição de um apoio à fixação de residência em habitação própria.",
142
- "Foram ratificadas as alterações orçamentais permutativas apresentadas pelo Presidente."
143
  ]
144
 
 
 
 
 
 
 
145
 
146
- def classify_text(text):
147
- preds = classifier.predict(text)
148
- cards = ""
149
- for p in preds[:10]:
150
- prob = p["probability"]
151
- label = p["label"]
152
- conf = p["confidence"]
153
- color = {"high": "#2ecc71", "medium": "#f1c40f", "low": "#e74c3c"}[conf]
154
- emoji = {"high": "🟢", "medium": "🟡", "low": "🔴"}[conf]
155
-
156
- cards += f"""
157
- <div style="
158
- border-left: 5px solid {color};
159
- background-color:#181818;
160
- padding:12px;
161
- margin-bottom:10px;
162
- border-radius:8px;
163
- box-shadow: 0 0 8px rgba(0,0,0,0.25);
164
- ">
165
- <strong style="color:#fff">{label}</strong> {emoji}<br>
166
- <small style="color:#bbb">Probability: {prob:.1%}</small>
167
- </div>
168
- """
169
- return cards
170
-
171
-
172
- css = """
173
- body, .gradio-container {
174
- background-color: #121212;
175
- color: #f5f5f5;
176
- font-family: 'Inter', 'Segoe UI', sans-serif;
177
  }
178
- h1 {
179
- color: #5DADE2;
180
- text-align: center;
181
- font-weight: 600;
182
- margin-top: 10px;
183
- letter-spacing: 0.5px;
184
  }
 
185
  textarea {
186
- background-color: #1E1E1E;
187
- color: #f5f5f5;
188
- border: 1px solid #333;
189
- border-radius: 8px;
190
  }
 
191
  button {
192
- background-color: #5DADE2 !important;
193
- color: white !important;
194
- border-radius: 8px !important;
195
- font-weight: 500;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  }
197
- .card-btn {
198
- width: 100%;
199
- background-color: #1E1E1E;
200
- color: white;
201
- border: 1px solid #333;
202
- border-radius: 8px;
203
- margin-bottom: 8px;
204
- text-align: left;
205
- padding: 10px;
 
 
 
206
  }
207
- .card-btn:hover {
208
- background-color: #2E2E2E;
 
 
 
 
 
 
 
 
 
 
 
209
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  """
211
 
212
- with gr.Blocks(css=css) as demo:
213
- gr.HTML("<h1>🧩 Linguistic Intelligence Studio</h1>")
214
- with gr.Row():
215
- with gr.Column(scale=6):
216
- text_input = gr.Textbox(label="Enter Portuguese administrative text",
217
- lines=10,
218
- placeholder="Introduza o texto em português aqui...")
219
- classify_btn = gr.Button("🔍 Classify")
220
- gr.HTML("<h3 style='color:white; margin-top:20px;'>Example texts (click to insert):</h3>")
221
- for ex in examples:
222
- btn = gr.Button(ex, elem_classes=["card-btn"])
223
- btn.click(lambda x=ex: x, None, text_input)
224
- with gr.Column(scale=4):
225
- output = gr.HTML(label="Predicted Categories")
226
- classify_btn.click(classify_text, inputs=text_input, outputs=output)
227
-
228
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  #!/usr/bin/env python3
2
  # -*- coding: utf-8 -*-
3
  """
4
+ 🪶 Council Matters Classifier PT
5
+ Modern animated Gradio interface for Portuguese administrative document classification.
6
  """
7
+
8
  import gradio as gr
9
+
10
+ # --- Dummy classifier (replace with your real model) ---
11
+ def classify_text(text):
12
+ fake_labels = ["Urbanism", "Finance", "Culture", "Environment", "Education"]
13
+ return [label for i, label in enumerate(fake_labels) if hash(text + str(i)) % 2 == 0]
14
+
15
+
16
+ # --- Suggestions (10 examples) ---
17
+ suggestions = [
18
+ "A Câmara Municipal aprovou o novo orçamento para 2025, com foco em sustentabilidade.",
19
+ "Foi decidido avançar com o projeto de requalificação do centro histórico.",
20
+ "A escola básica local vai receber novos equipamentos de informática.",
21
+ "O evento cultural contará com artistas locais e financiamento europeu.",
22
+ "A autarquia investirá na melhoria do transporte público intermunicipal.",
23
+ "Será criada uma nova taxa ambiental para reduzir a poluição urbana.",
24
+ "Os serviços municipais vão digitalizar os arquivos antigos.",
25
+ "Foi aprovada a cedência de terreno para construção de habitação social.",
26
+ "A Câmara apoiará as associações juvenis em atividades culturais.",
27
+ "O plano estratégico inclui medidas para atrair investimento privado.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  ]
29
 
30
+ # --- CSS Styling (black + motion) ---
31
+ custom_css = """
32
+ body {
33
+ background-color: #0c0c0c;
34
+ font-family: 'Inter', sans-serif;
35
+ }
36
 
37
+ .gradio-container {
38
+ background-color: #0c0c0c;
39
+ color: #f1f1f1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
+
42
+ h2, h3 {
43
+ text-align: center;
44
+ color: #00b4ff;
45
+ font-weight: 600;
 
46
  }
47
+
48
  textarea {
49
+ background-color: #181818 !important;
50
+ color: #fff !important;
51
+ border-radius: 12px !important;
52
+ border: 1px solid #333 !important;
53
  }
54
+
55
  button {
56
+ background: linear-gradient(90deg, #007aff, #00c3ff);
57
+ color: white !important;
58
+ font-weight: 600 !important;
59
+ border-radius: 10px !important;
60
+ border: none !important;
61
+ transition: 0.3s;
62
+ }
63
+
64
+ button:hover {
65
+ opacity: 0.9;
66
+ transform: scale(1.04);
67
+ }
68
+
69
+ .output-class {
70
+ display: flex;
71
+ flex-wrap: wrap;
72
+ gap: 10px;
73
+ justify-content: center;
74
+ margin-top: 10px;
75
+ animation: fadeIn 0.8s ease-in-out;
76
+ }
77
+
78
+ .output-chip {
79
+ background-color: #1a1a1a;
80
+ color: #00c3ff;
81
+ padding: 6px 14px;
82
+ border-radius: 10px;
83
+ font-weight: 500;
84
+ border: 1px solid #007aff33;
85
+ transition: 0.3s;
86
+ }
87
+
88
+ .output-chip:hover {
89
+ background-color: #007aff33;
90
+ transform: scale(1.05);
91
  }
92
+
93
+ .suggestion-box {
94
+ background-color: #111;
95
+ border-radius: 12px;
96
+ border: 1px solid #1f1f1f;
97
+ padding: 12px;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: space-between;
101
+ color: #aaa;
102
+ margin-top: 25px;
103
+ animation: slideUp 0.7s ease-in-out;
104
  }
105
+
106
+ .arrow-btn {
107
+ background: none;
108
+ border: none;
109
+ color: #00c3ff;
110
+ font-size: 22px;
111
+ cursor: pointer;
112
+ transition: 0.3s;
113
+ }
114
+
115
+ .arrow-btn:hover {
116
+ color: #00e0ff;
117
+ transform: scale(1.2);
118
  }
119
+
120
+ @keyframes fadeIn {
121
+ from { opacity: 0; transform: scale(0.96); }
122
+ to { opacity: 1; transform: scale(1); }
123
+ }
124
+
125
+ @keyframes slideUp {
126
+ from { opacity: 0; transform: translateY(10px); }
127
+ to { opacity: 1; transform: translateY(0); }
128
+ }
129
+ """
130
+
131
+ # --- JS Navigation logic for suggestions ---
132
+ custom_js = f"""
133
+ let examples = {suggestions};
134
+ let index = 0;
135
+
136
+ function updateSuggestion(direction) {{
137
+ const el = document.getElementById('suggestion-text');
138
+ el.style.opacity = 0;
139
+ setTimeout(() => {{
140
+ if (direction === 'next') {{
141
+ index = (index + 1) % examples.length;
142
+ }} else {{
143
+ index = (index - 1 + examples.length) % examples.length;
144
+ }}
145
+ el.innerText = examples[index];
146
+ document.getElementById('input-text').value = examples[index];
147
+ el.style.opacity = 1;
148
+ }}, 200);
149
+ }}
150
  """
151
 
152
+ # --- UI Layout ---
153
+ with gr.Blocks(css=custom_css, js=custom_js, theme="gradio/soft") as demo:
154
+ gr.Markdown("## 🏛️ **Council Matters Classifier – PT**")
155
+ gr.Markdown("### Enter Portuguese administrative text below:")
156
+
157
+ input_text = gr.Textbox(
158
+ label="",
159
+ placeholder="Escreva aqui o texto em português...",
160
+ lines=6,
161
+ elem_id="input-text"
162
+ )
163
+
164
+ classify_btn = gr.Button("Classify")
165
+
166
+ output = gr.HTML(label="Predicted Topics")
167
+
168
+ def classify_display(text):
169
+ labels = classify_text(text)
170
+ if not labels:
171
+ return "<div class='output-class'><span style='color:#777;'>No topics detected.</span></div>"
172
+ chips = "".join([f"<span class='output-chip'>{lbl}</span>" for lbl in labels])
173
+ return f"<div class='output-class'>{chips}</div>"
174
+
175
+ classify_btn.click(fn=classify_display, inputs=input_text, outputs=output)
176
+
177
+ # Suggestion carousel
178
+ gr.Markdown("### 💡 Suggestions")
179
+ gr.HTML("""
180
+ <div class='suggestion-box'>
181
+ <button class='arrow-btn' onclick="updateSuggestion('prev')">⟨</button>
182
+ <span id='suggestion-text' style='flex: 1; text-align: center; padding: 0 10px; transition: opacity 0.3s;'>
183
+ A Câmara Municipal aprovou o novo orçamento para 2025, com foco em sustentabilidade.
184
+ </span>
185
+ <button class='arrow-btn' onclick="updateSuggestion('next')">⟩</button>
186
+ </div>
187
+ """)
188
+
189
+ # --- Launch ---
190
+ if __name__ == "__main__":
191
+ demo.launch()