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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +268 -214
index.html CHANGED
@@ -3,31 +3,42 @@
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
  }
@@ -35,95 +46,121 @@
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>
@@ -131,47 +168,66 @@
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
@@ -182,139 +238,129 @@ class RecipeStructure(BaseModel):
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>
@@ -338,8 +384,17 @@ class StableDiffusionGenerator:
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
  });
@@ -351,7 +406,6 @@ class StableDiffusionGenerator:
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);
@@ -360,12 +414,12 @@ class StableDiffusionGenerator:
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);
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Konzept eines MAS mit Haystack 2.0</title>
7
 
8
  <!-- Tailwind CSS -->
9
  <script src="https://cdn.tailwindcss.com"></script>
10
 
11
+ <!-- Highlight.js for Code Formatting (Atom One Dark is close to HF's dark code blocks) -->
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
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
17
 
18
+ <!-- Google Fonts (Inter & Source Code Pro for HF feel) -->
19
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Source+Code+Pro:wght@400;500&display=swap" rel="stylesheet">
20
 
21
  <script>
22
  tailwind.config = {
23
  theme: {
24
  extend: {
25
  fontFamily: {
26
+ sans: ['Inter', 'ui-sans-serif', 'system-ui', 'sans-serif'],
27
+ mono: ['Source Code Pro', 'ui-monospace', 'SFMono-Regular', 'monospace'],
28
  },
29
  colors: {
30
+ hfYellow: {
31
+ DEFAULT: '#FFD21E',
32
+ hover: '#F5C518',
33
+ light: '#FFF5CC'
34
+ },
35
+ hfGray: {
36
+ 50: '#F9FAFB',
37
+ 100: '#F3F4F6',
38
+ 200: '#E5E7EB',
39
+ 800: '#1F2937',
40
+ 900: '#111827'
41
+ }
42
  }
43
  }
44
  }
 
46
  </script>
47
 
48
  <style>
49
+ body { font-family: 'Inter', sans-serif; background-color: #ffffff; color: #111827; }
50
+ pre code { font-family: 'Source Code Pro', monospace; border-radius: 0.75rem; font-size: 0.875rem; padding: 1.25rem; }
51
+ .prose h2 { margin-top: 3rem; margin-bottom: 1.25rem; font-weight: 700; font-size: 1.75rem; color: #111827; border-bottom: 1px solid #E5E7EB; padding-bottom: 0.5rem; display: flex; align-items: center; gap: 0.5rem; }
52
+ .prose h3 { margin-top: 2rem; margin-bottom: 1rem; font-weight: 600; font-size: 1.25rem; color: #374151; }
53
+ .prose p { margin-bottom: 1.25rem; line-height: 1.8; color: #4B5563; }
54
+ .prose table { width: 100%; border-collapse: collapse; margin-bottom: 2rem; border-radius: 0.5rem; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
55
+ .prose th, .prose td { border: 1px solid #E5E7EB; padding: 1rem; text-align: left; font-size: 0.95rem; }
56
+ .prose th { background-color: #F9FAFB; font-weight: 600; color: #111827; }
57
+
58
+ /* Tooltip / Copy Button */
59
+ .copy-btn { position: absolute; top: 0.75rem; right: 0.75rem; background: rgba(255,255,255,0.1); color: #D1D5DB; border: 1px solid rgba(255,255,255,0.2); padding: 0.25rem 0.75rem; border-radius: 0.375rem; cursor: pointer; font-size: 0.75rem; transition: all 0.2s; }
60
+ .copy-btn:hover { background: rgba(255,255,255,0.2); color: #fff; }
61
+ .code-wrapper { position: relative; margin-bottom: 2rem; border-radius: 0.75rem; background: #282c34; }
62
+ .file-label { position: absolute; top: -12px; left: 16px; background: #FFD21E; color: #111827; padding: 0.1rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; z-index: 10; font-family: 'Source Code Pro', monospace; border: 2px solid #fff; }
63
+
64
+ /* Custom Scrollbar for Sidebar */
65
+ #sidebar::-webkit-scrollbar { width: 6px; }
66
+ #sidebar::-webkit-scrollbar-track { background: transparent; }
67
+ #sidebar::-webkit-scrollbar-thumb { background-color: #E5E7EB; border-radius: 20px; }
68
  </style>
69
  </head>
70
+ <body class="flex flex-col md:flex-row min-h-screen selection:bg-hfYellow selection:text-black">
71
 
72
  <!-- Mobile Header -->
73
+ <div class="md:hidden bg-white border-b border-gray-200 p-4 flex justify-between items-center sticky top-0 z-50">
74
+ <div class="flex items-center gap-2">
75
+ <span class="text-2xl">🤗</span>
76
+ <h1 class="font-bold text-lg text-gray-900">MAS Konzept</h1>
77
+ </div>
78
+ <button id="mobile-menu-btn" class="text-gray-600 focus:outline-none hover:bg-gray-100 p-2 rounded-lg">
79
  <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>
80
  </button>
81
  </div>
82
 
83
  <!-- Sidebar Navigation -->
84
+ <nav id="sidebar" class="hidden md:flex flex-col w-full md:w-72 bg-gray-50 border-r border-gray-200 h-screen sticky top-0 overflow-y-auto shrink-0 transition-all duration-300 z-40">
85
+ <div class="p-6 hidden md:flex flex-col gap-2">
86
+ <div class="flex items-center gap-3">
87
+ <span class="text-3xl">🤗</span>
88
+ <h1 class="text-xl font-bold text-gray-900 tracking-tight leading-tight">Konzept eines MAS<br><span class="text-sm font-normal text-gray-500">mit Haystack 2.0</span></h1>
89
+ </div>
90
+ </div>
91
+
92
+ <div class="px-4 pb-4">
93
+ <div class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2 ml-3 mt-4">Architektur</div>
94
+ <ul class="space-y-1 text-sm font-medium">
95
+ <li><a href="#intro" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">1. Das Konzept & Framework</a></li>
96
+ <li><a href="#hf-infra" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">2. Hugging Face Inference</a></li>
97
+ </ul>
98
+
99
+ <div class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2 ml-3 mt-6">Die Agenten (Pipelines)</div>
100
+ <ul class="space-y-1 text-sm font-medium">
101
+ <li><a href="#agent1" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">3. Agent 1: Forschung & Suche</a></li>
102
+ <li><a href="#agent2" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">4. Agent 2: Struktur & Pydantic</a></li>
103
+ <li><a href="#agent3" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">5. Agent 3: Visionär (Bildgen.)</a></li>
104
+ <li><a href="#agent4" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">6. Agent 4: Meta-Aggregator</a></li>
105
+ </ul>
106
+
107
+ <div class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2 ml-3 mt-6">Orchestrierung & Code</div>
108
+ <ul class="space-y-1 text-sm font-medium">
109
+ <li><a href="#orchestration" class="block py-2 px-3 text-gray-600 rounded-lg hover:bg-gray-200 hover:text-gray-900 transition">7. Routing & Tool-Calling</a></li>
110
+ <li><a href="#repo" class="flex items-center gap-2 py-2 px-3 bg-hfYellow/20 text-yellow-800 rounded-lg hover:bg-hfYellow/30 transition">
111
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg>
112
+ 8. Repositorystruktur & main.py
113
+ </a></li>
114
+ </ul>
115
  </div>
 
 
 
 
 
 
 
 
 
116
  </nav>
117
 
118
  <!-- Main Content -->
119
+ <main class="flex-1 w-full max-w-4xl mx-auto p-6 md:p-10 lg:p-14">
120
  <div class="prose max-w-none">
121
 
122
  <!-- Hero -->
123
+ <div class="bg-hfYellow/10 border border-hfYellow rounded-2xl p-8 mb-12 shadow-sm">
124
+ <div class="flex items-center gap-3 mb-4">
125
+ <span class="text-4xl">🥘</span>
126
+ <h1 class="text-3xl font-bold text-gray-900 m-0">Architektur eines Kulinarischen MAS</h1>
127
+ </div>
128
+ <p class="text-gray-700 text-lg leading-relaxed m-0">Ein Konzept zur Orchestrierung von Multi-Agenten-Systemen (MAS) für <strong>"The Infinite Cookbook"</strong>. Wir zerlegen komplexe Aufgaben in spezialisierte Haystack 2.0 Pipelines und verbinden sie über die Hugging Face Infrastruktur (Open-Source LLMs) zu einem autonomen System.</p>
129
  </div>
130
 
131
  <section id="intro">
132
+ <h2><span>🏗️</span> 1. Das Konzept: Haystack 2.0</h2>
133
+ <p>Der Paradigmenwechsel: Ein einzelnes LLM scheitert oft an komplexen, mehrstufigen Aufgaben. Unser Konzept setzt auf <strong>Komponenten und Pipelines als dynamische Rechengraphen</strong>. Ein Agent in diesem Ökosystem ist eine autonome Einheit (z.B. ein spezialisiertes LLM + Tools), die Aufgaben plant, Werkzeuge auswählt und Strategien anpasst.</p>
 
134
  </section>
135
 
136
  <section id="hf-infra">
137
+ <h2><span>🤗</span> 2. Hugging Face Inference Provider</h2>
138
+ <p>Anstatt auf proprietäre Blackbox-Modelle zu setzen, nutzt dieses Konzept den Hugging Face Hub. Die <code>HuggingFaceAPIChatGenerator</code>-Komponente von Haystack ermöglicht nahtloses Wechseln zwischen Providern:</p>
139
 
140
  <div class="overflow-x-auto">
141
  <table>
142
  <thead>
143
  <tr>
144
+ <th>Infrastruktur</th>
145
  <th>Charakteristika</th>
146
+ <th>Modell-Beispiel im MAS</th>
147
  </tr>
148
  </thead>
149
  <tbody>
150
  <tr>
151
+ <td><strong>Serverless API</strong></td>
152
+ <td>Kostenlos, für Prototyping</td>
153
+ <td class="font-mono text-sm">Qwen/Qwen2.5-72B-Instruct</td>
154
  </tr>
155
  <tr>
156
  <td><strong>Inference Endpoints</strong></td>
157
+ <td>Dediziert, garantierte Latenz</td>
158
+ <td class="font-mono text-sm">black-forest-labs/FLUX.1-schnell</td>
 
 
 
 
 
159
  </tr>
160
  <tr>
161
+ <td><strong>TGI (Text Generation)</strong></td>
162
+ <td>Unterstützt JSON-Guiding</td>
163
+ <td class="font-mono text-sm">mistralai/Mistral-7B-Instruct</td>
164
  </tr>
165
  </tbody>
166
  </table>
 
168
  </section>
169
 
170
  <section id="agent1">
171
+ <h2><span>🔍</span> 3. Agent 1: Forschung & Suche</h2>
172
+ <p>Dieser Sub-Agent kapselt die Recherche. Er nutzt Web-Suchen, lädt HTML-Inhalte herunter, bereinigt sie und rankt sie. Das Ergebnis ist eine fokussierte Wissensbasis.</p>
173
+ <div class="code-wrapper mt-4">
174
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
 
175
  <pre><code class="language-python">from haystack import Pipeline
176
  from haystack.components.websearch import SerperDevWebSearch
177
+ # ... imports
 
 
 
 
178
  research_pipe = Pipeline()
179
+ research_pipe.add_component("search", SerperDevWebSearch(api_key="KEY"))
180
+ research_pipe.add_component("ranker", TransformersSimilarityRanker())
181
+ # ... verbindungen</code></pre>
182
+ </div>
183
+ </section>
184
+
185
+ <section id="agent2">
186
+ <h2><span>📋</span> 4. Agent 2: Struktur & Pydantic</h2>
187
+ <p>Um die unstrukturierten Forschungsnotizen maschinenlesbar zu machen, zwingen wir das LLM mittels <strong>Pydantic</strong> in ein striktes JSON-Schema. Das eliminiert Halluzinationen in der Datenstruktur.</p>
188
+ </section>
189
 
190
+ <section id="agent3">
191
+ <h2><span>🎨</span> 5. Agent 3: Visionär (Bildgen.)</h2>
192
+ <p>Da Sprachmodelle keine Bilder malen können, bauen wir eine eigene Custom Component in Haystack (<code>@component</code>), die via <code>diffusers</code> Bibliothek ein Stable Diffusion oder FLUX Modell ansteuert und als Base64-String zurückgibt.</p>
193
+ </section>
 
194
 
195
+ <section id="agent4">
196
+ <h2><span>💻</span> 6. Agent 4: Meta-Aggregator (HTML)</h2>
197
+ <p>Dieser Agent nutzt Jinja2-Templates, um die JSON-Daten (aus Agent 2) und den Base64-Bild-String (aus Agent 3) in eine fertige HTML-Rezeptkarte zu gießen.</p>
198
+ </section>
199
 
200
+ <section id="orchestration">
201
+ <h2><span>🚦</span> 7. Orchestrierung: Agent-as-a-Tool</h2>
202
+ <p>Die Magie passiert im Koordinator-Agenten. Anstatt alle Pipelines händisch zu verketten, packen wir jede Pipeline in ein Haystack <code>Tool</code>-Objekt. Ein übergeordnetes LLM entscheidet dann dynamisch, welches Tool aufgerufen werden muss.</p>
 
203
  </section>
204
 
205
+ <!-- KAPITEL 8: REPOSITORY UND CODE -->
206
+ <section id="repo">
207
+ <h2 class="bg-hfYellow/20 -mx-6 px-6 py-4 rounded-xl text-yellow-900 border-l-4 border-hfYellow">
208
+ <span>📁</span> 8. Die Code-Struktur (Vom Konzept zur Realität)
209
+ </h2>
210
+ <p>Ein Monolith (alles in einem Skript) führt bei MAS unweigerlich ins Chaos. Eine saubere Repository-Struktur spiegelt die Modularität des Konzepts wider. Hier siehst du, wie die konkreten Dateien in einem echten Projekt aussehen.</p>
211
 
212
+ <div class="bg-gray-50 border border-gray-200 rounded-lg p-4 mb-8 font-mono text-sm text-gray-700 leading-relaxed shadow-sm">
213
+ infinite-cookbook/<br>
214
+ ├── <span class="font-bold text-gray-900">schemas/</span><br>
215
+ │ └── <a href="#file-schema" class="text-blue-600 hover:underline">recipe.py</a> <span class="text-gray-400 italic"># Pydantic Modelle</span><br>
216
+ ├── <span class="font-bold text-gray-900">agents/</span><br>
217
+ │ └── <a href="#file-researcher" class="text-blue-600 hover:underline">researcher.py</a> <span class="text-gray-400 italic"># Pipeline Definitionen</span><br>
218
+ ├── <span class="font-bold text-gray-900">tools/</span><br>
219
+ │ └── <a href="#file-tools" class="text-blue-600 hover:underline">agent_tools.py</a> <span class="text-gray-400 italic"># Wrapper: Pipeline -> Tool</span><br>
220
+ └── <a href="#file-main" class="text-blue-600 hover:underline font-bold">main.py</a> <span class="text-gray-400 italic"># Orchestrierungs-Loop</span>
221
+ </div>
222
+
223
+ <h3 id="file-schema" class="flex items-center gap-2 mt-10"><svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg> 1. Datenschemata</h3>
224
+ <p>Wir definieren zentral, wie unser Endprodukt auszusehen hat.</p>
225
+ <div class="code-wrapper mt-4">
226
+ <div class="file-label">schemas/recipe.py</div>
227
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
228
  <pre><code class="language-python">from pydantic import BaseModel
229
+ from typing import List, Literal
230
 
 
231
  class Ingredient(BaseModel):
232
  name: str
233
  amount: float
 
238
  servings: int
239
  ingredients: List[Ingredient]
240
  instructions: List[str]
241
+ difficulty: Literal["Einfach", "Mittel", "Schwer"]</code></pre>
 
 
 
 
 
 
 
 
242
  </div>
 
 
243
 
244
+ <h3 id="file-researcher" class="flex items-center gap-2 mt-10"><svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path></svg> 2. Pipeline Module</h3>
245
+ <p>Anstatt Pipelines global zu instanziieren, verpacken wir sie in Funktionen (Factory-Pattern). So bleiben sie testbar.</p>
246
+ <div class="code-wrapper mt-4">
247
+ <div class="file-label">agents/researcher.py</div>
248
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
249
+ <pre><code class="language-python">from haystack import Pipeline
250
+ from haystack.components.websearch import SerperDevWebSearch
251
+ from haystack.components.fetchers import LinkContentFetcher
252
+ from haystack.components.converters import HTMLToDocument
253
+
254
+ def build_research_pipeline() -> Pipeline:
255
+ """Baut den Graphen für die Web-Recherche."""
256
+ pipe = Pipeline()
257
+
258
+ # Komponenten hinzufügen
259
+ pipe.add_component("search", SerperDevWebSearch())
260
+ pipe.add_component("fetcher", LinkContentFetcher())
261
+ pipe.add_component("html_converter", HTMLToDocument())
262
+
263
+ # Kanten (Edges) verbinden
264
+ pipe.connect("search.links", "fetcher.urls")
265
+ pipe.connect("fetcher.streams", "html_converter.sources")
266
+
267
+ return pipe</code></pre>
 
 
 
 
268
  </div>
 
269
 
270
+ <h3 id="file-tools" class="flex items-center gap-2 mt-10"><svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg> 3. Die Werkzeugkiste (Tool-Wrapper)</h3>
271
+ <p>Hier importieren wir die Pipelines und machen sie für das Koordinator-LLM "lesbar", indem wir sie mit Beschreibungen und Schemata versehen.</p>
272
+ <div class="code-wrapper mt-4">
273
+ <div class="file-label">tools/agent_tools.py</div>
274
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
275
+ <pre><code class="language-python">from haystack.tools import Tool
276
+ from agents.researcher import build_research_pipeline
277
+ # from agents.structurer import build_structuring_pipeline
278
+
279
+ # 1. Pipelines instanziieren
280
+ research_pipe = build_research_pipeline()
281
+ # structurer_pipe = build_structuring_pipeline()
282
+
283
+ # 2. In Tools kapseln
284
+ research_tool = Tool(
285
+ name="research_tool",
286
+ description="Nutze dieses Tool ZUERST. Durchsucht das Web nach authentischen Rezept-Infos.",
287
+ parameters={
288
+ "type": "object",
289
+ "properties": {
290
+ "query": {"type": "string", "description": "Der Suchbegriff, z.B. 'original Pad Thai'"}
291
+ },
292
+ "required": ["query"]
293
+ },
294
+ function=research_pipe.run
295
+ )
296
+
297
+ # Tool-Liste für den Haupt-Agenten exportieren
298
+ cookbook_tools = [research_tool] # + structurer_tool, image_tool, html_tool</code></pre>
299
+ </div>
300
+
301
+ <h3 id="file-main" class="flex items-center gap-2 mt-10"><svg class="w-5 h-5 text-hfYellow" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg> 4. Das Herzstück: main.py</h3>
302
+ <p>Die <code>main.py</code> ist nun unglaublich aufgeräumt. Sie initialisiert lediglich das Haupt-LLM (über Hugging Face), importiert die fertige Werkzeugkiste und startet die Interaktions-Schleife.</p>
303
+ <div class="code-wrapper mt-4">
304
+ <div class="file-label">main.py</div>
305
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
306
+ <pre><code class="language-python">import os
307
+ from haystack import Pipeline
308
+ from haystack.components.generators.chat import HuggingFaceAPIChatGenerator
309
+ from haystack.components.tools import ToolInvoker
310
+ from haystack.dataclasses import ChatMessage
311
+
312
+ # Unsere sauberen, modularen Imports!
313
+ from tools.agent_tools import cookbook_tools
314
+
315
+ def main():
316
+ # 1. Der Koordinator: Ein starkes Modell, das Tool-Calling beherrscht
317
+ llm = HuggingFaceAPIChatGenerator(
318
+ api_type="serverless_inference_api",
319
+ model="Qwen/Qwen2.5-72B-Instruct",
320
+ tools=cookbook_tools
321
+ )
322
 
323
+ # 2. Der Invoker: Führt die vom LLM gewählten Tools aus
324
+ invoker = ToolInvoker(tools=cookbook_tools)
325
+
326
+ # 3. Der Routing-Graph
327
+ coordinator = Pipeline()
328
+ coordinator.add_component("llm", llm)
329
+ coordinator.add_component("invoker", invoker)
330
 
331
+ # Zyklische Verbindung für iteratives Arbeiten
332
+ coordinator.connect("llm.replies", "invoker.messages")
333
+ coordinator.connect("invoker.tool_messages", "llm.messages")
334
+
335
+ # 4. System Start
336
+ user_prompt = "Erstelle ein Rezept für ein thailändisches grünes Curry samt HTML und Bild."
337
+ messages = [ChatMessage.from_user(user_prompt)]
338
 
339
+ print(f"🤖 Koordinator startet Aufgabe: {user_prompt}")
 
 
 
 
 
340
 
341
+ # Run Loop (Vereinfachte Ausführung)
342
+ result = coordinator.run({"llm": {"messages": messages}})
343
+
344
+ # Im echten MAS läuft dies in einer Schleife (ConditionalRouter),
345
+ # bis das LLM entscheidet: "Ich bin fertig und nutze kein Tool mehr."
346
+ print("Fertiges Ergebnis:", result["llm"]["replies"][0].text)
 
 
 
347
 
348
+ if __name__ == "__main__":
349
+ main()</code></pre>
350
+ </div>
 
 
 
 
 
 
 
 
 
351
 
352
+ <div class="mt-8 p-5 bg-blue-50/50 rounded-xl text-sm text-gray-700 border border-blue-100 flex items-start gap-4 shadow-sm">
353
+ <span class="text-2xl">💡</span>
354
+ <div>
355
+ <strong class="text-gray-900 block mb-1">Die Architektur-Philosophie:</strong>
356
+ Diese Aufteilung erlaubt es dir, die <code>researcher.py</code> komplett umzuschreiben oder das Modell in <code>main.py</code> auszutauschen, ohne dass du Angst haben musst, den Code des Bild-Generators kaputtzumachen. Das ist die wahre Stärke von Haystack 2.0 in Kombination mit einem modularen Setup!
357
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  </div>
359
 
 
 
 
360
  </section>
361
 
362
+ <footer class="mt-16 pt-8 border-t border-gray-200 text-sm text-gray-400 text-center">
363
+ <p>Ein konzeptioneller Guide für Multi-Agenten-Systeme.<br>Technologie-Stack: Haystack 2.0 & Hugging Face.</p>
364
  </footer>
365
  </div>
366
  </main>
 
384
  anchor.addEventListener('click', function (e) {
385
  e.preventDefault();
386
  if(window.innerWidth < 768) {
387
+ sidebar.classList.add('hidden');
388
  }
389
+
390
+ // Active State Styling Update
391
+ document.querySelectorAll('#sidebar a').forEach(a => {
392
+ a.classList.remove('bg-hfYellow/20', 'text-yellow-800', 'font-semibold');
393
+ a.classList.add('text-gray-600');
394
+ });
395
+ this.classList.remove('text-gray-600');
396
+ this.classList.add('bg-hfYellow/20', 'text-yellow-800', 'font-semibold');
397
+
398
  document.querySelector(this.getAttribute('href')).scrollIntoView({
399
  behavior: 'smooth'
400
  });
 
406
  const codeBlock = button.nextElementSibling.querySelector('code');
407
  const textToCopy = codeBlock.innerText;
408
 
 
409
  const textArea = document.createElement("textarea");
410
  textArea.value = textToCopy;
411
  document.body.appendChild(textArea);
 
414
  try {
415
  document.execCommand('copy');
416
  const originalText = button.innerText;
417
+ button.innerText = 'Copied!';
418
+ button.classList.add('bg-green-600', 'text-white', 'border-green-600');
419
 
420
  setTimeout(() => {
421
  button.innerText = originalText;
422
+ button.classList.remove('bg-green-600', 'text-white', 'border-green-600');
423
  }, 2000);
424
  } catch (err) {
425
  console.error('Kopieren fehlgeschlagen', err);