Kennst du das? Du musst spontan einen Vortrag halten, hast aber keine Ahnung über das Thema? Genau das ist die Idee hinter Speakaoke – einer Web-App, die auf Knopfdruck Präsentationen zu beliebigen Themen generiert! Du gibst einen Begriff ein (z. B. Quallen), und schon bekommst du eine fertige Präsentation mit fünf Folien, erstellt durch OpenAI. Mithilfe von Reveal.js kannst du dann direkt loslegen.
In diesem Mini-Tutorial zeige ich dir, wie Speakaoke technisch aufgebaut ist und wie du ein ähnliches Projekt mit Fastify (Backend) und SolidJS (Frontend) umsetzen kannst.
Speakaoke besteht aus zwei Hauptkomponenten:
Der Ablauf sieht so aus:
Jetzt gehen wir ins Detail!
Das Frontend besteht aus zwei Hauptseiten:
SearchPage
– Hier gibt der Nutzer den Suchbegriff ein.PresentationPage
– Hier wird die Präsentation angezeigt.SearchPage
– Das Suchfeldimport { createSignal } from "solid-js";
import { useNavigate } from "@solidjs/router";
import { applicationStore } from "../store/app-store";
const SearchPage = () => {
const [keyword, setKeyword] = createSignal("");
const navigate = useNavigate();
const fetchPresentation = async () => {
const response = await fetch("/api/presentation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ keyword: keyword() }),
});
const data = await response.json();
localStorage.setItem("presentation", JSON.stringify(data));
navigate("/presentation");
};
return (
<div>
<input
type="text"
onInput={(e) => setKeyword(e.target.value)}
placeholder="Thema eingeben..."
/>
<button onClick={fetchPresentation}>Generieren</button>
</div>
);
};
Sobald der Nutzer auf den Button klickt, wird eine Anfrage an das Backend gesendet, und die Antwort im localStorage
gespeichert.
PresentationPage
– Die Slideshow mit Theme-Optionenimport Reveal from "reveal.js";
import "reveal.js/dist/reveal.css";
import "reveal.js/dist/theme/black.css";
import { onMount, createSignal } from "solid-js";
import { marked } from "marked";
const PresentationPage = () => {
let deck;
const [theme, setTheme] = createSignal("black");
onMount(() => {
const presentationData = JSON.parse(
localStorage.getItem("presentation"),
);
deck = new Reveal();
deck.initialize();
});
return (
<div>
<select onChange={(e) => setTheme(e.target.value)}>
<option value="black">Black</option>
<option value="white">White</option>
<option value="league">League</option>
</select>
<div class="reveal">
<div class="slides">
{presentationData.slides.map((slide) => (
<section
innerHTML={marked(slide)}
class={theme()}
></section>
))}
</div>
</div>
</div>
);
};
Die oben gezeigte Implementierung funktioniert auf den ersten Blick gut. Allerdings gibt es in SolidJS ein Problem beim Wechsel der Route: Die Präsentation wird nicht korrekt neu initialisiert.
Daher braucht es eine etwas hacky Lösung, um sicherzustellen, dass Reveal.js richtig geladen wird:
onMount(() => {
const presentationData = JSON.parse(localStorage.getItem("presentation"));
setTimeout(() => {
if (!deck) {
deck = new Reveal();
deck.initialize({
controls: true,
progress: true,
hash: true,
});
setTimeout(() => deck.sync(), 500);
}
}, 100);
});
Diese Lösung stellt sicher, dass Reveal.js mit einer kleinen Verzögerung initialisiert wird, um Timing-Probleme beim Wechsel der Route zu umgehen.
Das Backend ist ein Fastify-Server, der eine einzige API-Route /presentation
bereitstellt. Hier wird das Keyword entgegengenommen, an OpenAI weitergegeben und die generierte Präsentation zurückgesendet.
const dotenv = require("dotenv");
dotenv.config();
const OpenAI = require("openai");
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
module.exports = async function (fastify) {
fastify.post("/presentation", async (request, reply) => {
const { keyword } = request.body;
const response = await openai.createCompletion({
model: "gpt-4",
prompt: `Erstelle eine Präsentation mit 5 Folien über ${keyword}. Jede Folie sollte eine Markdown-Überschrift und kurze Bulletpoints haben.`,
max_tokens: 500,
});
reply.send({ slides: response.data.choices[0].text.split("\n\n") });
});
};
Hier passiert die Magie! Die Präsentation wird von OpenAI im Markdown-Format generiert und an das Frontend zurückgeschickt.
Speakaoke ist bereits ein spaßiges Tool, aber es gibt noch viele spannende Erweiterungsmöglichkeiten:
Für Feedback bin ich immer dankbar. Gerne an jacob@derkuba.de
Viele Grüße
Euer Kuba