Разрабатываеце прозрачны інтерфейс RAG з викорыстанням React, Tailwind і Motion.
Створыце фронтэнд RAG, які включае етапы выкарыстоўвання дадзеных, інтэрактыўныя цітаты, панель джэраў і дзеянні пасля адпаведзі, выкарыстоўваючы React state, Tailwind CSS і Framer Motion.
Крыніцы: How I’d Build a Modern RAG Interface with React, Tailwind CSS & Framer Motion
Процес стварэння тэксту з аддытамі на базе пошуку можа быць тэхнічна выдатным, але застаўся вялікім продуктам, калі інтерфейс скрывае тое, што робіць система. Адна лявочка для чату з індыкаторам загрузкі не дае корыстніку жадных вясноў пра тое, якія дакументы былі пашуканы, звядзе гэтая адпаведь і чы робіцца ёй яшчэ. У гэтым кераванні паказана, як структураваць фронтэнд на базе React адпаведна рэальным этапам запыту RAG, каб пошук, джэрела, стрімінг і цытаты сталі відкрытымі, можна адгледзець часткамі ўсяго процесу.
Дызайн, адпаведны этапам пошуку
Ключовыя змены — це спрытваць продукт як серію крокаў, якія можа пераследаваць корыстнік, а не як „чат“: запит, пошук, джэрела, стварэнне тэксту, цытаты, аналіз. Гэтая серія крокаў вядомае, як організаваць прыемлер на базе React.
Для стандартнага внутрашняга асистента з навукай можна раздзеліць экран на тры часткі: бібліятэку дакументаў з левага боку, размову ў середзіне і джэрела для тэкучай адпаведзі на правы бок.
---------------------------------------------------------
| Documents | Conversation | Sources |
| | | |
| SOPs | User: How does onboarding work?| SOP.pdf |
| Policies | | |
| FAQs | Searching knowledge... | FAQ.pdf |
| | | |
| | AI response... | |
---------------------------------------------------------
Это застаецца інтерфейсам чату, але размова — толькі частка продукту. Аплякацыя таксама павінна адкрываць:
- навукі, якія можна шукаць
- тэкучы статус адналёгчэння
- джэрела, якія былі выкарыстаны для адпаведзі
- метаданныя дакументаў
- цітаты
- наступныя дзеянні
- ранейшыя размовы
Збір усьго гэтага ў аднай частцы чату быстра становіцца неконтрольным.
Дрэва компанентаў, якія падходзяць да продукту
Одна працэспрыводная структура выглядае так:
RAGApplication
|
|-- Sidebar
| |-- NewConversation
| |-- ConversationHistory
| `-- DocumentLibrary
|
|-- ChatWorkspace
| |-- MessageList
| |-- RetrievalStatus
| |-- StreamingResponse
| |-- CitationList
| `-- PromptInput
|
`-- SourcePanel
|-- SourceCard
|-- DocumentPreview
|-- RetrievedChunk
`-- SourceMetadata
Разбілень паказвае, як рознаюцца паведанні кожнага регіону. Паведамлення накапліваюцца як размова, показчык адзначэння ўвесь час зменшаецца, адказы прыходзяць па стрэму, панель вучорага залежыць ад выбранай цітаты, а бібліятэка функцыонуе як панель керавання. Адзінаковыя компоненты дазваляюць незалежна кераваць станамі кожнага з іх і запобегчаюць таму, каб стрэмовы адказ занова не перакроўцаваў увесь экран з кожным токенам.
Шаг 1: Аддаць достатню колькасць дадзеных з API
Анімазіі і макет будуць пазней. Спачатку бэкенд должен даць фронтэнду тое, з чым можна працаваць. Адказ, які містыць толькі створаны текст, заставляе інтерфейс не маты нічога для паказу, кроме самага тексту:
{
"answer": "..."
}
Болей насыцены контракт включае статус і спіс вучорага, якія леглі в основу адказу; кожны з іх мае ідэнтыфікатор, заглавле, апавязанне з раздзелам і короткі фрагмент:
{
"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..."
}
]
}
За дапамою такой структуры React можа адразаваць адпаведную адказ, паказваць інформацыю пра джэрела, ачынаць окремыя джэрела і прадваротна адразаваць актуальны фрагмент, чым ператварае падтрымкі ў важлівы элемент інтеракцыі, а не у дадатковы элемент. Неабяжна выкарыстоўваць стабільны id: ён становіцца ключам для карточак джэраў у React і средствам для ачынення дакумента.
Шаг 2: Атрыманне модэлю як частка стану UI
Запрос RAG праходзіць через кальколька разных фаз, і корыстна ясна ўказаць іх назвы:
idle
↓
retrieving
↓
sources_found
↓
generating
↓
complete
Найпростейшы пункт выйска ў React — це кальколька элементаў стану для текущай фазы, джэраў і адказа:
const [status, setStatus] = useState("idle");
const [sources, setSources] = useState([]);
const [answer, setAnswer] = useState("");
Калі корыстнік адправляе запит, працоўны код проходзіць чераз гэтыя фазы па мере працы запросу:
async function handleAsk(question) {
setStatus("retrieving");
const result = await askKnowledgeBase(question); setSources(result.sources);
setStatus("generating"); setAnswer(result.answer);
setStatus("complete");
}
Эта версія спецыяльна спроставана. Пакалькі ўсё ж адна вызова вяртае як джэрыянты, так і адказ, таму generating заменяецца на complete у тым жа цыкле, тады React групавае апдэйты, і корыстнік ніколі не бачыць проміжнага стану (а sources_found ніколі не задаецца). У реалізацыі з стрімінгам уместна б была прамова пры завершэнні запрашоўкі, пасля чаго — токены адказу, а потым — паведамлення пра завершэння. Кантроллер таксама патрабуе try/catch і статус error, ўбачліва, каб неудачны запрос не працаваў безканечна. Аднак явны статус дазволяе інтерфейсу точна паказаць, што відбываецца, і чым больш фаз, тым лёгкая ўтримаць адпаведнасць за дапамогою useReducer з вялікім колькасцю пераходаў, чым за дапамогою калькі useState вызоўваў.
Шаг 3: Заменіць індыкатор запрашоўкі на паказчык стану запрашоўкі
Простая паведамленне „З’являецца...“ майже нічага не адказвае. Аплікацыя на базе RAG можа адказваць пра свой процес обробкі дадзеных. Спачатку корыстнік бачыць, што ведаецкая база адначасова шукаецца:
Searching your knowledge base...
потым — скількі доказаў было знайдзена:
Found 4 relevant sources
і нарэшце — што адпаведны адказ зараз пішыцца:
Generating response...
Такая невялікая змена пояснюе працэс обробкі дадзеных і стварае чытальныя аспекты ўжо па час чакання. Мінімальны компонент перадае статус у вастоўленую паведамленне:
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;
}
Няма нічога выключнага, і самэ гэта ўсё: кампонент вяртае null, калі нема чаго адзвярнуцца, пры тым як аплікацыя ведае, што трэба чакаць. Размішчанне паведамлення ў рэжыяне aria-live="polite" дазволяе корыстнікам з экранавымі чытальнікамі чуць тыя ж апошнія змены.
Шаг 4: Стварэнне візуальной іерархіі з Tailwind
RAG-экраны быстра заполняюцца паведамленнямі вядароў і модэляў, назвамі дакументаў і ўрывкамі з іх, цитатамі, адзявамі, дзеяннямі і статусам выкарыстоўвання. Класы-інструменты Tailwind дапамагаюць швабрасна стварыць чыстую іерархію без неабязковасці стварання велікага спецыяльнага шаблона стылю. Напрыклад, карточка вучэння можа выкорыстоўваць контейнер з рамкамі і лёгкай тэневым афектам, а таксама выразны заголовак:
<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>
пасля чаго следуе мяккая пазначка раздзела і чытабельны урывак з достатнім расстоянням между лініямі:
<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>
Стыль спецыяльна залишаецца простым. У інтэрфейсах ШІ чытразумнае прадставлення інфармацыі важлівей за декоратыўны элементы, адтолькі як вядар хочаць знать тры рэчы: адпаведны адказ, його магчымы выхад і наступны крок. Візуальная вага таксама должна адпавядаць гэтай параду.
Крок 5: Зробіць цитаты інтэрактыўнымі
Большынства інтэрфейсаў RAG зупінаюцца на номере ў квадратных скобках, напрыклад [1]. Цітата є ўжо болей корыстной, калі вона дазволяе корыстніку пераглядзець падставы для тверджэння. Адной з простых способаў адрабаткаць гэта є перетвароўкі кожной цітаты на кнопку, якая фіксуе выбраны джэрела:
<button
onClick={() => setSelectedSource(source)}
className="rounded-full border px-3 py-1 text-sm"
>
Refund Policy · 3.2
</button>
Пры натыканні на кнопку ачынаецца панель джэрел, у якой паказываецца назва дасягнення:
Refund Policy
разам з роздзелам, цітаваным урывкам і можласцю ачынення цэлага дасягнення:
Section 3.2"Refund requests must be submitted within..."[Open document]
Цітаты тепер выпалююць рэальную працу, і падставы для адпаведных адказоў стаюць виднымі: корыстнікі можаць пераканацца ў тверджэнні простым натыканнем, замест таго каб пакладацца на довер'е. Справжні элемент button таксама дазволяе доступ да цітатаў за дапамогою клавіатуры.
Шаг 6: Іспользаваць анімасцы, каб пазначыць, што змянілася
Framer Motion займае свае месца ў такім типе інтэрфейсу толькі тады, калі анімацыя передае статус. Нічога не должна рухацца проста так. Хорашым варыянтом є тое, калі карткі-джерела павольна згасаюць і слегка падымаюцца, калі завершаецца процес запошуку. Пачніце з імпорту компонента motion:
import { motion } from "framer-motion";
Потым абгорніце кожную картку ў motion.div, які анімавае ўвесь перыяд часу у чверці секунды з прозрачнага стану з аффектамі падышчы до повнай видимасці:
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
<SourceCard source={source} />
</motion.div>
Панель з джэреламі можа выйсці з боку, калі выбрана цитата, і зноў выйсці назад, калі вона закрываецца:
<motion.aside
initial={{ x: 40, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: 40, opacity: 0 }}
>
<SourcePanel source={selectedSource} />
</motion.aside>
Адзін застерэжны момент: анімацыя exit запускаецца толькі тады, калі компонент атрымліваецца ўнутрь AnimatePresence, які заставляе элемент застацца установленым пакуль не завершыцца анімацыя выходу. Без яго React негайна знімае панель, і анімацыя выходу ніколи не запускаецца. Таксама зазначым, што на момент напісання Framer Motion распространяецца як пакет motion з точкай выходу motion/react; пераканаўцеся ў актуальных дасведчэннях па шляху імпорту, який пасходзіць для вашай версіі.
Ценнае ў тым, што несе анімацыя: ў чымось зменілася, прыбыла новая інформацыя, і гэты выклік належыць да гэтай адпаведзі. Следзіце за настройкай reduced-motion, каб яна ніколи не стала дыскомфортам.
Шаг 7: Разлічваць стрімінг і завантажэнне
Стрімінг створяе сваю проблему з корыстнасцю. Калі тэкст прыходзіць па частках, інтэрфейс должен яскрава паказваць, што адказ ужо ўтвараецца. Маленькі пульсуючы знак пасля тэкста добра гэта робіць. Компонент атрыбутуе ўжо наявны контэнт:
function StreamingAnswer({ content, isStreaming }) {
return (
<div className="prose max-w-none">
{content}
і дадае знак толькі калі стрімінг актыўны:
{isStreaming && (
<span className="ml-1 inline-block h-4 w-1 animate-pulse bg-slate-500" />
)}
</div>
);
}
Таксама разумнае — абмежыць тое, што можа зрабіць корыстнік з незаканчаным адказам. Функцыі чытання можа застацца доступнымі, тады калі будь-што, што зафіксуе адказ у іншым месцы, чакае на завершэнне:
Copy answer ✓
View sources ✓
Create task disabled while streaming
Export disabled while streaming
Мета — чыстая абмовка пра стан, а не выгляд: экспорт незаканчанага адказу прынесе несправны рэзультат.
Шаг 8: Зробіць панель вучоркі частью продукту
Правильная панель вучоркі — адна з найсильнейшых функцый, якія можа запропанаваць інтэрфейс RAG. Актуальнае ўважлівае падходжэнне да всего процесу:
User asks question
↓
RAG retrieves 4 chunks
↓
AI generates answer
↓
Citation cards appear
↓
User clicks citation
↓
Relevant document section opens
Як мінімум, панель можа паказваць:
- тытул дасягу
- раздзел
- выявлены фрагмент
- тып дасягу
- інфармацыю пра значэнне
- супакой або кантакт для ачыць дасяг
З часам яна можа стаць шырэй і паказваць:
- выяўлены текст
- суседзькі фрагменты для контэксту
- перадворак дасягу
- фільтры метадаў
Кожны новы элемент робіць систему менш «чорнай скрывкой». Хорашая панель таксама адкрывае слабыя стороны выявлення інфармацыі, што ўзначнае прычыну інвеставацій у якасць вучоркаў ў проектах RAG з самага пачатку.
Шаг 9: Аддаць можлівасці дзеяння пасля адпаведзі
Што самэ гэта — момент, калі чат-бот на адыях RAG пачына перацаваць як прыемлівая програма. Падазром, хтось запытаецца, як каманда адкалічвае новага кліента. Система запрашвае інструкцыі з процедуры адкалічвання і дае адказ з справакамі:
1. Create workspace
2. Request account access
3. Confirm scope
4. Assign team
5. Schedule kickoff
Узамест таго, каб застацца на гэтым, інтэрфейс можа запрацавіць галоўку, якая ведае назад да адпаведнага дакумента:
[View SOP]
а таксама дзеянні, якія выкарыстоўваюць рэзультат:
[Copy checklist][Create task list]
Найцікавейшым є дзеянне зі спісам задач. Ценнае значэнне AI-системы часта крыўдзіцца не столькі ў адказе, сколькі ў тым, што яна дазволяе зрабіць далей, і самэ гэта ўскладнюе розлік меж між чат-ботамі на адыях RAG і агентамі. Наша энцыклапедыя пра стварэнне UI для багатыядерных AI-агентоў раскрывае гэты напрамак.
Полная взаімадзея, ад пачатку да канца
Якшто адгрупаваць усё, цэлы вопыт праходзіць через увесь тэкст. Усё пачынаецца з запиту корыстніка пра тое, чы можа кліент-падпрыемніца анулюваць замовленне пасля 30 дзён:
USER
"Can an enterprise client cancel after 30 days?"
Пасля чаго React стварае стан запиту, API RAG атрымляе дакументы, лінія статусу аднараджае пра результаты пошуку, паказываецца колькість джэрел, LLM генеруе адпаведны адказ, які праказваецца на экране, паказываюцься чыпы з цітатамі, а клік на однага з іх выводзіць панель джэрела з адпаведным фрагментам у відобразэнне:
↓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
Уявіце сабе гэта як два взаімаўпрацоючыя шары: шар інтэлекту на бэкендзе, який атрымляе і генеруе інфармацыю, і шар корыстнічага інтерфейсу на фронтендзе, який робіць всю працу зрозумелай. Яны ствараюцца окалічна, але павінны быць спроектаваны разам, таму так важліва угода па першым кроку.
Прагматычны тэхналогічны стак
Легкую версію гэтага прыемплу можна стварыць за дапамою:
- Фронтэнд: React, Tailwind CSS і Framer Motion.
- Бэкэнд: FastAPI на Python.
- Атрыман дадзеных: embeddings і вектарная база дадзеных, кантролюемая за дапамою LangChain або невялікага спецыяльнага пайплайна.
- Модель: будзь-яя падходячая хоставаная API, такая як OpenAI, Gemini або Claude.
- Храненне: PostgreSQL або Supabase, з Qdrant або падобным вектарным хранілышчам.
Няма прычыны вжываць усё гэта з самага пачатку. Для проекту ў портфоліе падыходзіць мінімальны стартовы пункт:
React
+
Tailwind
+
Framer Motion
+
FastAPI
+
one LLM
+
one vector store
Як толькі основны цыкл запрацюе, можна поступова паспрабаваць падняць якосьць атрымана дадзеных, пераранжавання, аутентыкацыю, обробку дакументаў і іншыя функцыі.
Шаблон, якога трэба ухіляцца
Версія, ад каторай трэба ухіляцца, выглядае так:
Upload PDF
↓
Chat box
↓
Spinner
↓
Answer
Гэта паказвае, што канал обработкі працуе, але не паказвае значныя рэшткі продуктовага мышленьня: нічога не ўзглядна, не можна пераканацца і немагчыма выконваць. Болей сильны цэльны падход аб’еднвае калькі слоёў:
Knowledge
+
Retrieval
+
Transparent states
+
Citations
+
Source exploration
+
Useful actions
Такое аб’еднанне паказвае як інжынерыю ІІ, так і адлежны рэалізаванні фронтэнда.
Чаму слой інтарфейса заслуговае на увагу
Модель — толькі палова частка ІІ-продукту. Рэшта — гэта тое, што завжды было неабходна для хорашага програмнага забезпечэння: зрозумелыя взаімадзеі, чыстая адпаведнасць, прагнозаваныя станы, корыстныя рабочыя процесы і правільная архітэктура інформацыі. React, Tailwind і Motion не дадзяўнаюць ніякой інтэлігенцыі, проты яны вяршаюць, чыі можа скорыстацца яе, і робота над фронтэндам часта недаўаецца належной ціны.
Ключовыя выводы
- Спачатку праграмаваць адпаведнасць API; джэрелы з стабільнымі ID, разделамі і фрагментамі — гэта тое, што дазволяе ствараць цітаты і панелі джэралаў.
AnimatePresence, і прыймайце у вялікай частцы прагантаванні ўскораненага адображэння.