aidn commited on
Commit
526c809
·
verified ·
1 Parent(s): fff9250

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +378 -19
index.html CHANGED
@@ -1,19 +1,378 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="de" class="scroll-smooth">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Tutorial: Multi-Agenten-Systeme mit Haystack 2.0</title>
7
+
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+
11
+ <!-- Highlight.js for Code Formatting -->
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
14
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
15
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/xml.min.js"></script>
16
+
17
+ <!-- Google Fonts -->
18
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
19
+
20
+ <script>
21
+ tailwind.config = {
22
+ theme: {
23
+ extend: {
24
+ fontFamily: {
25
+ sans: ['Inter', 'sans-serif'],
26
+ mono: ['Fira Code', 'monospace'],
27
+ },
28
+ colors: {
29
+ primary: '#2563eb', // Blue-600
30
+ sidebar: '#0f172a', // Slate-900
31
+ }
32
+ }
33
+ }
34
+ }
35
+ </script>
36
+
37
+ <style>
38
+ body { font-family: 'Inter', sans-serif; }
39
+ pre code { font-family: 'Fira Code', monospace; border-radius: 0.5rem; }
40
+ .prose h2 { margin-top: 2.5rem; margin-bottom: 1rem; font-weight: 700; font-size: 1.5rem; color: #1e293b; border-bottom: 2px solid #e2e8f0; padding-bottom: 0.5rem; }
41
+ .prose h3 { margin-top: 2rem; margin-bottom: 0.75rem; font-weight: 600; font-size: 1.25rem; color: #334155; }
42
+ .prose p { margin-bottom: 1.25rem; line-height: 1.75; color: #475569; }
43
+ .prose table { width: 100%; border-collapse: collapse; margin-bottom: 2rem; }
44
+ .prose th, .prose td { border: 1px solid #cbd5e1; padding: 0.75rem; text-align: left; }
45
+ .prose th { background-color: #f1f5f9; font-weight: 600; color: #1e293b; }
46
+ .copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: rgba(255,255,255,0.1); color: #fff; border: none; padding: 0.25rem 0.5rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.75rem; transition: background 0.2s; }
47
+ .copy-btn:hover { background: rgba(255,255,255,0.2); }
48
+ .code-wrapper { position: relative; margin-bottom: 1.5rem; }
49
+ </style>
50
+ </head>
51
+ <body class="bg-slate-50 text-slate-800 flex flex-col md:flex-row min-h-screen">
52
+
53
+ <!-- Mobile Header -->
54
+ <div class="md:hidden bg-sidebar text-white p-4 flex justify-between items-center sticky top-0 z-50">
55
+ <h1 class="font-bold text-lg">Haystack 2.0 Tutorial</h1>
56
+ <button id="mobile-menu-btn" class="text-white focus:outline-none">
57
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg>
58
+ </button>
59
+ </div>
60
+
61
+ <!-- Sidebar Navigation -->
62
+ <nav id="sidebar" class="hidden md:flex flex-col w-full md:w-64 bg-sidebar text-slate-300 h-screen sticky top-0 overflow-y-auto shrink-0 transition-all duration-300 z-40">
63
+ <div class="p-6 hidden md:block">
64
+ <h1 class="text-2xl font-bold text-white tracking-tight">The Infinite Cookbook</h1>
65
+ <p class="text-xs text-slate-400 mt-2">MAS mit Haystack & Hugging Face</p>
66
+ </div>
67
+ <ul class="flex-1 px-4 pb-6 space-y-2 text-sm">
68
+ <li><a href="#intro" class="block py-2 px-3 rounded hover:bg-slate-800 hover:text-white transition">1. Einleitung & Architektur</a></li>
69
+ <li><a href="#hf-infra" class="block py-2 px-3 rounded hover:bg-slate-800 hover:text-white transition">2. Hugging Face Infrastruktur</a></li>
70
+ <li><a href="#agent1" class="block py-2 px-3 rounded hover:bg-slate-800 hover:text-white transition">3. Agent 1: Forschung & Suche</a></li>
71
+ <li><a href="#agent2" class="block py-2 px-3 rounded hover:bg-slate-800 hover:text-white transition">4. Agent 2: Struktur & Pydantic</a></li>
72
+ <li><a href="#agent3" class="block py-2 px-3 rounded hover:bg-slate-800 hover:text-white transition">5. Agent 3: Visionär (Bildgen.)</a></li>
73
+ <li><a href="#agent4" class="block py-2 px-3 rounded hover:bg-slate-800 hover:text-white transition">6. Agent 4: Meta-Aggregator (HTML)</a></li>
74
+ <li><a href="#orchestration" class="block py-2 px-3 rounded hover:bg-slate-800 hover:text-white transition">7. Orchestrierung & Fehlerbehandlung</a></li>
75
+ </ul>
76
+ </nav>
77
+
78
+ <!-- Main Content -->
79
+ <main class="flex-1 w-full max-w-4xl mx-auto p-6 md:p-10 lg:p-12">
80
+ <div class="prose max-w-none">
81
+
82
+ <!-- Hero -->
83
+ <div class="bg-blue-50 border-l-4 border-blue-500 p-6 rounded-r-lg mb-10">
84
+ <h1 class="text-3xl font-bold text-blue-900 mb-2">Tutorial: Bau eines Autonomen Kulinarischen Systems</h1>
85
+ <p class="text-blue-800 m-0">In diesem Tutorial setzen wir die Architektur aus dem Bericht <strong>"Architekturen autonomer kulinarischer Wissenssysteme"</strong> in Code um. Wir bauen "The Infinite Cookbook" mit Haystack 2.0 und der Hugging Face Inference-Infrastruktur.</p>
86
+ </div>
87
+
88
+ <section id="intro">
89
+ <h2>1. Die technologische Basis: Haystack 2.0</h2>
90
+ <p>Der Übergang von Haystack 1.x zu 2.0 markiert einen Paradigmenwechsel. Frühere Versionen waren lineare Ketten; Haystack 2.0 nutzt <strong>Komponenten und Pipelines als dynamische Rechengraphen</strong>. Dies unterstützt Kontrollflüsse, Schleifen und präzise Datenflusssteuerung – die Grundlage für echte Agenten.</p>
91
+ <p>Anstatt ein einzelnes, überfordertes LLM zu nutzen, orchestrieren wir eine Koalition spezialisierter Einheiten. Typprüfung an den Ein- und Ausgabesockets sichert die Stabilität.</p>
92
+ </section>
93
+
94
+ <section id="hf-infra">
95
+ <h2>2. Hugging Face Inference Provider</h2>
96
+ <p>Die Integration von Open-Source-Modellen erfolgt primär über die <code>HuggingFaceAPIChatGenerator</code>-Komponente. Seit Juli 2025 sind <code>chat_completion</code>-Endpunkte der Standard. Hier ist eine Übersicht der Infrastruktur-Optionen:</p>
97
+
98
+ <div class="overflow-x-auto">
99
+ <table>
100
+ <thead>
101
+ <tr>
102
+ <th>Infrastruktur-Option</th>
103
+ <th>Charakteristika</th>
104
+ <th>Anwendungsfall im MAS</th>
105
+ </tr>
106
+ </thead>
107
+ <tbody>
108
+ <tr>
109
+ <td><strong>Serverless Inference API</strong></td>
110
+ <td>Kostenlos/Rate-limited, schnell für Experimente</td>
111
+ <td>Prototyping und einfache Zusammenfassungen.</td>
112
+ </tr>
113
+ <tr>
114
+ <td><strong>Inference Endpoints</strong></td>
115
+ <td>Dedizierte Instanzen, stundenbasierte Abrechnung</td>
116
+ <td>Produktion, hohe Last, garantierte Latenz für Bildgenerierung.</td>
117
+ </tr>
118
+ <tr>
119
+ <td><strong>Text Generation Inference (TGI)</strong></td>
120
+ <td>Optimiertes Serving, unterstützt JSON-Guiding</td>
121
+ <td>Komplexe Datenextraktion mit strikter Schema-Einhaltung.</td>
122
+ </tr>
123
+ <tr>
124
+ <td><strong>Lokale Transformer</strong></td>
125
+ <td>Volle Kontrolle, erfordert eigene GPU-Ressourcen</td>
126
+ <td>Datenschutz-kritische Anwendungen oder Offline-Betrieb.</td>
127
+ </tr>
128
+ </tbody>
129
+ </table>
130
+ </div>
131
+ </section>
132
+
133
+ <section id="agent1">
134
+ <h2>3. Agent 1: Der kulinarische Forschungs-Spezialist</h2>
135
+ <p>Dieser Agent durchsucht das Web iterativ nach authentischen Rezepten. Er nutzt Suchmaschinen, extrahiert HTML, bereinigt es und rankt die Ergebnisse nach Relevanz (Context Optimization).</p>
136
+
137
+ <div class="code-wrapper">
138
+ <button class="copy-btn" onclick="copyCode(this)">Kopieren</button>
139
+ <pre><code class="language-python">from haystack import Pipeline
140
+ from haystack.components.websearch import SerperDevWebSearch
141
+ from haystack.components.fetchers import LinkContentFetcher
142
+ from haystack.components.converters import HTMLToDocument
143
+ from haystack.components.rankers import TransformersSimilarityRanker
144
+
145
+ # Pipeline-Definition für den Forschungs-Agenten
146
+ research_pipe = Pipeline()
147
+
148
+ # Komponenten hinzufügen
149
+ research_pipe.add_component("search", SerperDevWebSearch(api_key="DEIN_API_KEY"))
150
+ research_pipe.add_component("fetcher", LinkContentFetcher())
151
+ research_pipe.add_component("html_converter", HTMLToDocument())
152
+ research_pipe.add_component("ranker", TransformersSimilarityRanker(model="cross-encoder/ms-marco-MiniLM-L-6-v2"))
153
+
154
+ # Graph verbinden
155
+ research_pipe.connect("search.links", "fetcher.urls")
156
+ research_pipe.connect("fetcher.streams", "html_converter.sources")
157
+ research_pipe.connect("html_converter.documents", "ranker.documents")
158
+
159
+ # Ausführungssimulation (Im echten System orchestriert durch den Koordinator)
160
+ # result = research_pipe.run({"search": {"query": "authentische neapolitanische Pizza"}, "ranker": {"query": "authentische neapolitanische Pizza"}})
161
+ </code></pre>
162
+ </div>
163
+ </section>
164
+
165
+ <section id="agent2">
166
+ <h2>4. Agent 2: Der Struktur-Architekt für Rezeptdaten</h2>
167
+ <p>Die unstrukturierten Forschungsnotizen müssen in ein maschinenlesbares Format überführt werden. Haystack 2.0 unterstützt die Generierung strukturierter Ausgaben nativ über <strong>Pydantic-Modelle</strong>.</p>
168
+
169
+ <div class="code-wrapper">
170
+ <button class="copy-btn" onclick="copyCode(this)">Kopieren</button>
171
+ <pre><code class="language-python">from pydantic import BaseModel
172
+ from typing import List, Dict, Literal
173
+
174
+ # Definition des Pydantic-Schemas laut Spezifikation
175
+ class Ingredient(BaseModel):
176
+ name: str
177
+ amount: float
178
+ unit: str
179
+
180
+ class RecipeStructure(BaseModel):
181
+ recipe_title: str
182
+ servings: int
183
+ ingredients: List[Ingredient]
184
+ instructions: List[str]
185
+ nutrients: Dict[str, float]
186
+ difficulty: Literal["Einfach", "Mittel", "Schwer"]
187
+
188
+ # Nutzung im HuggingFace Chat Generator (mit TGI & JSON Guiding)
189
+ # generator = HuggingFaceAPIChatGenerator(
190
+ # api_type="text_generation_inference",
191
+ # generation_kwargs={"response_format": RecipeStructure.schema_json()}
192
+ # )
193
+ </code></pre>
194
+ </div>
195
+ <p>Durch die Erzwingung dieses Schemas werden Halluzinationen in der Struktur eliminiert. Der Agent wird zudem angewiesen, <em>keine eigenen Fakten zu erfinden</em>, sondern sich streng an den Kontext von Agent 1 zu halten.</p>
196
+ </section>
197
+
198
+ <section id="agent3">
199
+ <h2>5. Agent 3: Der Visionär - Bildgenerierung</h2>
200
+ <p>Ein Kochbuch braucht Bilder. Da Standard-Generatoren textbasiert sind, implementieren wir eine <strong>benutzerdefinierte Komponente</strong> mit dem <code>@component</code>-Dekorator, die z.B. Stable Diffusion XL (SDXL) über die Hugging Face <code>diffusers</code> Bibliothek anspricht.</p>
201
+
202
+ <div class="code-wrapper">
203
+ <button class="copy-btn" onclick="copyCode(this)">Kopieren</button>
204
+ <pre><code class="language-python">from haystack import component
205
+ from haystack.dataclasses import ImageContent
206
+ # import diffusers ...
207
+
208
+ @component
209
+ class StableDiffusionGenerator:
210
+ def __init__(self, model_id="stabilityai/stable-diffusion-xl-base-1.0"):
211
+ self.model_id = model_id
212
+ # self.pipe = DiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
213
+ # Optimierungen wie CPU-Offloading hier initialisieren
214
+
215
+ @component.output_types(image=ImageContent)
216
+ def run(self, prompt: str):
217
+ # Optimierter Prompt z.B.: "Dampfende Lasagne in einer rustikalen Keramikform..."
218
+
219
+ # image = self.pipe(prompt).images[0]
220
+ # Bild in Base64 umwandeln für Multimodalen Fluss
221
+ # base64_str = image_to_base64(image)
222
+
223
+ # Rückgabe als Haystack 2.0 ImageContent Objekt
224
+ return {"image": ImageContent.from_base64(base64_str)}
225
+ </code></pre>
226
+ </div>
227
+ </section>
228
+
229
+ <section id="agent4">
230
+ <h2>6. Agent 4: Der Meta-Aggregator und HTML-Designer</h2>
231
+ <p>Der letzte Agent empfängt das strukturierte Rezept (JSON) und das Bild (Base64) und verpackt es mithilfe von <strong>Jinja2-Templating</strong> in ansprechendes HTML. Dies erfordert keine externen Abhängigkeiten mehr.</p>
232
+
233
+ <div class="code-wrapper">
234
+ <button class="copy-btn" onclick="copyCode(this)">Kopieren</button>
235
+ <pre><code class="language-xml">&lt;!-- Jinja2 Template für den HTML-Agenten --&gt;
236
+ &lt;div class="recipe-card"&gt;
237
+ &lt;h1&gt;{{ recipe_title }}&lt;/h1&gt;
238
+
239
+ &lt;!-- Das Bild wird direkt als Base64 eingebettet --&gt;
240
+ &lt;img src="data:image/png;base64,{{ image_base64 }}" alt="{{ recipe_title }}"&gt;
241
+
242
+ &lt;div class="meta"&gt;
243
+ &lt;span&gt;Zeit: {{ prep_time + cook_time }} min&lt;/span&gt;
244
+ &lt;span&gt;Schwierigkeit: {{ difficulty }}&lt;/span&gt;
245
+ &lt;/div&gt;
246
+
247
+ &lt;h3&gt;Zutaten&lt;/h3&gt;
248
+ &lt;ul&gt;
249
+ {% for ingredient in ingredients %}
250
+ &lt;li&gt;{{ ingredient.amount }} {{ ingredient.unit }} {{ ingredient.name}}&lt;/li&gt;
251
+ {% endfor %}
252
+ &lt;/ul&gt;
253
+
254
+ &lt;h3&gt;Zubereitung&lt;/h3&gt;
255
+ &lt;ol&gt;
256
+ {% for step in instructions %}
257
+ &lt;li&gt;{{ step }}&lt;/li&gt;
258
+ {% endfor %}
259
+ &lt;/ol&gt;
260
+ &lt;/div&gt;</code></pre>
261
+ </div>
262
+ </section>
263
+
264
+ <section id="orchestration">
265
+ <h2>7. Orchestrierung und das Zusammenspiel der Agenten</h2>
266
+ <p>Die Koordination erfolgt über den <strong>"Agent-as-a-Tool"</strong>-Ansatz. Ein Haupt-Agent (Coordinator) übersetzt die Nutzeranfrage (z.B. <em>"Erstelle ein Rezept für ein thailändisches grünes Curry..."</em>) in Tool-Aufrufe.</p>
267
+ <ol class="list-decimal pl-6 mb-6 space-y-2 text-slate-600">
268
+ <li><strong>Recherche-Delegation:</strong> Aufruf von <code>research_tool</code>.</li>
269
+ <li><strong>Strukturierungs-Delegation:</strong> Aufruf von <code>recipe_summary_tool</code> (liefert Pydantic-Objekt).</li>
270
+ <li><strong>Visualisierungs-Delegation:</strong> Aufruf von <code>image_generation_tool</code>.</li>
271
+ <li><strong>Meta-Verpackung:</strong> Aufruf von <code>html_packaging_tool</code>.</li>
272
+ </ol>
273
+
274
+ <h3>Fehlerbehandlung mit Routern</h3>
275
+ <p>Falls ein Sub-Agent scheitert (z.B. keine Quellen gefunden), greift Haystacks Kontrollfluss-Logik ein:</p>
276
+
277
+ <div class="overflow-x-auto">
278
+ <table>
279
+ <thead>
280
+ <tr>
281
+ <th>Mechanismus</th>
282
+ <th>Komponente (Haystack)</th>
283
+ <th>Nutzen im kulinarischen MAS</th>
284
+ </tr>
285
+ </thead>
286
+ <tbody>
287
+ <tr>
288
+ <td><strong>Tool Invocation</strong></td>
289
+ <td><code>ToolInvoker</code></td>
290
+ <td>Führt die geplanten Aktionen (Suche, Bildgen) aus.</td>
291
+ </tr>
292
+ <tr>
293
+ <td><strong>State Management</strong></td>
294
+ <td>Agent State</td>
295
+ <td>Speichert das Rezeptobjekt über Agenten-Grenzen hinweg.</td>
296
+ </tr>
297
+ <tr>
298
+ <td><strong>Exit Conditions</strong></td>
299
+ <td><code>exit_conditions</code></td>
300
+ <td>Beendet den Loop, sobald der finale HTML-Code vorliegt.</td>
301
+ </tr>
302
+ <tr>
303
+ <td><strong>Fehlerkorrektur</strong></td>
304
+ <td><code>ConditionalRouter</code></td>
305
+ <td>Leitet bei Tool-Fehlern den Fluss zurück zur Modifikation (z.B. neuer Such-Prompt).</td>
306
+ </tr>
307
+ </tbody>
308
+ </table>
309
+ </div>
310
+
311
+ <div class="mt-8 p-4 bg-slate-100 rounded-lg text-sm text-slate-600 border border-slate-200">
312
+ <strong>Fazit:</strong> Durch die Zerlegung in spezialisierte Pipelines und die Nutzung von Hugging Face Modellen (Qwen, Llama-3, Mistral, FLUX) über Haystack 2.0 entsteht ein robustes, schema-treues System, das den neuen Standard für KI-gestützte Content-Generierung definiert.
313
+ </div>
314
+ </section>
315
+
316
+ <footer class="mt-16 pt-8 border-t border-slate-200 text-sm text-slate-500 text-center">
317
+ <p>Basierend auf dem Bericht: "Architekturen autonomer kulinarischer Wissenssysteme". Generiert für das "The Infinite Cookbook" Projekt.</p>
318
+ </footer>
319
+ </div>
320
+ </main>
321
+
322
+ <script>
323
+ // Syntax Highlighting initialisieren
324
+ document.addEventListener('DOMContentLoaded', (event) => {
325
+ hljs.highlightAll();
326
+ });
327
+
328
+ // Mobile Menu Toggle
329
+ const btn = document.getElementById('mobile-menu-btn');
330
+ const sidebar = document.getElementById('sidebar');
331
+
332
+ btn.addEventListener('click', () => {
333
+ sidebar.classList.toggle('hidden');
334
+ });
335
+
336
+ // Smooth scroll adjustment for fixed header (mobile)
337
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
338
+ anchor.addEventListener('click', function (e) {
339
+ e.preventDefault();
340
+ if(window.innerWidth < 768) {
341
+ sidebar.classList.add('hidden'); // Close menu on click on mobile
342
+ }
343
+ document.querySelector(this.getAttribute('href')).scrollIntoView({
344
+ behavior: 'smooth'
345
+ });
346
+ });
347
+ });
348
+
349
+ // Copy to clipboard function
350
+ function copyCode(button) {
351
+ const codeBlock = button.nextElementSibling.querySelector('code');
352
+ const textToCopy = codeBlock.innerText;
353
+
354
+ // Clipboard API fallback
355
+ const textArea = document.createElement("textarea");
356
+ textArea.value = textToCopy;
357
+ document.body.appendChild(textArea);
358
+ textArea.select();
359
+
360
+ try {
361
+ document.execCommand('copy');
362
+ const originalText = button.innerText;
363
+ button.innerText = 'Kopiert!';
364
+ button.classList.add('bg-green-600');
365
+
366
+ setTimeout(() => {
367
+ button.innerText = originalText;
368
+ button.classList.remove('bg-green-600');
369
+ }, 2000);
370
+ } catch (err) {
371
+ console.error('Kopieren fehlgeschlagen', err);
372
+ }
373
+
374
+ document.body.removeChild(textArea);
375
+ }
376
+ </script>
377
+ </body>
378
+ </html>