aidn commited on
Commit
dda28e6
Β·
verified Β·
1 Parent(s): d10aadb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -27
app.py CHANGED
@@ -1,11 +1,13 @@
1
  import os
2
  import json
 
3
  import gradio as gr
4
  from huggingface_hub import InferenceClient
5
 
6
  # ── Konfiguration ──────────────────────────────────────────────────────────────
7
- HF_TOKEN = os.environ.get("HF_TOKEN", "")
8
- MODEL_ID = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
 
9
 
10
  # ── System Prompts ─────────────────────────────────────────────────────────────
11
 
@@ -65,6 +67,100 @@ Rules:
65
  - ALL string values must be valid JSON strings. Plain ASCII only."""
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  # ── LLM-Calls ─────────────────────────────────────────────────────────────────
69
 
70
  def _call_llm(system, user, max_tokens=1024):
@@ -91,13 +187,10 @@ def translate(text, direction):
91
 
92
 
93
  def _extract_json(raw: str) -> dict:
94
- """Mehrere Strategien um JSON aus LLM-Antwort zu extrahieren."""
95
- # 1. Direkt parsen
96
  try:
97
  return json.loads(raw)
98
  except Exception:
99
  pass
100
- # 2. JSON-Block per Index extrahieren
101
  try:
102
  start = raw.find("{")
103
  end = raw.rfind("}") + 1
@@ -105,7 +198,6 @@ def _extract_json(raw: str) -> dict:
105
  return json.loads(raw[start:end])
106
  except Exception:
107
  pass
108
- # 3. Whitespace bereinigen und nochmal versuchen
109
  try:
110
  cleaned = raw[raw.find("{"):raw.rfind("}")+1]
111
  cleaned = " ".join(cleaned.split())
@@ -115,18 +207,29 @@ def _extract_json(raw: str) -> dict:
115
  raise ValueError("Kein valides JSON gefunden")
116
 
117
 
118
- def get_bingo(text: str) -> str:
 
119
  if not text.strip() or not HF_TOKEN:
120
- return ""
121
  last_err = None
122
  for attempt in range(3):
123
  try:
124
  raw = _call_llm(PROMPT_BINGO, text, max_tokens=600)
125
  data = _extract_json(raw)
126
- return _render_bingo(data)
 
 
 
 
 
 
 
 
 
127
  except Exception as e:
128
  last_err = e
129
- return f"<p style='color:#c00;font-size:.85rem;padding:8px;'>Analyse fehlgeschlagen nach 3 Versuchen: {last_err}</p>"
 
130
 
131
 
132
  def _render_bingo(data):
@@ -150,7 +253,7 @@ def _render_bingo(data):
150
  for m in metrics:
151
  score = int(m.get("score", 0))
152
  label = m.get("label", m.get("icon", ""))
153
- icon = ICONS.get(label, m.get("icon", ""))
154
  color = bar_color(score)
155
  pct = score * 10
156
  rows += f"""
@@ -168,13 +271,11 @@ def _render_bingo(data):
168
  total = sum(int(m.get("score", 0)) for m in metrics)
169
  max_score = len(metrics) * 10
170
  total_pct = round(total / max_score * 100) if max_score else 0
171
-
172
  badge_color = "#C0392B" if total_pct >= 70 else "#E67E22" if total_pct >= 40 else "#27AE60"
173
 
174
  return f"""
175
  <div style="background:#fff;border:1px solid #E0DFDC;border-radius:12px;
176
- padding:22px 26px;margin-top:4px;
177
- box-shadow:0 2px 12px rgba(0,0,0,.07);">
178
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:20px;">
179
  <span style="font-size:1.3rem;">🎯</span>
180
  <span style="font-weight:700;font-size:.95rem;color:#004182;
@@ -211,10 +312,16 @@ def swap_direction(current_dir, inp, out):
211
  def run_translate(text, direction):
212
  result = translate(text, direction)
213
  if direction == "to_linkedin":
214
- return result, gr.update(value=result, visible=True), gr.update(value="", visible=False)
 
 
 
215
  else:
216
- bingo_html = get_bingo(text)
217
- return result, gr.update(value="", visible=False), gr.update(value=bingo_html, visible=True)
 
 
 
218
 
219
 
220
  # ── CSS ───────────────────────────────────────────────────────────────────────
@@ -237,13 +344,8 @@ body, .gradio-container {
237
  }
238
  .li-header {
239
  background: linear-gradient(135deg, var(--li-blue-dark) 0%, var(--li-blue) 70%, var(--li-blue-mid) 100%);
240
- border-radius: 12px;
241
- padding: 24px 28px;
242
- margin-bottom: 20px;
243
- box-shadow: 0 4px 20px rgba(10,102,194,.3);
244
- display: flex;
245
- align-items: center;
246
- gap: 18px;
247
  }
248
  .li-header .icon { font-size: 2.6rem; line-height: 1; }
249
  .li-header h1 { margin: 0 !important; font-size: 1.65rem !important; font-weight: 700 !important; color: #fff !important; }
@@ -316,7 +418,7 @@ with gr.Blocks(title="LinkedIn Translator", css=CSS) as demo:
316
  )
317
  with gr.Column(scale=1, min_width=120):
318
  gr.HTML("<div style='height:40px'></div>")
319
- translate_btn = gr.Button("Übersetzen", variant="primary", size="lg")
320
  gr.HTML("<div style='height:12px'></div>")
321
  swap_btn = gr.Button("πŸ”„ β†’ LinkedIn", variant="secondary", size="sm")
322
  with gr.Column(scale=5):
@@ -327,14 +429,20 @@ with gr.Blocks(title="LinkedIn Translator", css=CSS) as demo:
327
  interactive=False,
328
  )
329
 
 
330
  with gr.Row():
331
  with gr.Column():
332
  markdown_out = gr.Markdown(
333
- value="*Noch kein Ergebnis – bitte zuerst ΓΌbersetzen.*",
334
  visible=True,
335
  )
336
  bingo_out = gr.HTML(value="", visible=False)
337
 
 
 
 
 
 
338
  if not HF_TOKEN:
339
  gr.HTML("""
340
  <div style="background:#FFF4CE;border:1px solid #F9C642;border-left:4px solid #F9C642;
@@ -349,6 +457,7 @@ with gr.Blocks(title="LinkedIn Translator", css=CSS) as demo:
349
  <span>🧠 Llama 4 Maverick 17B</span>
350
  <span>πŸ”„ Bidirektional</span>
351
  <span>🎯 Corporate Nonsense Score</span>
 
352
  </div>
353
  """)
354
 
@@ -357,7 +466,7 @@ with gr.Blocks(title="LinkedIn Translator", css=CSS) as demo:
357
  translate_btn.click(
358
  fn=run_translate,
359
  inputs=[input_box, direction_state],
360
- outputs=[output_box, markdown_out, bingo_out],
361
  )
362
 
363
  def do_swap(direction, inp, out):
 
1
  import os
2
  import json
3
+ import datetime
4
  import gradio as gr
5
  from huggingface_hub import InferenceClient
6
 
7
  # ── Konfiguration ──────────────────────────────────────────────────────────────
8
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
9
+ MODEL_ID = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
10
+ LEADERBOARD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "leaderboard.json")
11
 
12
  # ── System Prompts ─────────────────────────────────────────────────────────────
13
 
 
67
  - ALL string values must be valid JSON strings. Plain ASCII only."""
68
 
69
 
70
+ # ── Leaderboard ────────────────────────────────────────────────────────────────
71
+
72
+ def _load_lb() -> list:
73
+ try:
74
+ with open(LEADERBOARD_PATH, "r", encoding="utf-8") as f:
75
+ return json.load(f)
76
+ except Exception:
77
+ return []
78
+
79
+ def _save_lb(entries: list):
80
+ try:
81
+ with open(LEADERBOARD_PATH, "w", encoding="utf-8") as f:
82
+ json.dump(entries, f, ensure_ascii=False, indent=2)
83
+ except Exception:
84
+ pass
85
+
86
+ def _add_to_lb(post_text: str, total: int, max_score: int, verdict: str):
87
+ entries = _load_lb()
88
+ entries.append({
89
+ "text": post_text[:280].strip(), # preview, max 280 chars
90
+ "total": total,
91
+ "max": max_score,
92
+ "pct": round(total / max_score * 100) if max_score else 0,
93
+ "verdict": verdict,
94
+ "date": datetime.datetime.now().strftime("%d.%m.%Y"),
95
+ })
96
+ _save_lb(entries)
97
+
98
+ def _render_leaderboard() -> str:
99
+ entries = _load_lb()
100
+ if not entries:
101
+ return "<p style='color:#888;font-size:.85rem;text-align:center;padding:20px;'>Noch keine Eintraege. Analysiere einen LinkedIn-Post um zu starten.</p>"
102
+
103
+ sorted_asc = sorted(entries, key=lambda x: x["pct"])
104
+ sorted_desc = sorted(entries, key=lambda x: x["pct"], reverse=True)
105
+ best = sorted_asc[:5]
106
+ worst = sorted_desc[:5]
107
+
108
+ def entry_html(e, rank, is_worst):
109
+ pct = e["pct"]
110
+ color = "#C0392B" if pct >= 70 else "#E67E22" if pct >= 40 else "#27AE60"
111
+ medal = ["πŸ₯‡","πŸ₯ˆ","πŸ₯‰","4.","5."][rank]
112
+ text_preview = e["text"][:120] + ("…" if len(e["text"]) > 120 else "")
113
+ return f"""
114
+ <div style="padding:10px 12px;border:1px solid #E0DFDC;border-radius:8px;
115
+ margin-bottom:8px;background:#FAFAFA;">
116
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
117
+ <span style="font-size:.82rem;font-weight:700;color:#333;">{medal}</span>
118
+ <span style="font-size:.82rem;font-weight:700;color:{color};
119
+ background:{color}18;border-radius:99px;padding:2px 8px;">
120
+ {pct}% &nbsp;({e['total']}/{e['max']})
121
+ </span>
122
+ <span style="font-size:.72rem;color:#aaa;">{e.get('date','')}</span>
123
+ </div>
124
+ <div style="font-size:.78rem;color:#555;line-height:1.45;margin-bottom:4px;">{text_preview}</div>
125
+ <div style="font-size:.75rem;color:#888;font-style:italic;">"{e.get('verdict','')}"</div>
126
+ </div>"""
127
+
128
+ best_html = "".join(entry_html(e, i, False) for i, e in enumerate(best))
129
+ worst_html = "".join(entry_html(e, i, True) for i, e in enumerate(worst))
130
+
131
+ total_count = len(entries)
132
+ avg_pct = round(sum(e["pct"] for e in entries) / total_count) if entries else 0
133
+
134
+ return f"""
135
+ <div style="background:#fff;border:1px solid #E0DFDC;border-radius:12px;
136
+ padding:22px 26px;margin-top:4px;box-shadow:0 2px 12px rgba(0,0,0,.07);">
137
+ <div style="display:flex;align-items:center;gap:10px;margin-bottom:20px;">
138
+ <span style="font-size:1.3rem;">πŸ†</span>
139
+ <span style="font-weight:700;font-size:.95rem;color:#004182;
140
+ text-transform:uppercase;letter-spacing:.5px;">Leaderboard</span>
141
+ <span style="margin-left:auto;font-size:.78rem;color:#888;">
142
+ {total_count} Eintraege &nbsp;&middot;&nbsp; Ø {avg_pct}% Nonsense
143
+ </span>
144
+ </div>
145
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
146
+ <div>
147
+ <div style="font-size:.76rem;font-weight:700;text-transform:uppercase;
148
+ letter-spacing:.5px;color:#27AE60;margin-bottom:10px;">
149
+ βœ… Substanzreichste Posts (niedrigster Score)
150
+ </div>
151
+ {best_html}
152
+ </div>
153
+ <div>
154
+ <div style="font-size:.76rem;font-weight:700;text-transform:uppercase;
155
+ letter-spacing:.5px;color:#C0392B;margin-bottom:10px;">
156
+ πŸ’© Reinster LinkedIn-Nonsense (hoechster Score)
157
+ </div>
158
+ {worst_html}
159
+ </div>
160
+ </div>
161
+ </div>"""
162
+
163
+
164
  # ── LLM-Calls ─────────────────────────────────────────────────────────────────
165
 
166
  def _call_llm(system, user, max_tokens=1024):
 
187
 
188
 
189
  def _extract_json(raw: str) -> dict:
 
 
190
  try:
191
  return json.loads(raw)
192
  except Exception:
193
  pass
 
194
  try:
195
  start = raw.find("{")
196
  end = raw.rfind("}") + 1
 
198
  return json.loads(raw[start:end])
199
  except Exception:
200
  pass
 
201
  try:
202
  cleaned = raw[raw.find("{"):raw.rfind("}")+1]
203
  cleaned = " ".join(cleaned.split())
 
207
  raise ValueError("Kein valides JSON gefunden")
208
 
209
 
210
+ def get_bingo(text: str):
211
+ """Gibt (bingo_html, leaderboard_html) zurueck."""
212
  if not text.strip() or not HF_TOKEN:
213
+ return "", _render_leaderboard()
214
  last_err = None
215
  for attempt in range(3):
216
  try:
217
  raw = _call_llm(PROMPT_BINGO, text, max_tokens=600)
218
  data = _extract_json(raw)
219
+ bingo_html = _render_bingo(data)
220
+
221
+ # Leaderboard aktualisieren
222
+ metrics = data.get("metrics", [])
223
+ total = sum(int(m.get("score", 0)) for m in metrics)
224
+ max_score = len(metrics) * 10
225
+ verdict = data.get("verdict", "")
226
+ _add_to_lb(text, total, max_score, verdict)
227
+
228
+ return bingo_html, _render_leaderboard()
229
  except Exception as e:
230
  last_err = e
231
+ err_html = f"<p style='color:#c00;font-size:.85rem;padding:8px;'>Analyse fehlgeschlagen: {last_err}</p>"
232
+ return err_html, _render_leaderboard()
233
 
234
 
235
  def _render_bingo(data):
 
253
  for m in metrics:
254
  score = int(m.get("score", 0))
255
  label = m.get("label", m.get("icon", ""))
256
+ icon = ICONS.get(label, "")
257
  color = bar_color(score)
258
  pct = score * 10
259
  rows += f"""
 
271
  total = sum(int(m.get("score", 0)) for m in metrics)
272
  max_score = len(metrics) * 10
273
  total_pct = round(total / max_score * 100) if max_score else 0
 
274
  badge_color = "#C0392B" if total_pct >= 70 else "#E67E22" if total_pct >= 40 else "#27AE60"
275
 
276
  return f"""
277
  <div style="background:#fff;border:1px solid #E0DFDC;border-radius:12px;
278
+ padding:22px 26px;margin-top:4px;box-shadow:0 2px 12px rgba(0,0,0,.07);">
 
279
  <div style="display:flex;align-items:center;gap:10px;margin-bottom:20px;">
280
  <span style="font-size:1.3rem;">🎯</span>
281
  <span style="font-weight:700;font-size:.95rem;color:#004182;
 
312
  def run_translate(text, direction):
313
  result = translate(text, direction)
314
  if direction == "to_linkedin":
315
+ return (result,
316
+ gr.update(value=result, visible=True),
317
+ gr.update(value="", visible=False),
318
+ gr.update(value=_render_leaderboard()))
319
  else:
320
+ bingo_html, lb_html = get_bingo(text)
321
+ return (result,
322
+ gr.update(value="", visible=False),
323
+ gr.update(value=bingo_html, visible=True),
324
+ gr.update(value=lb_html))
325
 
326
 
327
  # ── CSS ───────────────────────────────────────────────────────────────────────
 
344
  }
345
  .li-header {
346
  background: linear-gradient(135deg, var(--li-blue-dark) 0%, var(--li-blue) 70%, var(--li-blue-mid) 100%);
347
+ border-radius: 12px; padding: 24px 28px; margin-bottom: 20px;
348
+ box-shadow: 0 4px 20px rgba(10,102,194,.3); display: flex; align-items: center; gap: 18px;
 
 
 
 
 
349
  }
350
  .li-header .icon { font-size: 2.6rem; line-height: 1; }
351
  .li-header h1 { margin: 0 !important; font-size: 1.65rem !important; font-weight: 700 !important; color: #fff !important; }
 
418
  )
419
  with gr.Column(scale=1, min_width=120):
420
  gr.HTML("<div style='height:40px'></div>")
421
+ translate_btn = gr.Button("Uebersetzen", variant="primary", size="lg")
422
  gr.HTML("<div style='height:12px'></div>")
423
  swap_btn = gr.Button("πŸ”„ β†’ LinkedIn", variant="secondary", size="sm")
424
  with gr.Column(scale=5):
 
429
  interactive=False,
430
  )
431
 
432
+ # ── Score + Markdown ──────────────────────────────────────────────────────
433
  with gr.Row():
434
  with gr.Column():
435
  markdown_out = gr.Markdown(
436
+ value="*Noch kein Ergebnis - bitte zuerst uebersetzen.*",
437
  visible=True,
438
  )
439
  bingo_out = gr.HTML(value="", visible=False)
440
 
441
+ # ── Leaderboard ───────────────────────────────────────────────────────────
442
+ with gr.Row():
443
+ with gr.Column():
444
+ lb_out = gr.HTML(value=_render_leaderboard())
445
+
446
  if not HF_TOKEN:
447
  gr.HTML("""
448
  <div style="background:#FFF4CE;border:1px solid #F9C642;border-left:4px solid #F9C642;
 
457
  <span>🧠 Llama 4 Maverick 17B</span>
458
  <span>πŸ”„ Bidirektional</span>
459
  <span>🎯 Corporate Nonsense Score</span>
460
+ <span>πŸ† Leaderboard</span>
461
  </div>
462
  """)
463
 
 
466
  translate_btn.click(
467
  fn=run_translate,
468
  inputs=[input_box, direction_state],
469
+ outputs=[output_box, markdown_out, bingo_out, lb_out],
470
  )
471
 
472
  def do_swap(direction, inp, out):