Startseite / Artikel

Entwurf einer transparenten RAG-Schnittstelle mit React, Tailwind und Motion

Erstellen Sie eine RAG-Voransicht, die die Suchvorgänge, interaktive Zitate, ein Quellenpanel sowie Aktionen nach der Antwort mithilfe von React State, Tailwind CSS und Framer Motion bereitstellt.

Verlag: REACTAPP.TOP11 Min. Lesezeit2544 Wörter

Quellen: How I’d Build a Modern RAG Interface with React, Tailwind CSS & Framer Motion

Ein Pipeline für retrieval-augmented generation kann technisch hervorragend sein und dennoch wie ein schwaches Produkt wirken, wenn die Benutzeroberfläche verbirgt, was das System gerade tut. Ein einzelnes Chatfeld mit einem Ladeindikator verrät dem Nutzer nichts darüber, welche Dokumente gesucht wurden, woher die Antwort stammt oder ob sie noch erstellt wird. Diese Anleitung zeigt, wie man eine React-Frontend-Struktur um die tatsächlichen Schritte einer RAG-Anfrage herum aufbaut, sodass das Abrufen von Inhalten, die Quellen, der Stream-Modus sowie die Zitierungen sichtbare und überprüfbare Bestandteile des Nutzererlebnisses werden.

Design basierend auf den Schritten des Abrufens

Der entscheidende Wandel besteht darin, das Produkt als eine vom Nutzer nachvollziehbare Abfolge statt als „Chat“ zu betrachten: Frage, Abrufen, Quellen, Erstellung, Zitierungen, weitere Untersuchung. Diese Abfolge bestimmt die Organisation der React-Anwendung.

Bei einem typischen internen Wissensassistenten kann der Bildschirm in drei Bereiche unterteilt werden: eine Dokumentensammlung links, das Gespräch in der Mitte und die Quellen für die aktuelle Antwort rechts.

---------------------------------------------------------
| Documents |          Conversation          | Sources |
|           |                                |         |
| SOPs      | User: How does onboarding work?| SOP.pdf |
| Policies  |                                |         |
| FAQs      | Searching knowledge...         | FAQ.pdf |
|           |                                |         |
|           | AI response...                 |         |
---------------------------------------------------------

Es bleibt eine Chat-Schnittstelle, doch das Gespräch ist nur ein Teil des Produkts. Darum herum muss die Anwendung außerdem Folgendes bereitstellen:

  • das zur Suche verfügbare Wissen
  • den aktuellen Abrufzustand
  • die für eine Antwort verwendeten Quellen
  • Dokumentmetadaten
  • Zitierungen
  • Nachfolgeaktionen
  • frühere Gespräche

Wenn all das in ein einziges Chat-Komponente gepackt wird, wird es schnell unübersichtlich.

Eine Komponentenstruktur, die zum Produkt passt

Eine praktikable Struktur sieht so aus:

RAGApplication
|
|-- Sidebar
|   |-- NewConversation
|   |-- ConversationHistory
|   `-- DocumentLibrary
|
|-- ChatWorkspace
|   |-- MessageList
|   |-- RetrievalStatus
|   |-- StreamingResponse
|   |-- CitationList
|   `-- PromptInput
|
`-- SourcePanel
    |-- SourceCard
    |-- DocumentPreview
    |-- RetrievedChunk
    `-- SourceMetadata

Die Aufteilung zeigt, wie unterschiedlich sich jede Region verhält. Nachrichten sammeln sich wie in einem Gespräch, der Abrufindikator ist von kurzer Dauer, die Antworten fließen ein, das Quellenpanel hängt von der ausgewählten Zitation ab und die Bibliothek funktioniert wie ein Dashboard. Getrennte Komponenten ermöglichen es, den Zustand jeder Komponente unabhängig zu verwalten und zu verhindern, dass die fließenden Antworten jeden Token mit einer Neugenerierung des gesamten Bildschirms beantworten.

Schritt 1: Genügend Daten aus der API zurückgeben

Animationen und Layout kommen später. Zuerst muss der Backend dem Frontend etwas zur Verarbeitung liefern. Eine Antwort, die nur den generierten Text enthält, lässt das UI nichts anderes anzeigen als diesen Text:

{
  "answer": "..."
}

Ein umfassendereres Kontrakt beinhaltet einen Status sowie die Liste der Quellen, auf denen die Antwort beruht – jede mit einem Identifikator, einem Titel, einer Abschnittsreferenz und einem kurzen Auszug:

{
  "answer": "Enterprise customers can request...",
  "status": "complete",
  "sources": [
    {
      "id": "doc-1",
      "title": "Refund Policy",
      "section": "3.2",
      "snippet": "Refund requests must be submitted..."
    },
    {
      "id": "doc-2",
      "title": "Enterprise Terms",
      "section": "5",
      "snippet": "Enterprise subscriptions..."
    }
  ]
}

Mit dieser Struktur kann React die Antwort anzeigen, Zitat-Icons zeigen, einzelne Quellen öffnen und den entsprechenden Abschnitt vorvisualisieren, wodurch die Belege zu einer erstklassigen Interaktion werden statt nur etwas, das später hinzukommt. Achten Sie auf eine stabile id: Sie dient als React-Schlüssel für die Quellkarten und als Identifikator zum Öffnen eines Dokuments.

Schritt 2: Abruf des Modells als expliziter UI-Zustand

Eine RAG-Anfrage durchläuft mehrere unterschiedliche Phasen – es ist hilfreich, diese explizit zu benennen:

idle
↓
retrieving
↓
sources_found
↓
generating
↓
complete

Der einfachste Ausgangspunkt in React sind einige Zustände für die aktuelle Phase, die Quellen sowie die Antwort:

const [status, setStatus] = useState("idle");
const [sources, setSources] = useState([]);
const [answer, setAnswer] = useState("");

Wenn der Benutzer eine Frage einreicht, durchläuft der Handler diese Phasen im Laufe des Vorgangs:

async function handleAsk(question) {
  setStatus("retrieving");
  const result = await askKnowledgeBase(question);  setSources(result.sources);
  setStatus("generating");  setAnswer(result.answer);
  setStatus("complete");
}

Dies ist absichtlich vereinfacht dargestellt. Da ein Aufruf sowohl die Quellen als auch die Antwort zurückgibt, wird generating im selben Schritt durch complete ersetzt, wodurch React die Updates in Batches ausführt und der Benutzer den Zwischenzustand niemals sieht (und sources_found wird überhaupt nie gesetzt). Eine Streaming-Implementierung würde hingegen ein Ereignis für abgeschlossene Abfrage ausgeben, anschließend die Antwort-Tokens und danach den Abschluss. Der Handler benötigt außerdem try/catch sowie einen error-Status, damit eine fehlgeschlagene Anfrage nicht endlos läuft. Dennoch ermöglicht ein expliziter Status es der Benutzeroberfläche, genau anzugeben, was vor sich geht – und je mehr Phasen es gibt, desto einfacher bleibt ein useReducer mit definierten Übergängen konsistent als mehrere useState-Aufrufe.

Schritt 3: Den Spinner durch einen Abfrage-Indikator ersetzen

Eine einfache „Laden...“-Meldung sagt fast nichts aus. Eine RAG-Anwendung kann hingegen den Ablauf beschreiben. Zuerst sieht der Benutzer, dass in der Wissensdatenbank gesucht wird:

Searching your knowledge base...

dann, wie viele Belege gefunden wurden:

Found 4 relevant sources

und schließlich, dass die Antwort erstellt wird:

Generating response...

Diese kleine Änderung erklärt den Ablauf und gibt Hinweise auf die Dauer des Wartens. Ein minimales Komponentenmodul übersetzt den Status in eine Meldung:

function RetrievalStatus({ status, sourceCount }) {
  if (status === "retrieving") {
    return <p>Searching your knowledge base...</p>;
  }
  if (status === "generating") {
    return <p>Found {sourceCount} sources. Generating response...</p>;
  }  return null;
}

Nichts Besonderes – und genau das ist der Punkt: Die Komponente gibt null zurück, sobald es nichts zu melden gibt, doch die Anwendung wirkt dadurch sofort überlegter. Die Anzeige der Meldung in einem aria-live="polite"-Bereich ermöglicht es Benutzern mit Screen-Readern, dieselben Aktualisierungen zu hören.

Schritt 4: Visuelle Hierarchie mit Tailwind erstellen

RAG-Bildschirme füllen sich schnell mit Benutzer- und Modellnachrichten, Dateinamen und Auszügen, Zitaten, Warnungen, Aktionen sowie Statusangaben zur Informationsabrufung. Die Utility-Klassen von Tailwind ermöglichen es, schnell eine klare Hierarchie zu schaffen, ohne auf umfangreiche benutzerdefinierte Stylesheets angewiesen zu sein. Eine Quellkarte kann beispielsweise einen gerahmten, leicht verschatteten Container mit einem fetten Titel verwenden:

<div className="rounded-xl border border-slate-200 bg-white p-4 shadow-sm">
  <p className="text-sm font-semibold text-slate-900">
    Refund Policy
  </p>

danach folgt eine dezent gestaltete Abschnittsbezeichnung sowie ein gut lesbarer Auszug mit ausreichendem Zeilenabstand:

  <p className="mt-1 text-xs text-slate-500">
    Section 3.2
  </p>  <p className="mt-3 text-sm leading-6 text-slate-700">
    Refund requests must be submitted within...
  </p>
</div>

Das Design ist absichtlich schlicht gehalten. In KI-Interfaces zählt Klarheit mehr als Verzierung, denn der Benutzer möchte drei Dinge wissen: die Antwort, ihre Herkunft sowie den möglichen nächsten Schritt. Die visuelle Wirkung sollte dieser Reihenfolge entsprechen.

Schritt 5: Zitate interaktiv gestalten

Viele RAG-Interfaces enden bei einer in Klammern gesetzten Zahl wie [1]. Eine Quelle ist weitaus nützlicher, wenn sie dem Benutzer ermöglicht, die Beweise hinter einer Aussage zu überprüfen. Jede Quelle als Button darzustellen, der die ausgewählte Quelle speichert, ist eine einfache Möglichkeit, dies zu erreichen:

<button
  onClick={() => setSelectedSource(source)}
  className="rounded-full border px-3 py-1 text-sm"
>
  Refund Policy · 3.2
</button>

Durch Klicken auf den Button öffnet sich das Quellenpanel, das den Dokumententitel anzeigt:

Refund Policy

zusammen mit dem Abschnitt, dem zitierten Textabschnitt und einer Möglichkeit, das vollständige Dokument zu öffnen:

Section 3.2"Refund requests must be submitted within..."[Open document]

Quellen erfüllen nun eine wirkliche Funktion, und die Begründung der Antwort wird sichtbar: Benutzer können eine Aussage mit einem Klick überprüfen, anstatt ihr blind zu vertrauen. Ein echtes Button-Element sorgt außerdem dafür, dass Quellen auch per Tastatur erreichbar bleiben.

Schritt 6: Verwendung von Animationen, um zu erklären, was sich geändert hat

Framer Motion findet in dieser Art von Interface nur dann Verwendung, wenn die Animation den Zustand vermittelt. Nichts sollte aus reiner Laune animiert werden. Eine gute Anwendung ist es, die Quellkarten nach Abschluss der Abrufung leicht zu verblasen und nach oben zu schieben. Beginnen Sie damit, das motion-Komponente zu importieren:

import { motion } from "framer-motion";

Dann umhüllen Sie jede Karte mit einem motion.div, der innerhalb von ein Viertel Sekunde von transparent und verschoben zu vollständig sichtbar animiert wird:

<motion.div
  initial={{ opacity: 0, y: 8 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.25 }}
>
  <SourceCard source={source} />
</motion.div>

Das Quellpanel kann bei Auswahl einer Zitation von der Seite hereingleiten und wieder hinausgleiten, wenn es geschlossen wird:

<motion.aside
  initial={{ x: 40, opacity: 0 }}
  animate={{ x: 0, opacity: 1 }}
  exit={{ x: 40, opacity: 0 }}
>
  <SourcePanel source={selectedSource} />
</motion.aside>

Eine Einschränkung: Die exit-Animation läuft nur dann, wenn das Komponente innerhalb von AnimatePresence dargestellt wird, was das Element so lange im System hält, bis die Ausgangsanimation abgeschlossen ist. Ohne dies entfernt React das Panel sofort, und die Ausziehanimation wird nie abgespielt. Beachten Sie außerdem, dass Framer Motion zum Zeitpunkt der Erstellung als motion-Paket mit einem Einstiegspunkt motion/react bereitgestellt wird; prüfen Sie die aktuellen Dokumente nach dem Importpfad, der zu Ihrer installierten Version passt.

Der Wert liegt in der Botschaft, die die Bewegung übermittelt: Etwas hat sich geändert, neue Informationen sind eingetroffen, und diese Quelle gehört zu dieser Antwort. Respektieren Sie die Einstellung „reduced-motion“, damit sie niemals zu einer Störung wird.

Schritt 7: Streaming anders behandeln als Laden

Das Streaming erzeugt sein eigenes UX-Problem. Wenn der Text Zeichen für Zeichen ankommt, muss die Benutzeroberfläche deutlich machen, dass die Antwort noch geschrieben wird. Ein kleiner, pulsierender Cursor nach dem Text erfüllt diese Funktion gut. Das Komponente rendernt den bisherigen Inhalt:

function StreamingAnswer({ content, isStreaming }) {
  return (
    <div className="prose max-w-none">
      {content}

und fügt den Cursor nur hinzu, solange das Streaming aktiv ist:

      {isStreaming && (
        <span className="ml-1 inline-block h-4 w-1 animate-pulse bg-slate-500" />
      )}
    </div>
  );
}

Es ist auch sinnvoll, einzuschränken, was der Benutzer mit einer unvollendeten Antwort tun kann. Lesefunktionen können weiterhin verfügbar bleiben, während alle Aktionen, die die Antwort an einen anderen Ort speichern, auf die Fertigstellung warten:

Copy answer       ✓
View sources      ✓
Create task       disabled while streaming
Export            disabled while streaming

Das Ziel ist eine ehrliche Kommunikation des Zustands – nicht nur ein ansprechendes Erscheinungsbild: Die Exportierung einer halbgeschriebenen Antwort würde ein fehlerhaftes Ergebnis liefern.

Schritt 8: Das Quellpanel zum Produkt machen

Ein ordentliches Quellpanel gehört zu den stärksten Funktionen, die eine RAG-Benutzeroberfläche anbieten kann. Betrachten Sie den gesamten Prozess:

User asks question
        ↓
RAG retrieves 4 chunks
        ↓
AI generates answer
        ↓
Citation cards appear
        ↓
User clicks citation
        ↓
Relevant document section opens

Mindestens kann das Panel folgendes anzeigen:

  • den Dokumenttitel
  • die Abschnittsbezeichnung
  • den abgerufenen Textabschnitt
  • die Dokumenttypen
  • Informationen zur Relevanz
  • einen Link oder Knopf zum Öffnen des Dokuments

Mit der Zeit kann es um folgende Funktionen erweitert werden:

  • Hervorhebung des passenden Textes
  • angrenzende Teile zum Kontext
  • eine Vorschau des Dokuments
  • Filter für Metadaten

Jede Erweiterung macht das System weniger zu einer Black Box. Ein gutes Panel zeigt außerdem Schwächen bei der Suche auf, was ein Grund ist, frühzeitig in Quelltextqualität bei RAG-Projekten zu investieren.

Schritt 9: Aktionen nach der Antwort anbieten

Hier beginnt ein RAG-Chatbot, sich in eine Anwendung zu verwandeln. Angenommen, jemand fragt nach dem Prozess, wie das Team einen neuen Kunden aufnimmt. Das System holt die Onboarding-SOP ab und antwortet mit einer Checkliste:

1. Create workspace
2. Request account access
3. Confirm scope
4. Assign team
5. Schedule kickoff

Anstatt dort aufzuhören, kann die Benutzeroberfläche einen Link zum Quelldokument anbieten:

[View SOP]

zusätzlich zu Aktionen, die das Ergebnis erneut nutzen:

[Copy checklist][Create task list]

Die Aufgabenliste-Aktion ist die interessante. Der Wert eines KI-Systems liegt oft weniger in der Antwort selbst als darin, was es anschließend ermöglicht – und genau hier beginnen RAG-Anwendungen, sich in Richtung Agenten zu entwickeln. Unser Leitfaden zur Erstellung von mehrschrittigen KI-Agenten-Benutzeroberflächen behandelt genau diese Richtung.

Die vollständige Interaktion, von Anfang bis Ende

Zusammenfassend lässt sich sagen, dass eine einzige Frage durch den gesamten Stack wandert. Sie beginnt damit, dass der Benutzer fragt, ob ein Unternehmenskunde nach 30 Tagen stornieren kann:

USER
"Can an enterprise client cancel after 30 days?"

Daraufhin erstellt React den Abfrastatus, die RAG-API holt Dokumente ab, die Statuszeile gibt Auskunft über die Suche, die Anzahl der Quellen wird angezeigt, das LLM erzeugt eine fundierte Antwort, die auf dem Bildschirm angezeigt wird, es erscheinen Zitierchips, und ein Klick darauf bringt das Quellenpanel mit dem entsprechenden Abschnitt in Sicht:

        ↓REACT
Creates query state        ↓RAG API
Retrieves relevant documents        ↓UI STATUS
"Searching your knowledge base..."        ↓SOURCES
3 relevant sources found        ↓LLM
Generates grounded response        ↓STREAMING UI
Response appears progressively        ↓CITATIONS
Refund Policy · 3.2
Enterprise Terms · 5        ↓USER
Clicks citation        ↓MOTION
Source panel slides into view        ↓SOURCE
Relevant passage displayed

Betrachten Sie es als zwei zusammenarbeitende Schichten: eine Intelligenzschicht im Backend, die Daten abruft und generiert, sowie eine Benutzeroberflächenschicht im Frontend, die die Ergebnisse verständlich macht. Sie werden getrennt entwickelt, müssen aber gemeinsam konzipiert werden – deshalb ist der Vertrag für Schritt 1 so wichtig.

Eine pragmatische Technologiestack

Eine leichtgewichtige Version dieser Anwendung kann mit folgendem Aufbau erstellt werden:

  • Frontend: React, Tailwind CSS und Framer Motion.
  • Backend: FastAPI auf Python-Basis.
  • Datenabruf: Embeddings sowie eine Vektordatenbank, die mit LangChain oder einem kleinen benutzerdefinierten Pipeline gesteuert wird.
  • Modell: jede geeignete gehostete API, wie z. B. OpenAI, Gemini oder Claude.
  • Speicherung: PostgreSQL oder Supabase in Kombination mit Qdrant oder einem ähnlichen Vektor-Speicher.

Es ist nicht notwendig, alles von Anfang an zu verwenden. Für ein Portfolio-Projekt reicht ein minimaler Ausgangspunkt aus:

React
+
Tailwind
+
Framer Motion
+
FastAPI
+
one LLM
+
one vector store

Sobald der Kernmechanismus funktioniert, können die Qualität des Datenabrufs, das Neuranking, die Authentifizierung, die Dokumentverarbeitung sowie weitere Funktionen schrittweise verbessert werden.

Das Muster, das man vermeiden sollte

Die Version, von der man abraten sollte, sieht so aus:

Upload PDF
↓
Chat box
↓
Spinner
↓
Answer

Dies beweist, dass die Pipeline funktioniert, zeigt aber wenig Produktdenken: Nichts ist sichtbar, überprüfbar oder handlungsrelevant. Ein stärkeres Ziel kombiniert mehrere Ebenen:

Knowledge
+
Retrieval
+
Transparent states
+
Citations
+
Source exploration
+
Useful actions

Diese Kombination demonstriert sowohl Ingenieurskunst im Bereich KI als auch eine solide Implementierung der Frontend-Technologien.

Warum die Schnittstellschicht Aufmerksamkeit verdient

Das Modell ist nur die Hälfte eines KI-Produkts. Der Rest sind jene Elemente, die gute Software seit jeher erfordert: verständliche Interaktionen, klare Rückmeldungen, vorhersagbare Zustände, nützliche Arbeitsabläufe sowie eine solide Informationsarchitektur. React, Tailwind und Motion tragen keinerlei Intelligenz bei, bestimmen jedoch darüber, ob jemand davon profitieren kann – und Frontend-Arbeit wird regelmäßig unterschätzt.

Kernpunkte

  • Entwerfen Sie zunächst die API-Antwort; Quellen mit stabilen IDs, Abschnitten und Auszügen ermöglichen Zitierungen sowie Quellenansichten.
  • Modellieren Sie die Anfrage als benannte Phasen und planen Sie für das Streaming sowie Fehlerfälle, anstatt nur ein einzelnes Ladeflag zu steuern.
  • Betrachten Sie Zitate als Buttons, die Beweise öffnen, und widmen Sie dem Quellenpanel echte Aufmerksamkeit.
  • Verwenden Sie Animationen nur, um Veränderungen anzuzeigen, schließen Sie ausschließende Elemente in AnimatePresence ein und respektieren Sie Einstellungen zur Reduzierung von Animationen.
  • Sperren Sie Aktionen, die von einer abgeschlossenen Antwort abhängen, und bieten Sie weitere Schritte erst an, wenn diese fertig ist – denn genau dann wird ein Chatbot zu einer Anwendung.