aidn commited on
Commit
a697c20
Β·
verified Β·
1 Parent(s): 82ed216

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -68
app.py CHANGED
@@ -95,7 +95,6 @@ Antworte NUR mit dem verbesserten Post. Kein Vorwort, keine ErklΓ€rung."""
95
  # ── HF Dataset Persistenz ──────────────────────────────────────────────────────
96
 
97
  def _fetch_dataset() -> list:
98
- """Laedt bestehende EintrΓ€ge aus dem HF Dataset Repo."""
99
  if not DATASET_REPO or not HF_TOKEN:
100
  return []
101
  try:
@@ -113,13 +112,12 @@ def _fetch_dataset() -> list:
113
  entries.append(json.loads(line))
114
  return entries
115
  except EntryNotFoundError:
116
- return [] # Datei existiert noch nicht
117
  except Exception:
118
  return []
119
 
120
 
121
  def _push_entry(entry: dict) -> str:
122
- """HΓ€ngt einen neuen Eintrag an das JSONL im HF Dataset an. Gibt Fehler oder leer zurΓΌck."""
123
  if not DATASET_REPO or not HF_TOKEN:
124
  return ""
125
  try:
@@ -149,10 +147,9 @@ def _dataset_count() -> int:
149
 
150
 
151
  def _init_leaderboard():
152
- """Beim Start: lokalen Cache immer frisch aus dem HF Dataset laden."""
153
  entries = _fetch_dataset()
154
  if not entries:
155
- return # kein Dataset konfiguriert oder noch leer
156
  lb = []
157
  for e in entries:
158
  metrics = e.get("metrics", [])
@@ -169,8 +166,7 @@ def _init_leaderboard():
169
  _save_lb(lb)
170
 
171
 
172
-
173
- # ── Lokales Leaderboard (schneller Cache fΓΌr UI) ──────────────────────────────
174
 
175
  def _load_lb() -> list:
176
  try:
@@ -215,10 +211,10 @@ def _render_leaderboard() -> str:
215
  def entry_html(e, rank, side="worst"):
216
  pct = e["pct"]
217
  if side == "best":
218
- color = "#1A5C34" if pct < 15 else "#27AE60" if pct < 70 else "#E67E22"
219
  else:
220
  color = "#C0392B" if pct >= 70 else "#E67E22" if pct >= 50 else "#D4AC0D" if pct >= 30 else "#27AE60"
221
- medal = ["πŸ₯‡","πŸ₯ˆ","πŸ₯‰"] [rank] if rank < 3 else f"{rank+1}."
222
  full = e["text"].replace("<", "&lt;").replace(">", "&gt;")
223
  preview = full[:120] + ("..." if len(full) > 120 else "")
224
  has_more = len(e["text"]) > 120
@@ -274,11 +270,11 @@ def _render_leaderboard() -> str:
274
  </span>
275
  <div style="margin-left:auto; display:flex; flex-direction:column; align-items:flex-end; gap:4px;">
276
  {dataset_badge}
277
- <button onclick="document.getElementById('hidden_sync_btn').click()"
278
- style="background:transparent; border:1px solid #E0DFDC; color:#666;
279
- font-size:.75rem; font-weight:600; padding:3px 10px; border-radius:99px;
280
  cursor:pointer;"
281
- onmouseover="this.style.background='#EBF3FB'; this.style.color='#0A66C2';"
282
  onmouseout="this.style.background='transparent'; this.style.color='#666';">
283
  πŸ”„ Aktualisieren
284
  </button>
@@ -302,11 +298,12 @@ def _render_leaderboard() -> str:
302
  </div>
303
  </div>"""
304
 
 
305
  def force_sync_and_render():
306
- """Zieht die aktuellsten Daten aus dem HF Dataset und rendert das Leaderboard neu."""
307
  _init_leaderboard()
308
  return _render_leaderboard()
309
 
 
310
  # ── Leaderboard beim Start initialisieren ─────────────────────────────────────
311
  _init_leaderboard()
312
 
@@ -369,10 +366,8 @@ def get_bingo(text):
369
  max_s = len(metrics) * 10
370
  verdict = data.get("verdict", "")
371
 
372
- # Lokales Leaderboard
373
  _add_to_lb(text, total, max_s, verdict)
374
 
375
- # HF Dataset (async best-effort)
376
  entry = {
377
  "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
378
  "post_text": text,
@@ -392,21 +387,20 @@ def get_bingo(text):
392
  return (f"<p style='color:#c00;font-size:.85rem;padding:8px;'>Analyse fehlgeschlagen: {last_err}</p>",
393
  _render_leaderboard())
394
 
 
395
  def generate_tuned_post(original_text, ton, substanz, laenge, zielgruppe, cta):
396
  if not original_text.strip() or not HF_TOKEN:
397
- return "Bitte zuerst einen Post analysieren."
398
-
399
  prompt = PROMPT_AI_TUNING.format(zielgruppe=zielgruppe, cta=cta)
400
  user_msg = f"""ORIGINAL POST:
401
  {original_text}
402
 
403
  TUNING:
404
  - Ton: {ton}/100 (0=Business Pro, 100=Dynamisch & Bold)
405
- - Substanz: {substanz}/100 (0=Storytelling, 100=Fakten & Insights)
406
  - LΓ€nge: {laenge}/100 (0=Kurz, 100=AusfΓΌhrlich)
407
  - Zielgruppe: {zielgruppe}
408
  - Call to Action: {cta}"""
409
-
410
  try:
411
  return _call_llm(prompt, user_msg, max_tokens=800)
412
  except Exception as e:
@@ -466,11 +460,12 @@ def _render_bingo(data):
466
  πŸ’¬ <strong style="font-style:normal;">Urteil:</strong> {verdict}
467
  </div>
468
  <div style="margin-top:14px;display:flex;justify-content:flex-end;">
469
- <button onclick="
470
- var panel = document.getElementById('tuning_toggle_btn');
471
- if(panel) panel.click();
472
- " style="background:#004182;color:#fff;border:none;border-radius:22px;
473
- padding:8px 20px;font-size:.82rem;font-weight:700;cursor:pointer;">
 
474
  ✨ AI Tuning
475
  </button>
476
  </div>
@@ -560,28 +555,24 @@ button.secondary:hover { background:var(--li-blue-light) !important; }
560
  font-size:.74rem; color:var(--li-muted); border-top:1px solid var(--li-border);
561
  padding-top:10px; margin-top:8px; display:flex; gap:20px; flex-wrap:wrap; justify-content:center;
562
  }
563
- .tiny-sync {
564
- min-width: fit-content !important;
565
- width: fit-content !important;
566
- margin-left: auto !important;
567
- margin-bottom: -15px !important;
568
- z-index: 10;
569
- }
570
- .tiny-sync button {
571
- background: transparent !important;
572
- border: 1px solid var(--li-border) !important;
573
- color: var(--li-muted) !important;
574
- font-size: 0.75rem !important;
575
- padding: 4px 12px !important;
576
- box-shadow: none !important;
577
- border-radius: 99px !important;
578
  }
579
- .tiny-sync button:hover {
580
- background: var(--li-blue-light) !important;
581
- color: var(--li-blue-dark) !important;
582
- border-color: var(--li-blue-mid) !important;
 
 
 
583
  }
584
  #hidden_sync_btn { display: none !important; }
 
585
  """
586
 
587
  # ── UI ─────────────────────────────────────────────────────────────────────────
@@ -622,20 +613,21 @@ with gr.Blocks(title="LinkedIn Translator") as demo:
622
  markdown_out = gr.Markdown(value="*Noch kein Ergebnis - bitte zuerst ΓΌbersetzen.*", visible=True)
623
  bingo_out = gr.HTML(value="", visible=False)
624
 
625
- # ── AI Tuning Panel ──────────────────────────────────────────────────────
 
 
626
  with gr.Row():
627
  with gr.Column():
628
- tuning_panel = gr.Column(visible=False)
629
- tuning_toggle_btn = gr.Button("toggle", elem_id="tuning_toggle_btn", visible=False)
630
  with tuning_panel:
631
  gr.HTML("""
632
- <div style="background:#fff;border:1px solid #E0DFDC;border-radius:12px;
633
- padding:22px 26px;margin-top:4px;box-shadow:0 2px 12px rgba(0,0,0,.07);">
634
- <div style="display:flex;align-items:center;gap:10px;margin-bottom:20px;">
635
  <span style="font-size:1.3rem;">✨</span>
636
  <span style="font-weight:700;font-size:.95rem;color:#004182;
637
  text-transform:uppercase;letter-spacing:.5px;">AI Post Tuning</span>
638
- </div>
 
 
639
  </div>
640
  """)
641
  with gr.Row():
@@ -666,16 +658,17 @@ with gr.Blocks(title="LinkedIn Translator") as demo:
666
  value="Kommentare & Dialog",
667
  label="⚑ Call to Action"
668
  )
 
669
  tuning_btn = gr.Button("✨ Post optimieren", variant="primary")
670
-
671
  tuning_out = gr.Textbox(
672
  label="πŸ’‘ Optimierter Post",
673
  lines=10,
674
  interactive=True,
675
- placeholder="Der optimierte Post erscheint hier..."
676
  )
677
-
678
 
 
679
  with gr.Row():
680
  with gr.Column():
681
  lb_out = gr.HTML(value=_render_leaderboard())
@@ -701,11 +694,14 @@ with gr.Blocks(title="LinkedIn Translator") as demo:
701
  <span>🧠 Llama 4 Maverick 17B</span>
702
  <span>πŸ”„ Bidirektional</span>
703
  <span>🎯 Corporate Nonsense Score</span>
 
704
  <span>πŸ† Leaderboard</span>
705
  <span>πŸ€— Auto-Dataset</span>
706
  </div>
707
  """)
708
 
 
 
709
  translate_btn.click(
710
  fn=run_translate,
711
  inputs=[input_box, direction_state],
@@ -714,12 +710,14 @@ with gr.Blocks(title="LinkedIn Translator") as demo:
714
 
715
  def do_swap(direction, inp, out):
716
  new_dir, new_inp, new_out, lbl_in, lbl_out, btn_txt = swap_direction(direction, inp, out)
717
- banner = ('<div class="direction-banner">Modus: '
718
  + lbl_in.split(" ", 1)[1] + " &rarr; " + lbl_out.split(" ", 1)[1] + "</div>")
719
  if new_dir == "to_linkedin":
720
- md_upd, bingo_upd = gr.update(value="*Noch kein Ergebnis.*", visible=True), gr.update(value="", visible=False)
 
721
  else:
722
- md_upd, bingo_upd = gr.update(value="", visible=False), gr.update(value="", visible=True)
 
723
  return (new_dir, gr.update(value=new_inp, label=lbl_in),
724
  gr.update(value=new_out, label=lbl_out),
725
  gr.update(value=btn_txt), banner, md_upd, bingo_upd)
@@ -731,25 +729,21 @@ with gr.Blocks(title="LinkedIn Translator") as demo:
731
  )
732
 
733
  hidden_sync_btn.click(
734
- fn=force_sync_and_render,
735
- inputs=[],
736
- outputs=[lb_out]
737
- )
738
 
739
- # Tuning panel toggle
740
- def toggle_tuning(current_text):
741
- return gr.update(visible=True)
742
-
743
  tuning_toggle_btn.click(
744
- fn=toggle_tuning,
745
- inputs=[input_box],
746
  outputs=[tuning_panel]
747
  )
748
 
749
- # Post optimieren
750
  tuning_btn.click(
751
  fn=generate_tuned_post,
752
  inputs=[input_box, slider_ton, slider_substanz, slider_laenge, dd_zielgruppe, dd_cta],
753
  outputs=[tuning_out]
754
  )
 
755
  demo.launch(css=CSS)
 
95
  # ── HF Dataset Persistenz ──────────────────────────────────────────────────────
96
 
97
  def _fetch_dataset() -> list:
 
98
  if not DATASET_REPO or not HF_TOKEN:
99
  return []
100
  try:
 
112
  entries.append(json.loads(line))
113
  return entries
114
  except EntryNotFoundError:
115
+ return []
116
  except Exception:
117
  return []
118
 
119
 
120
  def _push_entry(entry: dict) -> str:
 
121
  if not DATASET_REPO or not HF_TOKEN:
122
  return ""
123
  try:
 
147
 
148
 
149
  def _init_leaderboard():
 
150
  entries = _fetch_dataset()
151
  if not entries:
152
+ return
153
  lb = []
154
  for e in entries:
155
  metrics = e.get("metrics", [])
 
166
  _save_lb(lb)
167
 
168
 
169
+ # ── Lokales Leaderboard ────────────────────────────────────────────────────────
 
170
 
171
  def _load_lb() -> list:
172
  try:
 
211
  def entry_html(e, rank, side="worst"):
212
  pct = e["pct"]
213
  if side == "best":
214
+ color = "#1A5C34" if pct < 20 else "#27AE60" if pct < 40 else "#E67E22"
215
  else:
216
  color = "#C0392B" if pct >= 70 else "#E67E22" if pct >= 50 else "#D4AC0D" if pct >= 30 else "#27AE60"
217
+ medal = ["πŸ₯‡","πŸ₯ˆ","πŸ₯‰"][rank] if rank < 3 else f"{rank+1}."
218
  full = e["text"].replace("<", "&lt;").replace(">", "&gt;")
219
  preview = full[:120] + ("..." if len(full) > 120 else "")
220
  has_more = len(e["text"]) > 120
 
270
  </span>
271
  <div style="margin-left:auto; display:flex; flex-direction:column; align-items:flex-end; gap:4px;">
272
  {dataset_badge}
273
+ <button onclick="document.getElementById('hidden_sync_btn').click()"
274
+ style="background:transparent; border:1px solid #E0DFDC; color:#666;
275
+ font-size:.75rem; font-weight:600; padding:3px 10px; border-radius:99px;
276
  cursor:pointer;"
277
+ onmouseover="this.style.background='#EBF3FB'; this.style.color='#0A66C2';"
278
  onmouseout="this.style.background='transparent'; this.style.color='#666';">
279
  πŸ”„ Aktualisieren
280
  </button>
 
298
  </div>
299
  </div>"""
300
 
301
+
302
  def force_sync_and_render():
 
303
  _init_leaderboard()
304
  return _render_leaderboard()
305
 
306
+
307
  # ── Leaderboard beim Start initialisieren ─────────────────────────────────────
308
  _init_leaderboard()
309
 
 
366
  max_s = len(metrics) * 10
367
  verdict = data.get("verdict", "")
368
 
 
369
  _add_to_lb(text, total, max_s, verdict)
370
 
 
371
  entry = {
372
  "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
373
  "post_text": text,
 
387
  return (f"<p style='color:#c00;font-size:.85rem;padding:8px;'>Analyse fehlgeschlagen: {last_err}</p>",
388
  _render_leaderboard())
389
 
390
+
391
  def generate_tuned_post(original_text, ton, substanz, laenge, zielgruppe, cta):
392
  if not original_text.strip() or not HF_TOKEN:
393
+ return "Bitte zuerst einen Post eingeben."
 
394
  prompt = PROMPT_AI_TUNING.format(zielgruppe=zielgruppe, cta=cta)
395
  user_msg = f"""ORIGINAL POST:
396
  {original_text}
397
 
398
  TUNING:
399
  - Ton: {ton}/100 (0=Business Pro, 100=Dynamisch & Bold)
400
+ - Substanz: {substanz}/100 (0=Storytelling, 100=Fakten & Insights)
401
  - LΓ€nge: {laenge}/100 (0=Kurz, 100=AusfΓΌhrlich)
402
  - Zielgruppe: {zielgruppe}
403
  - Call to Action: {cta}"""
 
404
  try:
405
  return _call_llm(prompt, user_msg, max_tokens=800)
406
  except Exception as e:
 
460
  πŸ’¬ <strong style="font-style:normal;">Urteil:</strong> {verdict}
461
  </div>
462
  <div style="margin-top:14px;display:flex;justify-content:flex-end;">
463
+ <button onclick="document.getElementById('tuning_toggle_btn').click()"
464
+ style="background:#004182;color:#fff;border:none;border-radius:22px;
465
+ padding:8px 20px;font-size:.82rem;font-weight:700;cursor:pointer;
466
+ transition:background .15s;"
467
+ onmouseover="this.style.background='#0A66C2';"
468
+ onmouseout="this.style.background='#004182';">
469
  ✨ AI Tuning
470
  </button>
471
  </div>
 
555
  font-size:.74rem; color:var(--li-muted); border-top:1px solid var(--li-border);
556
  padding-top:10px; margin-top:8px; display:flex; gap:20px; flex-wrap:wrap; justify-content:center;
557
  }
558
+ .tuning-card {
559
+ background: #fff !important;
560
+ border: 1px solid #E0DFDC !important;
561
+ border-radius: 12px !important;
562
+ padding: 22px 26px !important;
563
+ margin-top: 4px !important;
564
+ box-shadow: 0 2px 12px rgba(0,0,0,.07) !important;
 
 
 
 
 
 
 
 
565
  }
566
+ .tuning-card .tuning-header {
567
+ display: flex;
568
+ align-items: center;
569
+ gap: 10px;
570
+ margin-bottom: 20px;
571
+ padding-bottom: 14px;
572
+ border-bottom: 1px solid #E0DFDC;
573
  }
574
  #hidden_sync_btn { display: none !important; }
575
+ #tuning_toggle_btn { display: none !important; }
576
  """
577
 
578
  # ── UI ─────────────────────────────────────────────────────────────────────────
 
613
  markdown_out = gr.Markdown(value="*Noch kein Ergebnis - bitte zuerst ΓΌbersetzen.*", visible=True)
614
  bingo_out = gr.HTML(value="", visible=False)
615
 
616
+ # ── AI Tuning Panel ───────────────────────────────────────────────────────
617
+ tuning_toggle_btn = gr.Button("toggle", elem_id="tuning_toggle_btn", visible=False)
618
+
619
  with gr.Row():
620
  with gr.Column():
621
+ tuning_panel = gr.Column(visible=False, elem_classes=["tuning-card"])
 
622
  with tuning_panel:
623
  gr.HTML("""
624
+ <div class="tuning-header">
 
 
625
  <span style="font-size:1.3rem;">✨</span>
626
  <span style="font-weight:700;font-size:.95rem;color:#004182;
627
  text-transform:uppercase;letter-spacing:.5px;">AI Post Tuning</span>
628
+ <span style="font-size:.78rem;color:#888;margin-left:8px;">
629
+ Optimiere deinen Post gezielt fΓΌr dein Ziel
630
+ </span>
631
  </div>
632
  """)
633
  with gr.Row():
 
658
  value="Kommentare & Dialog",
659
  label="⚑ Call to Action"
660
  )
661
+ gr.HTML("<div style='height:8px'></div>")
662
  tuning_btn = gr.Button("✨ Post optimieren", variant="primary")
663
+
664
  tuning_out = gr.Textbox(
665
  label="πŸ’‘ Optimierter Post",
666
  lines=10,
667
  interactive=True,
668
+ placeholder="Der optimierte Post erscheint hier – direkt kopierbar und editierbar."
669
  )
 
670
 
671
+ # ── Leaderboard ───────────────────────────────────────────────────────────
672
  with gr.Row():
673
  with gr.Column():
674
  lb_out = gr.HTML(value=_render_leaderboard())
 
694
  <span>🧠 Llama 4 Maverick 17B</span>
695
  <span>πŸ”„ Bidirektional</span>
696
  <span>🎯 Corporate Nonsense Score</span>
697
+ <span>✨ AI Tuning</span>
698
  <span>πŸ† Leaderboard</span>
699
  <span>πŸ€— Auto-Dataset</span>
700
  </div>
701
  """)
702
 
703
+ # ── Event Handler ─────────────────────────────────────────────────────────
704
+
705
  translate_btn.click(
706
  fn=run_translate,
707
  inputs=[input_box, direction_state],
 
710
 
711
  def do_swap(direction, inp, out):
712
  new_dir, new_inp, new_out, lbl_in, lbl_out, btn_txt = swap_direction(direction, inp, out)
713
+ banner = ('<div class="direction-banner">'
714
  + lbl_in.split(" ", 1)[1] + " &rarr; " + lbl_out.split(" ", 1)[1] + "</div>")
715
  if new_dir == "to_linkedin":
716
+ md_upd = gr.update(value="*Noch kein Ergebnis.*", visible=True)
717
+ bingo_upd = gr.update(value="", visible=False)
718
  else:
719
+ md_upd = gr.update(value="", visible=False)
720
+ bingo_upd = gr.update(value="", visible=True)
721
  return (new_dir, gr.update(value=new_inp, label=lbl_in),
722
  gr.update(value=new_out, label=lbl_out),
723
  gr.update(value=btn_txt), banner, md_upd, bingo_upd)
 
729
  )
730
 
731
  hidden_sync_btn.click(
732
+ fn=force_sync_and_render,
733
+ inputs=[],
734
+ outputs=[lb_out]
735
+ )
736
 
 
 
 
 
737
  tuning_toggle_btn.click(
738
+ fn=lambda: gr.update(visible=True),
739
+ inputs=[],
740
  outputs=[tuning_panel]
741
  )
742
 
 
743
  tuning_btn.click(
744
  fn=generate_tuned_post,
745
  inputs=[input_box, slider_ton, slider_substanz, slider_laenge, dd_zielgruppe, dd_cta],
746
  outputs=[tuning_out]
747
  )
748
+
749
  demo.launch(css=CSS)