jmp1987 commited on
Commit
b6a8167
·
verified ·
1 Parent(s): 65a98c5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +142 -171
index.html CHANGED
@@ -22,18 +22,17 @@
22
  .btn-generate{background:linear-gradient(90deg,#f7971e,#ffd200);color:#000}
23
  .btn-generate:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(255,210,0,.3)}
24
  .btn-generate:disabled{opacity:.5;cursor:not-allowed;transform:none}
25
- .btn-download{background:linear-gradient(90deg,#11998e,#38ef7d);color:#000;margin-top:12px}
26
- .btn-download:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(56,239,125,.3)}
27
  .progress-wrap{margin:20px 0;display:none}
28
- .progress-bar{height:6px;border-radius:3px;background:rgba(255,255,255,.1);overflow:hidden}
29
- .progress-fill{height:100%;background:linear-gradient(90deg,#f7971e,#ffd200);border-radius:3px;width:0%;transition:width .4s}
30
- .status{text-align:center;margin-top:10px;font-size:.9rem;color:#ffd200}
31
- .preview-content{background:rgba(255,255,255,.05);border-radius:12px;padding:20px;max-height:400px;overflow-y:auto;font-size:.85rem;line-height:1.7;color:#ddd;white-space:pre-wrap;font-family:Georgia,serif;border:1px solid rgba(255,255,255,.08)}
32
  .chapter-list{list-style:none;padding:0}
33
  .chapter-list li{padding:8px 12px;border-left:3px solid #ffd200;margin-bottom:8px;background:rgba(255,210,0,.05);border-radius:0 8px 8px 0;font-size:.9rem;color:#eee}
34
  .tag{display:inline-block;background:rgba(255,210,0,.15);border:1px solid rgba(255,210,0,.3);color:#ffd200;padding:3px 10px;border-radius:20px;font-size:.75rem;margin:3px;cursor:pointer}
35
  .tag:hover{background:rgba(255,210,0,.3)}
36
- .api-note{text-align:center;font-size:.78rem;color:#888;margin-top:8px}
37
  .sections-info{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:16px}
38
  .stat-box{background:rgba(255,255,255,.05);border-radius:10px;padding:12px;text-align:center}
39
  .stat-box .val{font-size:1.4rem;font-weight:700;color:#ffd200}
@@ -41,35 +40,35 @@
41
  .tabs{display:flex;gap:8px;margin-bottom:16px}
42
  .tab{padding:8px 18px;border-radius:8px;cursor:pointer;font-size:.85rem;border:1px solid rgba(255,255,255,.15);color:#aaa;background:transparent}
43
  .tab.active{background:rgba(255,210,0,.15);border-color:#ffd200;color:#ffd200}
44
- .error{color:#ff6b6b;font-size:.85rem;margin-top:8px;padding:10px;background:rgba(255,107,107,.1);border-radius:8px;border:1px solid rgba(255,107,107,.3);display:none}
45
- .model-info{font-size:.75rem;color:#888;text-align:center;margin-top:6px}
46
  </style>
47
  </head>
48
  <body>
49
  <div class="container">
50
  <h1>📚 KDP eBook Generator</h1>
51
- <p class="subtitle">Erstelle in Sekunden ein Amazon KDP-fertiges eBook mit einem Klick</p>
52
 
53
- <div class="card" id="inputCard">
54
  <div style="margin-bottom:20px">
55
- <label>📖 Thema / Titel des eBooks</label>
56
- <input type="text" id="topic" placeholder="z.B. Stoizismus für Anfänger, Ketogene Ernährung, Python lernen..."/>
57
  <div style="margin-top:8px">
58
  <span class="tag" onclick="setTopic('Stoizismus für modernes Leben')">Stoizismus</span>
59
- <span class="tag" onclick="setTopic('Abnehmen ohne Diät – 10 wissenschaftliche Strategien')">Abnehmen</span>
60
  <span class="tag" onclick="setTopic('Passives Einkommen mit KI')">Passives Einkommen</span>
61
  <span class="tag" onclick="setTopic('Meditation für Anfänger in 21 Tagen')">Meditation</span>
62
  <span class="tag" onclick="setTopic('Kryptowährungen einfach erklärt')">Krypto</span>
63
- <span class="tag" onclick="setTopic('Produktivität steigern – Deep Work Methoden')">Produktivität</span>
64
  </div>
65
  </div>
66
  <div style="margin-bottom:20px">
67
  <label>🎯 Zielgruppe & Kontext</label>
68
- <textarea id="context" placeholder="z.B. Für gestresste Berufstätige zwischen 30–50, die mehr Gelassenheit im Alltag suchen..."></textarea>
69
  </div>
70
  <div class="row" style="margin-bottom:20px">
71
  <div>
72
- <label>📝 Anzahl Kapitel</label>
73
  <select id="chapters">
74
  <option value="5">5 Kapitel</option>
75
  <option value="7" selected>7 Kapitel</option>
@@ -92,9 +91,9 @@
92
  <select id="style">
93
  <option value="informative and clear">Informativ & Klar</option>
94
  <option value="motivating and inspiring" selected>Motivierend</option>
95
- <option value="scientific and evidence-based">Wissenschaftlich</option>
96
  <option value="casual and humorous">Locker & Humorvoll</option>
97
- <option value="storytelling-based">Storytelling</option>
98
  </select>
99
  </div>
100
  <div>
@@ -102,219 +101,191 @@
102
  <input type="password" id="apiToken" placeholder="hf_xxxxxxxxxxxxxxxx"/>
103
  </div>
104
  </div>
105
- <p class="api-note">🔑 Kostenlosen Token auf <a href="https://huggingface.co/settings/tokens" target="_blank" style="color:#ffd200">huggingface.co/settings/tokens</a> erstellen (Write oder Read)</p>
106
- <p class="model-info">Modell: mistralai/Mistral-7B-Instruct-v0.3 via HF Inference API</p>
107
  <div id="errorMsg" class="error"></div>
108
  </div>
109
 
110
- <button class="btn btn-generate" id="generateBtn" onclick="generateEbook()">⚡ eBook jetzt generieren</button>
111
 
112
- <div class="progress-wrap" id="progressWrap">
113
- <div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
114
- <div class="status" id="statusText">Initialisiere...</div>
115
  </div>
116
 
117
  <div id="resultCard" style="display:none" class="card">
118
- <h2 style="margin-bottom:16px;color:#ffd200;font-size:1.3rem">✅ eBook erstellt!</h2>
119
  <div class="sections-info">
120
- <div class="stat-box"><div class="val" id="statWords">0</div><div class="lbl">Wörter</div></div>
121
- <div class="stat-box"><div class="val" id="statChapters">0</div><div class="lbl">Kapitel</div></div>
122
- <div class="stat-box"><div class="val" id="statPages">~0</div><div class="lbl">KDP-Seiten</div></div>
123
  </div>
124
  <div style="margin-top:20px">
125
  <div class="tabs">
126
- <div class="tab active" id="tabOutline" onclick="showTab('outline')">📋 Inhaltsverzeichnis</div>
127
- <div class="tab" id="tabPreview" onclick="showTab('preview')">👁️ Vorschau</div>
128
  </div>
129
- <div id="outlineTab"><ul class="chapter-list" id="chapterList"></ul></div>
130
- <div id="previewTab" style="display:none"><div class="preview-content" id="previewContent"></div></div>
131
  </div>
132
- <button class="btn btn-download" onclick="downloadHTML()">📥 HTML eBook herunterladen (KDP-fertig)</button>
133
- <button class="btn btn-download" onclick="downloadTXT()" style="background:linear-gradient(90deg,#667eea,#764ba2);margin-top:8px">📄 Text (.txt) herunterladen</button>
134
  </div>
135
  </div>
136
 
137
- <script>
 
 
138
  let ebookData = null;
 
139
 
140
- function setTopic(t){ document.getElementById('topic').value = t; }
 
 
 
 
 
141
 
142
- function showTab(tab){
143
- document.getElementById('outlineTab').style.display = tab==='outline'?'block':'none';
144
- document.getElementById('previewTab').style.display = tab==='preview'?'block':'none';
145
- document.getElementById('tabOutline').className = 'tab' + (tab==='outline'?' active':'');
146
- document.getElementById('tabPreview').className = 'tab' + (tab==='preview'?' active':'');
147
- }
148
 
149
- function setProgress(pct, msg){
150
- document.getElementById('progressFill').style.width = pct+'%';
151
- document.getElementById('statusText').textContent = msg;
152
- }
153
-
154
- function showError(msg){
155
- const el = document.getElementById('errorMsg');
156
- el.textContent = '❌ ' + msg;
157
- el.style.display = 'block';
158
- }
159
 
160
- // HuggingFace Inference API - correct endpoint
161
- async function callHF(token, systemPrompt, userPrompt) {
162
- const model = 'mistralai/Mistral-7B-Instruct-v0.3';
163
- const url = 'https://api-inference.huggingface.co/models/' + model + '/v1/chat/completions';
164
-
165
- const response = await fetch(url, {
166
- method: 'POST',
167
- headers: {
168
- 'Authorization': 'Bearer ' + token,
169
- 'Content-Type': 'application/json'
170
- },
171
- body: JSON.stringify({
172
- model: model,
173
- messages: [
174
- { role: 'system', content: systemPrompt },
175
- { role: 'user', content: userPrompt }
176
- ],
177
- max_tokens: 1500,
178
- temperature: 0.7,
179
- stream: false
180
- })
181
  });
182
-
183
- if (!response.ok) {
184
- const errText = await response.text();
185
- let errMsg = 'HTTP ' + response.status;
186
- try {
187
- const errJson = JSON.parse(errText);
188
- errMsg = errJson.error || errMsg;
189
- } catch(e) {}
190
- throw new Error(errMsg);
191
- }
192
-
193
- const data = await response.json();
194
- return data.choices[0].message.content.trim();
195
  }
196
 
197
- async function generateEbook(){
198
- const topic = document.getElementById('topic').value.trim();
199
  const context = document.getElementById('context').value.trim();
200
- const numChapters = parseInt(document.getElementById('chapters').value);
201
- const language = document.getElementById('language').value;
202
- const style = document.getElementById('style').value;
203
- const token = document.getElementById('apiToken').value.trim();
204
- const errEl = document.getElementById('errorMsg');
205
  errEl.style.display = 'none';
206
 
207
- if(!topic){ showError('Bitte gib ein Thema ein.'); return; }
208
- if(!token){ showError('Bitte gib deinen HuggingFace API Token ein.'); return; }
209
 
210
- document.getElementById('generateBtn').disabled = true;
211
- document.getElementById('progressWrap').style.display = 'block';
212
  document.getElementById('resultCard').style.display = 'none';
213
 
214
  try {
215
- // Step 1: Generate outline
216
- setProgress(5, '📋 Erstelle Kapitelstruktur...');
217
-
218
- const systemPrompt = 'You are a professional book author. Always respond in ' + language + '. Be concise and structured.';
219
-
220
- const outlineResult = await callHF(token, systemPrompt,
221
- 'Create a numbered list of exactly ' + numChapters + ' chapter titles for an eBook about: "' + topic + '". Target audience: ' + (context || 'general readers') + '. Writing style: ' + style + '. Output ONLY the numbered list, nothing else. Example format:\n1. Chapter Title\n2. Chapter Title'
222
- );
223
 
224
- setProgress(15, ' Kapitelstruktur erstellt');
 
 
 
 
 
225
 
226
- // Parse chapter titles
227
- const lines = outlineResult.split('\n').filter(l => /^\d+[\.\)\:]/.test(l.trim()));
228
- let chapterTitles = lines.map(l => l.replace(/^\d+[\.\)\:]\s*/, '').trim()).filter(t => t.length > 2);
229
-
230
- if(chapterTitles.length < 2){
231
- // Fallback: try splitting by newline and take non-empty lines
232
- const fallback = outlineResult.split('\n').map(l=>l.trim()).filter(l=>l.length>5 && !l.startsWith('#'));
233
- chapterTitles = fallback.slice(0, numChapters);
234
  }
235
- if(chapterTitles.length < 2) throw new Error('Kapitelstruktur konnte nicht generiert werden. Versuche es erneut.');
236
- chapterTitles = chapterTitles.slice(0, numChapters);
 
237
 
238
- // Step 2: Generate chapter content
239
  const chapters = [];
240
- for(let i = 0; i < chapterTitles.length; i++){
241
- const pct = 15 + Math.round((i + 1) / chapterTitles.length * 78);
242
- setProgress(pct, '✍️ Schreibe Kapitel ' + (i+1) + '/' + chapterTitles.length + ': ' + chapterTitles[i].substring(0,35) + '...');
243
-
244
- const content = await callHF(token, systemPrompt,
245
- 'Write chapter ' + (i+1) + ' of an eBook titled "' + topic + '".' +
246
- '\nChapter title: "' + chapterTitles[i] + '"' +
247
- '\nTarget audience: ' + (context || 'general readers') +
248
- '\nStyle: ' + style +
249
- '\nWrite at least 350 words of actual content. Use paragraphs. No placeholder text. Start writing directly.'
250
  );
251
-
252
- chapters.push({ title: chapterTitles[i], content: content });
253
  }
254
 
255
- setProgress(98, '📚 Finalisiere eBook...');
256
-
257
- const intro = 'Dieses eBook wurde für ' + (context || 'interessierte Leserinnen und Leser') +
258
- ' verfasst und behandelt das Thema "' + topic + '" auf ' + style + 'e Weise.';
259
-
260
- ebookData = { topic, context, language, style, intro, chapters };
261
-
262
- const wordCount = chapters.reduce((s,c) => s + c.content.split(/\s+/).length, 0);
263
- document.getElementById('statWords').textContent = wordCount.toLocaleString('de-DE');
264
- document.getElementById('statChapters').textContent = chapters.length;
265
- document.getElementById('statPages').textContent = '~' + Math.round(wordCount / 250);
266
 
267
- document.getElementById('chapterList').innerHTML =
268
- chapters.map((c,i) => '<li>Kapitel ' + (i+1) + ': ' + c.title + '</li>').join('');
 
 
 
 
 
 
269
 
270
- document.getElementById('previewContent').textContent =
271
- topic + '\n' + '='.repeat(50) + '\n\n' + intro + '\n\n' +
272
- chapters.map((c,i) => 'Kapitel ' + (i+1) + ': ' + c.title + '\n' + '-'.repeat(40) + '\n' + c.content).join('\n\n');
273
-
274
- setProgress(100, '🎉 eBook fertig!');
275
  document.getElementById('resultCard').style.display = 'block';
276
 
277
  } catch(err) {
278
- showError(err.message);
279
- document.getElementById('progressWrap').style.display = 'none';
280
  } finally {
281
- document.getElementById('generateBtn').disabled = false;
282
  }
283
- }
284
 
285
- function buildHTMLEbook(){
286
- const { topic, language, style, intro, chapters } = ebookData;
287
- const langCode = {'German':'de','English':'en','Spanish':'es','French':'fr'}[language] || 'de';
288
- const toc = chapters.map((c,i) => '<li><a href="#ch'+(i+1)+'">Kapitel '+(i+1)+': '+c.title+'</a></li>').join('');
289
- const chHTML = chapters.map((c,i) =>
290
- '<div class="chapter" id="ch'+(i+1)+'">' +
291
- '<div class="ch-num">Kapitel '+(i+1)+'</div>' +
292
- '<h2>'+c.title+'</h2>' +
293
- c.content.split('\n\n').filter(p=>p.trim()).map(p=>'<p>'+p.replace(/\n/g,' ')+'</p>').join('') +
294
- '</div>'
295
  ).join('');
296
- return '<!DOCTYPE html>\n<html lang="'+langCode+'">\n<head>\n<meta charset="UTF-8"/>\n<title>'+topic+'</title>\n<style>\n@page{size:6in 9in;margin:1in}\nbody{font-family:Georgia,serif;font-size:11pt;line-height:1.8;color:#1a1a1a;max-width:580px;margin:0 auto;padding:40px 20px}\n.cover{text-align:center;padding:100px 40px;border-bottom:3px double #333;margin-bottom:60px;page-break-after:always}\n.cover h1{font-size:26pt;line-height:1.3;margin-bottom:20px}\n.toc{page-break-after:always}\n.toc h2{font-size:15pt;border-bottom:2px solid #333;padding-bottom:8px;margin-bottom:16px}\n.toc ul{list-style:none;padding:0}\n.toc li{padding:7px 0;border-bottom:1px dotted #ccc}\n.toc a{text-decoration:none;color:#222}\n.intro{font-style:italic;color:#555;border-left:4px solid #aaa;padding-left:16px;margin:30px 0 50px;page-break-after:always}\n.chapter{page-break-before:always;margin-bottom:40px}\n.ch-num{font-size:9pt;text-transform:uppercase;letter-spacing:3px;color:#999;margin-bottom:6px}\n.chapter h2{font-size:17pt;border-bottom:2px solid #222;padding-bottom:10px;margin-bottom:20px}\np{margin-bottom:14px;text-align:justify;text-indent:1.5em}\np:first-of-type{text-indent:0}\n</style>\n</head>\n<body>\n<div class="cover"><h1>'+topic+'</h1><p style="color:#666;margin-top:20px">'+language+' &bull; '+style+'</p><p style="color:#999;margin-top:10px">&copy; '+new Date().getFullYear()+'</p></div>\n<div class="toc"><h2>Inhaltsverzeichnis</h2><ul>'+toc+'</ul></div>\n<div class="intro"><p>'+intro+'</p></div>\n'+chHTML+'\n</body>\n</html>';
 
 
 
 
 
 
 
 
 
 
 
297
  }
298
 
299
- function downloadHTML(){
300
  if(!ebookData) return;
301
- const blob = new Blob([buildHTMLEbook()], {type:'text/html;charset=utf-8'});
302
  const a = document.createElement('a');
303
- a.href = URL.createObjectURL(blob);
304
  a.download = ebookData.topic.replace(/[^\w\säöüÄÖÜß]/g,'_').substring(0,50)+'_KDP.html';
305
  a.click();
306
- }
307
 
308
- function downloadTXT(){
309
  if(!ebookData) return;
310
  const txt = ebookData.topic+'\n'+'='.repeat(60)+'\n\n'+ebookData.intro+'\n\n'+
311
  ebookData.chapters.map((c,i)=>'KAPITEL '+(i+1)+': '+c.title.toUpperCase()+'\n'+'-'.repeat(60)+'\n'+c.content).join('\n\n');
312
- const blob = new Blob([txt], {type:'text/plain;charset=utf-8'});
313
  const a = document.createElement('a');
314
- a.href = URL.createObjectURL(blob);
315
  a.download = ebookData.topic.replace(/[^\w\säöüÄÖÜß]/g,'_').substring(0,50)+'_KDP.txt';
316
  a.click();
317
- }
318
  </script>
319
  </body>
320
  </html>
 
22
  .btn-generate{background:linear-gradient(90deg,#f7971e,#ffd200);color:#000}
23
  .btn-generate:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(255,210,0,.3)}
24
  .btn-generate:disabled{opacity:.5;cursor:not-allowed;transform:none}
25
+ .btn-dl{color:#000;margin-top:10px}
26
+ .btn-dl:hover{transform:translateY(-2px)}
27
  .progress-wrap{margin:20px 0;display:none}
28
+ .progress-bar{height:8px;border-radius:4px;background:rgba(255,255,255,.1);overflow:hidden}
29
+ .progress-fill{height:100%;background:linear-gradient(90deg,#f7971e,#ffd200);border-radius:4px;width:0%;transition:width .5s}
30
+ .status{text-align:center;margin-top:10px;font-size:.9rem;color:#ffd200;min-height:20px}
31
+ .preview-content{background:rgba(255,255,255,.05);border-radius:12px;padding:20px;max-height:380px;overflow-y:auto;font-size:.85rem;line-height:1.7;color:#ddd;white-space:pre-wrap;font-family:Georgia,serif;border:1px solid rgba(255,255,255,.08)}
32
  .chapter-list{list-style:none;padding:0}
33
  .chapter-list li{padding:8px 12px;border-left:3px solid #ffd200;margin-bottom:8px;background:rgba(255,210,0,.05);border-radius:0 8px 8px 0;font-size:.9rem;color:#eee}
34
  .tag{display:inline-block;background:rgba(255,210,0,.15);border:1px solid rgba(255,210,0,.3);color:#ffd200;padding:3px 10px;border-radius:20px;font-size:.75rem;margin:3px;cursor:pointer}
35
  .tag:hover{background:rgba(255,210,0,.3)}
 
36
  .sections-info{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:16px}
37
  .stat-box{background:rgba(255,255,255,.05);border-radius:10px;padding:12px;text-align:center}
38
  .stat-box .val{font-size:1.4rem;font-weight:700;color:#ffd200}
 
40
  .tabs{display:flex;gap:8px;margin-bottom:16px}
41
  .tab{padding:8px 18px;border-radius:8px;cursor:pointer;font-size:.85rem;border:1px solid rgba(255,255,255,.15);color:#aaa;background:transparent}
42
  .tab.active{background:rgba(255,210,0,.15);border-color:#ffd200;color:#ffd200}
43
+ .error{color:#ff6b6b;font-size:.85rem;margin-top:8px;padding:12px 16px;background:rgba(255,107,107,.1);border-radius:8px;border:1px solid rgba(255,107,107,.3);display:none;line-height:1.5}
44
+ .note{text-align:center;font-size:.78rem;color:#888;margin-top:6px}
45
  </style>
46
  </head>
47
  <body>
48
  <div class="container">
49
  <h1>📚 KDP eBook Generator</h1>
50
+ <p class="subtitle">Amazon KDP-fertiges eBook mit einem Klick erstellen</p>
51
 
52
+ <div class="card">
53
  <div style="margin-bottom:20px">
54
+ <label>📖 Thema / Titel</label>
55
+ <input type="text" id="topic" placeholder="z.B. Stoizismus für Anfänger, Ketogene Ernährung..."/>
56
  <div style="margin-top:8px">
57
  <span class="tag" onclick="setTopic('Stoizismus für modernes Leben')">Stoizismus</span>
58
+ <span class="tag" onclick="setTopic('Abnehmen ohne Diät – 10 Strategien')">Abnehmen</span>
59
  <span class="tag" onclick="setTopic('Passives Einkommen mit KI')">Passives Einkommen</span>
60
  <span class="tag" onclick="setTopic('Meditation für Anfänger in 21 Tagen')">Meditation</span>
61
  <span class="tag" onclick="setTopic('Kryptowährungen einfach erklärt')">Krypto</span>
62
+ <span class="tag" onclick="setTopic('Produktivität steigern – Deep Work')">Produktivität</span>
63
  </div>
64
  </div>
65
  <div style="margin-bottom:20px">
66
  <label>🎯 Zielgruppe & Kontext</label>
67
+ <textarea id="context" placeholder="z.B. Gestresste Berufstätige 30–50, praktischer Ratgeber-Stil..."></textarea>
68
  </div>
69
  <div class="row" style="margin-bottom:20px">
70
  <div>
71
+ <label>📝 Kapitel</label>
72
  <select id="chapters">
73
  <option value="5">5 Kapitel</option>
74
  <option value="7" selected>7 Kapitel</option>
 
91
  <select id="style">
92
  <option value="informative and clear">Informativ & Klar</option>
93
  <option value="motivating and inspiring" selected>Motivierend</option>
94
+ <option value="scientific">Wissenschaftlich</option>
95
  <option value="casual and humorous">Locker & Humorvoll</option>
96
+ <option value="storytelling">Storytelling</option>
97
  </select>
98
  </div>
99
  <div>
 
101
  <input type="password" id="apiToken" placeholder="hf_xxxxxxxxxxxxxxxx"/>
102
  </div>
103
  </div>
104
+ <p class="note">🔑 Token erstellen: <a href="https://huggingface.co/settings/tokens" target="_blank" style="color:#ffd200">huggingface.co/settings/tokens</a> (Write-Berechtigung)</p>
 
105
  <div id="errorMsg" class="error"></div>
106
  </div>
107
 
108
+ <button class="btn btn-generate" id="genBtn" onclick="generate()">⚡ eBook jetzt generieren</button>
109
 
110
+ <div class="progress-wrap" id="pw">
111
+ <div class="progress-bar"><div class="progress-fill" id="pf"></div></div>
112
+ <div class="status" id="st"></div>
113
  </div>
114
 
115
  <div id="resultCard" style="display:none" class="card">
116
+ <h2 style="color:#ffd200;font-size:1.3rem;margin-bottom:16px">✅ eBook fertig!</h2>
117
  <div class="sections-info">
118
+ <div class="stat-box"><div class="val" id="sW">0</div><div class="lbl">Wörter</div></div>
119
+ <div class="stat-box"><div class="val" id="sC">0</div><div class="lbl">Kapitel</div></div>
120
+ <div class="stat-box"><div class="val" id="sP">~0</div><div class="lbl">KDP-Seiten</div></div>
121
  </div>
122
  <div style="margin-top:20px">
123
  <div class="tabs">
124
+ <div class="tab active" id="t1" onclick="showTab('o')">📋 Inhalt</div>
125
+ <div class="tab" id="t2" onclick="showTab('p')">👁️ Vorschau</div>
126
  </div>
127
+ <div id="tabO"><ul class="chapter-list" id="cList"></ul></div>
128
+ <div id="tabP" style="display:none"><div class="preview-content" id="prev"></div></div>
129
  </div>
130
+ <button class="btn btn-dl" style="background:linear-gradient(90deg,#11998e,#38ef7d);margin-top:16px" onclick="dlHTML()">📥 HTML eBook (KDP-fertig)</button>
131
+ <button class="btn btn-dl" style="background:linear-gradient(90deg,#667eea,#764ba2)" onclick="dlTXT()">📄 Text (.txt)</button>
132
  </div>
133
  </div>
134
 
135
+ <script type="module">
136
+ import { InferenceClient } from 'https://esm.sh/@huggingface/inference@3';
137
+
138
  let ebookData = null;
139
+ window.setTopic = t => document.getElementById('topic').value = t;
140
 
141
+ window.showTab = tab => {
142
+ document.getElementById('tabO').style.display = tab==='o'?'block':'none';
143
+ document.getElementById('tabP').style.display = tab==='p'?'block':'none';
144
+ document.getElementById('t1').className = 'tab'+(tab==='o'?' active':'');
145
+ document.getElementById('t2').className = 'tab'+(tab==='p'?' active':'');
146
+ };
147
 
148
+ const prog = (p,m) => {
149
+ document.getElementById('pf').style.width = p+'%';
150
+ document.getElementById('st').textContent = m;
151
+ };
 
 
152
 
153
+ const showErr = m => {
154
+ const e = document.getElementById('errorMsg');
155
+ e.innerHTML = '' + m;
156
+ e.style.display = 'block';
157
+ };
 
 
 
 
 
158
 
159
+ async function ask(client, system, user) {
160
+ const out = await client.chatCompletion({
161
+ model: 'mistralai/Mistral-7B-Instruct-v0.3',
162
+ messages: [
163
+ { role: 'system', content: system },
164
+ { role: 'user', content: user }
165
+ ],
166
+ max_tokens: 1500,
167
+ temperature: 0.7
 
 
 
 
 
 
 
 
 
 
 
 
168
  });
169
+ return out.choices[0].message.content.trim();
 
 
 
 
 
 
 
 
 
 
 
 
170
  }
171
 
172
+ window.generate = async () => {
173
+ const topic = document.getElementById('topic').value.trim();
174
  const context = document.getElementById('context').value.trim();
175
+ const numCh = parseInt(document.getElementById('chapters').value);
176
+ const lang = document.getElementById('language').value;
177
+ const style = document.getElementById('style').value;
178
+ const token = document.getElementById('apiToken').value.trim();
179
+ const errEl = document.getElementById('errorMsg');
180
  errEl.style.display = 'none';
181
 
182
+ if(!topic){ showErr('Bitte gib ein Thema ein.'); return; }
183
+ if(!token){ showErr('Bitte gib deinen HuggingFace API Token ein.'); return; }
184
 
185
+ document.getElementById('genBtn').disabled = true;
186
+ document.getElementById('pw').style.display = 'block';
187
  document.getElementById('resultCard').style.display = 'none';
188
 
189
  try {
190
+ const client = new InferenceClient(token);
191
+ const sys = 'You are a professional book author. Always write in ' + lang + '. Be detailed and engaging.';
 
 
 
 
 
 
192
 
193
+ prog(5, '📋 Erstelle Kapitelstruktur...');
194
+ const outlineRaw = await ask(client, sys,
195
+ 'Create exactly ' + numCh + ' chapter titles for an eBook: "' + topic + '"\n' +
196
+ 'Audience: ' + (context||'general readers') + '\nStyle: ' + style + '\n' +
197
+ 'Output ONLY a numbered list:\n1. Title\n2. Title\n...'
198
+ );
199
 
200
+ const lines = outlineRaw.split('\n').filter(l => /^\d+[.):]/.test(l.trim()));
201
+ let titles = lines.map(l => l.replace(/^\d+[.):] */, '').trim()).filter(t=>t.length>2).slice(0, numCh);
202
+ if(titles.length < 2) {
203
+ const fb = outlineRaw.split('\n').map(l=>l.trim()).filter(l=>l.length>5);
204
+ titles = fb.slice(0, numCh);
 
 
 
205
  }
206
+ if(titles.length < 2) throw new Error('Kapitelstruktur konnte nicht generiert werden. Bitte erneut versuchen.');
207
+
208
+ prog(15, '✅ ' + titles.length + ' Kapitel geplant');
209
 
 
210
  const chapters = [];
211
+ for(let i=0; i<titles.length; i++){
212
+ prog(15 + Math.round((i+1)/titles.length*78),
213
+ '✍️ Kapitel ' + (i+1) + '/' + titles.length + ': ' + titles[i].substring(0,40) + '...');
214
+ const content = await ask(client, sys,
215
+ 'Write chapter ' + (i+1) + ' of the eBook "' + topic + '"\n' +
216
+ 'Chapter: "' + titles[i] + '"\n' +
217
+ 'Audience: ' + (context||'general readers') + '\n' +
218
+ 'Style: ' + style + '\n' +
219
+ 'Write 400+ words. Multiple paragraphs. Real content only. Start directly.'
 
220
  );
221
+ chapters.push({ title: titles[i], content });
 
222
  }
223
 
224
+ prog(98, '📚 Finalisiere...');
225
+ const intro = 'Dieses eBook wurde für ' + (context||'interessierte Leserinnen und Leser') +
226
+ ' verfasst und behandelt das Thema "' + topic + '" auf ' + style + 'e Weise.';
227
+ ebookData = { topic, lang, style, intro, chapters };
228
+ window.ebookData = ebookData;
 
 
 
 
 
 
229
 
230
+ const wc = chapters.reduce((s,c)=>s+c.content.split(/\s+/).length,0);
231
+ document.getElementById('sW').textContent = wc.toLocaleString('de-DE');
232
+ document.getElementById('sC').textContent = chapters.length;
233
+ document.getElementById('sP').textContent = '~'+Math.round(wc/250);
234
+ document.getElementById('cList').innerHTML = chapters.map((c,i)=>'<li>Kapitel '+(i+1)+': '+c.title+'</li>').join('');
235
+ document.getElementById('prev').textContent =
236
+ topic+'\n'+'='.repeat(50)+'\n\n'+intro+'\n\n'+
237
+ chapters.map((c,i)=>'Kapitel '+(i+1)+': '+c.title+'\n'+'-'.repeat(40)+'\n'+c.content).join('\n\n');
238
 
239
+ prog(100,'🎉 Fertig!');
 
 
 
 
240
  document.getElementById('resultCard').style.display = 'block';
241
 
242
  } catch(err) {
243
+ showErr(err.message || String(err));
244
+ document.getElementById('pw').style.display = 'none';
245
  } finally {
246
+ document.getElementById('genBtn').disabled = false;
247
  }
248
+ };
249
 
250
+ function buildHTML(){
251
+ const { topic, lang, style, intro, chapters } = ebookData;
252
+ const lc = {'German':'de','English':'en','Spanish':'es','French':'fr'}[lang]||'de';
253
+ const toc = chapters.map((c,i)=>'<li><a href="#ch'+(i+1)+'">Kapitel '+(i+1)+': '+c.title+'</a></li>').join('');
254
+ const chs = chapters.map((c,i)=>
255
+ '<div class="ch" id="ch'+(i+1)+'"><div class="cnum">Kapitel '+(i+1)+'</div><h2>'+c.title+'</h2>'+
256
+ c.content.split('\n\n').filter(p=>p.trim()).map(p=>'<p>'+p.replace(/\n/g,' ')+'</p>').join('')+'</div>'
 
 
 
257
  ).join('');
258
+ return '<!DOCTYPE html><html lang="'+lc+'"><head><meta charset="UTF-8"/><title>'+topic+
259
+ '</title><style>@page{size:6in 9in;margin:1in}body{font-family:Georgia,serif;font-size:11pt;line-height:1.8;color:#1a1a1a;max-width:580px;margin:0 auto;padding:40px 20px}'+
260
+ '.cover{text-align:center;padding:100px 40px;border-bottom:3px double #333;margin-bottom:60px;page-break-after:always}.cover h1{font-size:24pt;line-height:1.3}'+
261
+ '.toc{page-break-after:always}.toc h2{font-size:15pt;border-bottom:2px solid #333;padding-bottom:8px;margin-bottom:16px}.toc ul{list-style:none;padding:0}'+
262
+ '.toc li{padding:7px 0;border-bottom:1px dotted #ccc}.toc a{text-decoration:none;color:#222}'+
263
+ '.intro{font-style:italic;color:#555;border-left:4px solid #aaa;padding-left:16px;margin:30px 0 50px;page-break-after:always}'+
264
+ '.ch{page-break-before:always}.cnum{font-size:9pt;text-transform:uppercase;letter-spacing:3px;color:#999;margin-bottom:6px}'+
265
+ '.ch h2{font-size:17pt;border-bottom:2px solid #222;padding-bottom:10px;margin-bottom:20px}'+
266
+ 'p{margin-bottom:14px;text-align:justify;text-indent:1.5em}p:first-of-type{text-indent:0}</style></head><body>'+
267
+ '<div class="cover"><h1>'+topic+'</h1><p style="color:#666;margin-top:20px">&copy; '+new Date().getFullYear()+'</p></div>'+
268
+ '<div class="toc"><h2>Inhaltsverzeichnis</h2><ul>'+toc+'</ul></div>'+
269
+ '<div class="intro"><p>'+intro+'</p></div>'+chs+'</body></html>';
270
  }
271
 
272
+ window.dlHTML = () => {
273
  if(!ebookData) return;
 
274
  const a = document.createElement('a');
275
+ a.href = URL.createObjectURL(new Blob([buildHTML()],{type:'text/html;charset=utf-8'}));
276
  a.download = ebookData.topic.replace(/[^\w\säöüÄÖÜß]/g,'_').substring(0,50)+'_KDP.html';
277
  a.click();
278
+ };
279
 
280
+ window.dlTXT = () => {
281
  if(!ebookData) return;
282
  const txt = ebookData.topic+'\n'+'='.repeat(60)+'\n\n'+ebookData.intro+'\n\n'+
283
  ebookData.chapters.map((c,i)=>'KAPITEL '+(i+1)+': '+c.title.toUpperCase()+'\n'+'-'.repeat(60)+'\n'+c.content).join('\n\n');
 
284
  const a = document.createElement('a');
285
+ a.href = URL.createObjectURL(new Blob([txt],{type:'text/plain;charset=utf-8'}));
286
  a.download = ebookData.topic.replace(/[^\w\säöüÄÖÜß]/g,'_').substring(0,50)+'_KDP.txt';
287
  a.click();
288
+ };
289
  </script>
290
  </body>
291
  </html>