aidn commited on
Commit
bdc4ca8
Β·
verified Β·
1 Parent(s): 7ad6fd2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +292 -0
app.py ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ from huggingface_hub import InferenceClient
4
+
5
+ # ── Konfiguration ──────────────────────────────────────────────────────────────
6
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
7
+ MODEL_ID = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
8
+
9
+ # ── System Prompts ─────────────────────────────────────────────────────────────
10
+
11
+ PROMPT_TO_LINKEDIN = """Du bist ein LinkedIn-Influencer-Generator. Deine einzige Aufgabe ist es, banale, alltΓ€gliche Aussagen in absurd ausschweifende, klischeebeladene LinkedIn-Posts zu verwandeln.
12
+
13
+ Regeln:
14
+ - Alles ist eine "Journey", ein "Gamechanger" oder eine "powerful lesson"
15
+ - Nutze mindestens 3 Emojis strategisch
16
+ - ErwΓ€hne "Growth", "Mindset", "Passion" oder "Impact" wo immer mΓΆglich
17
+ - FΓΌge eine persΓΆnliche Anekdote hinzu, die niemand braucht
18
+ - Endet mit einer rhetorischen Frage an die Community
19
+ - Benutze dramatische ZeilenumbrΓΌche fΓΌr Effekt
20
+ - Alles ist ausnahmslos positiv, auch wenn das Original negativ ist
21
+ - Hashtags am Ende sind Pflicht (mindestens 5)
22
+ - Klingt wie jemand, der gerade ein Buch ΓΌber sich selbst schreiben wΓΌrde
23
+
24
+ Antworte NUR mit dem LinkedIn-Post. Kein Vorwort, keine ErklΓ€rung."""
25
+
26
+ PROMPT_FROM_LINKEDIN = """Du bist ein semantischer Reduzierer. Deine Aufgabe ist es, aufgeblasene LinkedIn-Texte auf den absolut nΓΌchternen, semantisch einzig relevanten Kern zu reduzieren.
27
+
28
+ Regeln:
29
+ - Entferne ALLE Buzzwords, Emojis, Hashtags und Floskeln
30
+ - Ein Satz. Maximal zwei.
31
+ - Keine Wertung, keine Emotion, reiner Informationsgehalt
32
+ - Kein "Ich freue mich", kein "Excited", kein "Honored" – was ist das Faktum?
33
+ - Schreibe wie ein Pressemitteilungs-Minimalist
34
+
35
+ Antworte NUR mit dem reduzierten Satz. Kein Vorwort, keine ErklΓ€rung."""
36
+
37
+
38
+ # ── LLM-Call ──────────────────────────────────────────────────────────────────
39
+
40
+ def translate(text: str, direction: str) -> str:
41
+ if not text.strip():
42
+ return ""
43
+ if not HF_TOKEN:
44
+ return "⚠️ Kein HF_TOKEN gefunden. Bitte in den Space-Settings als Secret hinterlegen."
45
+
46
+ system_prompt = PROMPT_TO_LINKEDIN if direction == "to_linkedin" else PROMPT_FROM_LINKEDIN
47
+
48
+ try:
49
+ client = InferenceClient(provider="novita", api_key=HF_TOKEN)
50
+ response = client.chat.completions.create(
51
+ model=MODEL_ID,
52
+ messages=[
53
+ {"role": "system", "content": system_prompt},
54
+ {"role": "user", "content": text},
55
+ ],
56
+ max_tokens=1024,
57
+ )
58
+ return response.choices[0].message.content.strip()
59
+ except Exception as e:
60
+ return f"⚠️ Fehler: {e}"
61
+
62
+
63
+ # ── State & Handler ────────────────────────────────────────────────────────────
64
+
65
+ # direction: "to_linkedin" β†’ Normal β†’ LinkedIn
66
+ # "from_linkedin" β†’ LinkedIn β†’ Normal
67
+
68
+ def swap_direction(current_dir, input_text, output_text):
69
+ """Tauscht Richtung und Felder."""
70
+ new_dir = "from_linkedin" if current_dir == "to_linkedin" else "to_linkedin"
71
+ return (
72
+ new_dir,
73
+ output_text, # altes Output wird neuer Input
74
+ input_text, # alter Input wird neues Output
75
+ *_labels(new_dir),
76
+ )
77
+
78
+ def _labels(direction):
79
+ if direction == "to_linkedin":
80
+ return "✍️ Normale Aussage", "πŸ’Ό LinkedIn Speech", "πŸ”„ β†’ LinkedIn"
81
+ else:
82
+ return "πŸ’Ό LinkedIn Speech", "✍️ Normale Aussage", "πŸ”„ β†’ Entbuzzen"
83
+
84
+ def run_translate(text, direction):
85
+ return translate(text, direction)
86
+
87
+
88
+ # ── CSS ───────────────────────────────────────────────────────────────────────
89
+
90
+ CSS = """
91
+ :root {
92
+ --li-blue: #0A66C2;
93
+ --li-blue-dark: #004182;
94
+ --li-blue-light: #EBF3FB;
95
+ --li-blue-mid: #70B5F9;
96
+ --li-bg: #F3F2EF;
97
+ --li-card: #FFFFFF;
98
+ --li-text: #191919;
99
+ --li-muted: #666666;
100
+ --li-border: #E0DFDC;
101
+ }
102
+
103
+ body, .gradio-container {
104
+ background: var(--li-bg) !important;
105
+ font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important;
106
+ }
107
+
108
+ .li-header {
109
+ background: linear-gradient(135deg, var(--li-blue-dark) 0%, var(--li-blue) 70%, var(--li-blue-mid) 100%);
110
+ border-radius: 12px;
111
+ padding: 24px 28px;
112
+ margin-bottom: 20px;
113
+ box-shadow: 0 4px 20px rgba(10,102,194,.3);
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 18px;
117
+ }
118
+ .li-header .icon { font-size: 2.6rem; line-height: 1; }
119
+ .li-header h1 { margin: 0 !important; font-size: 1.65rem !important; font-weight: 700 !important; color: #fff !important; }
120
+ .li-header p { margin: 4px 0 0 !important; font-size: .86rem !important; color: rgba(255,255,255,.88) !important; }
121
+ .li-header .badge {
122
+ margin-left: auto;
123
+ background: rgba(255,255,255,.18);
124
+ border-radius: 20px;
125
+ padding: 5px 14px;
126
+ font-size: .74rem;
127
+ font-weight: 600;
128
+ letter-spacing: .4px;
129
+ color: #fff;
130
+ white-space: nowrap;
131
+ }
132
+
133
+ .direction-banner {
134
+ text-align: center;
135
+ font-size: .8rem;
136
+ font-weight: 700;
137
+ letter-spacing: .6px;
138
+ text-transform: uppercase;
139
+ color: var(--li-blue-dark);
140
+ margin-bottom: 6px;
141
+ }
142
+
143
+ label > span {
144
+ font-weight: 600 !important;
145
+ font-size: .76rem !important;
146
+ text-transform: uppercase !important;
147
+ letter-spacing: .5px !important;
148
+ color: var(--li-blue-dark) !important;
149
+ }
150
+
151
+ textarea {
152
+ font-size: .9rem !important;
153
+ line-height: 1.7 !important;
154
+ border-color: var(--li-border) !important;
155
+ border-radius: 8px !important;
156
+ background: var(--li-card) !important;
157
+ color: var(--li-text) !important;
158
+ resize: vertical !important;
159
+ }
160
+ textarea:focus {
161
+ border-color: var(--li-blue) !important;
162
+ box-shadow: 0 0 0 2px var(--li-blue-light) !important;
163
+ outline: none !important;
164
+ }
165
+
166
+ button.primary {
167
+ background: var(--li-blue) !important;
168
+ border-radius: 22px !important;
169
+ border: none !important;
170
+ font-weight: 700 !important;
171
+ font-size: 1rem !important;
172
+ padding: 10px 32px !important;
173
+ box-shadow: 0 2px 10px rgba(10,102,194,.35) !important;
174
+ transition: background .15s, box-shadow .15s !important;
175
+ }
176
+ button.primary:hover {
177
+ background: var(--li-blue-dark) !important;
178
+ box-shadow: 0 4px 18px rgba(10,102,194,.45) !important;
179
+ }
180
+
181
+ button.secondary {
182
+ background: var(--li-card) !important;
183
+ color: var(--li-blue) !important;
184
+ border: 2px solid var(--li-blue) !important;
185
+ border-radius: 22px !important;
186
+ font-weight: 700 !important;
187
+ font-size: 1rem !important;
188
+ padding: 10px 28px !important;
189
+ transition: all .15s !important;
190
+ }
191
+ button.secondary:hover {
192
+ background: var(--li-blue-light) !important;
193
+ }
194
+
195
+ .li-footer {
196
+ font-size: .74rem;
197
+ color: var(--li-muted);
198
+ border-top: 1px solid var(--li-border);
199
+ padding-top: 10px;
200
+ margin-top: 8px;
201
+ display: flex;
202
+ gap: 20px;
203
+ flex-wrap: wrap;
204
+ justify-content: center;
205
+ }
206
+ """
207
+
208
+ # ── UI ─────────────────────────────────────────────────────────────────────────
209
+
210
+ with gr.Blocks(title="LinkedIn Translator", css=CSS) as demo:
211
+
212
+ direction_state = gr.State("to_linkedin")
213
+
214
+ gr.HTML("""
215
+ <div class="li-header">
216
+ <div class="icon">πŸ’Ό</div>
217
+ <div>
218
+ <h1>LinkedIn Translator</h1>
219
+ <p>Banale Wahrheit ↔ Epische LinkedIn-Prosa &nbsp;Β·&nbsp; powered by Llama 4</p>
220
+ </div>
221
+ <div class="badge">✨ AI-Powered</div>
222
+ </div>
223
+ """)
224
+
225
+ dir_label = gr.HTML(
226
+ '<div class="direction-banner">Modus: Normale Sprache β†’ LinkedIn Speech</div>'
227
+ )
228
+
229
+ with gr.Row(equal_height=True):
230
+ with gr.Column(scale=5):
231
+ input_box = gr.Textbox(
232
+ label="✍️ Normale Aussage",
233
+ placeholder="z.B. β€žIch hab heute meinen Kaffee verschΓΌttet."",
234
+ lines=10,
235
+ )
236
+ with gr.Column(scale=1, min_width=120):
237
+ gr.HTML("<div style='height:40px'></div>")
238
+ translate_btn = gr.Button("β–Ά Übersetzen", variant="primary", size="lg")
239
+ gr.HTML("<div style='height:12px'></div>")
240
+ swap_btn = gr.Button("πŸ”„ β†’ LinkedIn", variant="secondary", size="sm",
241
+ elem_id="swap_btn")
242
+ with gr.Column(scale=5):
243
+ output_box = gr.Textbox(
244
+ label="πŸ’Ό LinkedIn Speech",
245
+ placeholder="Die transformierte Version erscheint hier ...",
246
+ lines=10,
247
+ interactive=False,
248
+ )
249
+
250
+ if not HF_TOKEN:
251
+ gr.HTML("""
252
+ <div style="background:#FFF4CE;border:1px solid #F9C642;border-left:4px solid #F9C642;
253
+ border-radius:6px;padding:10px 14px;font-size:.85rem;color:#7A5800;margin-top:8px;">
254
+ <strong>⚠️ Kein HF_TOKEN gefunden.</strong>
255
+ FΓΌge ihn unter <em>Settings β†’ Variables and secrets</em> als <code>HF_TOKEN</code> hinzu.
256
+ </div>
257
+ """)
258
+
259
+ gr.HTML("""
260
+ <div class="li-footer">
261
+ <span>🧠 Llama 4 Maverick 17B</span>
262
+ <span>πŸ”„ Bidirektional</span>
263
+ <span>πŸ’‘ Ironie inklusive</span>
264
+ </div>
265
+ """)
266
+
267
+ # ── Event-Handler ───────────���──────────────────────────────────────────────
268
+
269
+ translate_btn.click(
270
+ fn=run_translate,
271
+ inputs=[input_box, direction_state],
272
+ outputs=[output_box],
273
+ )
274
+
275
+ def do_swap(direction, inp, out):
276
+ new_dir, new_inp, new_out, lbl_in, lbl_out, btn_txt = swap_direction(direction, inp, out)
277
+ banner = f'<div class="direction-banner">Modus: {lbl_in.split(" ", 1)[1]} β†’ {lbl_out.split(" ", 1)[1]}</div>'
278
+ return (
279
+ new_dir,
280
+ gr.update(value=new_inp, label=lbl_in),
281
+ gr.update(value=new_out, label=lbl_out),
282
+ gr.update(value=btn_txt),
283
+ banner,
284
+ )
285
+
286
+ swap_btn.click(
287
+ fn=do_swap,
288
+ inputs=[direction_state, input_box, output_box],
289
+ outputs=[direction_state, input_box, output_box, swap_btn, dir_label],
290
+ )
291
+
292
+ demo.launch()