Dokumentace Maturitní Práce Waifuu
Abstrakt
Tato práce představuje webovou a mobilní aplikaci pro chatování s fiktivními postavami poháněnými umělou inteligencí. Umožňuje uživatelům vytvářet vlastní postavy, procházet a chatovat s postavami ostatních uživatelů, přidávat přátele a účastnit se veřejného chatu. Teoretická část se zabývá roleplaying chatboty, jejich možnostmi využití, architekturou velkých jazykových modelů a technikami pro překonání jejich omezení. Praktická část popisuje implementaci klíčových funkcí, jako je náhodný veřejný chat, správa přátel, nabídka komunitních postav, statistiky postav, sdílení zpráv, veřejné profily a realtime skupinový chat. Využívá moderní webové technologie, jako React, Next.js, PostgreSQL… Aplikace poskytuje zajímavou lidskou zkušenost z interakce s AI postavami a podporuje kreativitu a sociální vazby mezi uživateli.
Klíčová slova
umělá inteligence, chatboty, roleplay, veřejný chat, přátelé, komunitní postavy, statistiky, sdílení zpráv, uživatelské profily, realtime komunikace, velké jazykové modely, kontextové okno, halucinace, vektorová databáze, databázový návrh, webové technologie, mobilní aplikace
Prohlášení
Prohlašuji, že jsem zadanou diplomovou práci vypracoval/a samostatně a všechny použité zdroje uvedl/a v seznamu literatury a použitých zdrojů.
Ve Zlíně dne 16.4.2024 Matěj Pešl
Poděkování
Děkuji Viktoru Vráblíkovi za zpětnou vazbu, Kubovi Dorničákovi za lehkou pomoc, a Adamovi Krupovi za psychickou podporu.
Obsah
Teoretická část
Zájem o roleplay chatboty
S nástupem A.I. chatbotů došlo v posledních měsících k nárůstu popularity chatbotů založených na roleplay, jako jsou například ti, které nabízí CharacterAI či Replika. [1] Tito společníci poháněni umělou inteligencí zaujali pozornost různorodých skupin jednotlivců, z nichž každý je přitahován jiným typem zážitku, které tyto chatboty poskytují.
Jednimi z hlavních důvodů, proč jsou lidé přitahováni k chatbotům založeným na roleplay, jsou zvědavost, sociální podpora (a rozšířeně i nedostatek sociální podpory / interakce), a podpora mentálního zdraví .[2][3] AI společníci nabízejí možnost jak fantazijního dobrodružství, tak presonalizovanou konverzaci s virtuálním přítelem.
V čím dál více digitálním věku, kdy se osobní interakce stávají méně častými, tito AI společníci nabízejí pocit sounáležitosti a emocionální podpory. Pro jednotlivce, kteří mohou mít problémy se sociální úzkostí nebo je pro ně obtížné navazovat spojení v reálném životě, mohou chatboty sloužit jako odrazový můstek k budování sebevědomí a procvičování sociálních dovedností v kontrolovaném prostředí.
Kreativní typy, jako jsou spisovatelé a umělci, mohou najít inspiraci v imaginárních světech a postavách, které tyto platformy nabízejí. Mohou používat chatboty jako nástroj k prozkoumávání nových nápadů, vývoji příběhů a dokonce spolupracovat s AI na vytváření jedinečného obsahu.
Hráči a fanoušci interaktivního vyprávění příběhů jsou další skupinou, která tíhne k chatbotům založeným na roleplay. Tito jednotlivci si užívají pohlcující zážitek z interakce s dynamickou, reagující postavou a schopnost utvářet příběh prostřednictvím svých voleb a akcí. Chatboty nabízejí úroveň interaktivity a personalizace, kterou tradiční videohry a knihy nemohou poskytnout.
Kromě toho mohou být k chatbotům založeným na roleplay přitahováni lidé, kteří jsou zvědaví ohledně technologií a umělé inteligence, jako způsob zkoumání schopností a omezení AI. Mohou se zapojit do těchto platforem, aby lépe pochopili, jak funguje strojové učení a zpracování přirozeného jazyka, a aby na vlastní oči viděli pokroky dosažené v oblasti konverzační AI.
Jaké jsou možnosti praktického využití?
Praktické využití roleplay chatbotů je poměrně široké a zahrnuje několik oblastí. Většina z nich se zabývá konceptem A.I. a chatbotů obecně, a nasazení na formát roleplaye je jen jednou z podob. Tyto možnosti zahrnují:
- Terapie a mentální zdraví
- Chatboty mohou sloužit jako doplněk tradiční terapie ,[4] poskytovat emocionální podporu a pomáhat s procvičováním kognitivně-behaviorálních technik. [5]
- Pro lidi s úzkostí nebo sociální fobií mohou být bezpečným prostředím pro nácvik sociálních interakcí. [5:1]
- Chatboty zaměřené na specifické poruchy (deprese, PTSD apod.) mohou poskytovat cílenou podporu a edukaci.
- Vzdělávání a trénink
- Roleplay chatboty lze použít k simulaci různých scénářů pro trénink komunikačních a vyjednávacích dovedností.
- V jazykovém vzdělávání umožňují procvičovat konverzaci s "rodilým mluvčím".
- Medici a zdravotníci je mohou využít k procvičování komunikace s pacienty.
- Zábava a kreativita
- Roleplay s AI charaktery nabízí nové kreativní možnosti pro spisovatele, scénáristy, a vývojáře her.
- Chatboty mohou sloužit jako interaktivní společníci pro "DND-style" hry a podobné kreativní aktivity.
- Umělci mohou s jejich pomocí zkoumat nové nápady a získávat inspiraci.
A.I. ve formě chatbota obecně, ale i specificky roleplay A.I., mají velký potenciál obohatit obrovskou řadu oblastí, od duševního zdraví, přes vzdělávání, až po zábavu. Klíčové je pochopení aktuálních limitací a nedostatků. V současném stavu nejsou chatboty ještě ve většině odvětví připraveny plně nahradit lidi . Dávat jim tedy možnost naprosté autonomie je nevhodné, a v některých případech, nebezpečné. Další výzkum ukáže, jak nejlépe využít jejich možnosti ku prospěchu jednotlivců i celé společnosti.
LLM / A.I. systémy
Kontextové okno
Kontextové okno, také známé jako pozornostní okno, je klíčovou součástí velkých jazykových modelů (LLM) .[6] Určuje množství vstupních tokenů, které model zvažuje při generování výstupu. V podstatě Kontextové okno určuje, jak velkou část předchozího textu může model "vidět" a využít k vygenerování dalšího tokenu v sekvenci. LLM zpracovávají vstupní sekvence tím, že se zaměřují na relevantní informace v rámci kontextového okna, což jim umožňuje zachytit závislosti a generovat koherentní a kontextově vhodné výstupy. K tomu využívají "attention mechanismy" .[7]
Halucinace
Halucinace označují jev, kdy LLM generují obsah, který je nekonzistentní, fakticky nesprávný nebo zcela smyšlený .[8] Tento problém vzniká kvůli schopnosti modelů generovat věrohodně znějící text na základě vzorů naučených z trénovacích dat, bez skutečného porozumění přesnosti nebo relevanci informací. [8:1]
Omezení a jejich příčiny
Délka kontextového okna
Navzdory působivým schopnostem LLM podléhají určitým omezením, zejména pokud jde o velikost kontextového okna .[6:1] Primárním omezením je délka paměti, která se vztahuje k maximálnímu počtu tokenů, které mohou být zahrnuty v kontextovém okně. Toto omezení vyplývá z výpočetních omezení a architektury modelů. Nutno však poznamenat, že k datu 8.3.2024 už to jsou měsíce po vzniku nové architektury, která tuto limitaci s délkou kontextového okna, a dokonce i s pozorností (tj. "zapomínáním" v rámci obsahu v kontextovém okně) do značné míry stírá. Využívá ji jak nově vydaný model "Claude 3" od společnosti Anthropic ,[9] který v benchmarcích poráží všechny současně dostupné modely ,[9:1] tak Gemini Ultra 1.5 od Googlu, který je stále v betě. [10]
Se zvětšující se velikostí kontextového okna exponenciálně roste výpočetní složitost a paměťové nároky modelu .[11] LLM potřebují ukládat a zpracovávat obsah a pozornostní váhy pro každý token v kontextovém okně, což je tím náročnější, čím delší je obsah v kontextovém okně. V důsledku toho praktické důvody, jako je dostupný výpočetní výkon, kapacita paměti a čas učení, omezují maximální velikost kontextového okna.
Faktory přispívající k halucinacím
-
Nedostatek explicitního ověřování faktů: LLM inherentně neověřují pravdivost informací, které generují. Spoléhají na vzory a asociace naučené z trénovacích dat, které mohou obsahovat nepřesné nebo zavádějící informace. [8:2]
-
Nedostatečný kontext: Omezené kontextové okno v LLM může vést k halucinacím, když model generuje text na základě neúplných nebo nejednoznačných informací. Bez přístupu k úplnému kontextu může model dělat nesprávné předpoklady nebo generovat irelevantní obsah.
-
Přehnaná sebejistota: LLM mohou generovat text s vysokou mírou jistoty, i když jsou informace nesprávné nebo smyšlené. [8:3] Tato přehnaná sebejistota může ztěžovat detekci a filtrování halucinací.
Přístupy k překonání některých omezení
Redukce halucinací
Zmírňování halucinací v LLM je aktivní oblastí výzkumu. Některé přístupy zahrnují:
-
Vylepšená trénovací data: Pečlivý výběr vysoce kvalitních, fakticky přesných trénovacích dat může pomoci snížit výskyt halucinací. To zahrnuje filtrování nespolehlivých zdrojů a důraz na ověřené informace.
-
Mechanismy ověřování faktů: Začlenění explicitních kroků ověřování faktů, jako je křížové ověřování generovaného obsahu se spolehlivými znalostními bázemi nebo využití zpětné vazby od lidí, může pomoci identifikovat a opravit halucinace. [8:4]
-
Odhad nejistoty: Vývoj technik pro odhad nejistoty generovaného obsahu může pomoci označit potenciálně halucinované informace .[8:5] To umožňuje opatrnější používání výstupů LLM a podporuje další ověřování.
-
Přístupy s člověkem v procesu: Zapojení lidského dohledu a zásahů do procesu generování ("human-in-the-loop") může pomoci zachytit a opravit halucinace. Lidští odborníci mohou poskytovat zpětnou vazbu, ověřovat generovaný obsah a navádět model k přesnějším a spolehlivějším výstupům.

Zdroj: https://arxiv.org/abs/2309.06794v1
Rozšíření délky kontextového okna
K řešení omezení daných velikostí kontextového okna byly navrženy a implementovány různé přístupy. Jednou z běžných technik je použití vektorové databáze (Vector DB) k rozšíření délky paměti modelu. V tomto přístupu je vedle LLM udržována samostatná vektorová databáze. Vector DB ukládá páry klíč-hodnota, kde klíče představují sémantické reprezentace vstupních tokenů (obsah "vektorizovaný"/"embedovaný" skrze embeddings) a hodnoty obsahují odpovídající informace .[12] Při zpracování vstupní sekvence může LLM dotazovat Vector DB, aby získala relevantní informace nad rámec aktuálního kontextového okna. Tím model může přistupovat k informacím z mnohem většího kontextu a účinně tak rozšiřovat délku své paměti.
Vector DB funguje jako externí paměť, na kterou se LLM může odkazovat při generování výstupů. Umožňuje modelu načítat a využívat relevantní informace z předchozích vstupů nebo externích zdrojů znalostí, i když spadají mimo aktuální kontextové okno. Tento přístup umožňuje LLM zvládat dlouhodobější závislosti a udržovat koherenci v rozšířených sekvencích.
Použití Vector DB s sebou však přináší vlastní výzvy a omezení. Efektivita tohoto přístupu závisí na kvalitě a relevanci informací 1. uložených ve Vector DB, a 2. vytažených z Vector DB na základě semantického vyhledávání. Pro dosažení optimálních výsledků je zásadní správná indexace, vyhledávání (jak algoritmus, tak embeddings a normalizace inputu) a integrace získaných informací do generačního procesu modelu. Je to komplexní problém.
Návrh databáze
Schéma databáze
Poznámka: schéma popisuje jen zásadní tabulky, a z důvodu čitelnosti vynechává spoustu menších, určených k podpůrným funkcím.

Ukázka: popis databázové implementace Stripe plateb
Platební systém se točí kolem tří hlavních modelů: User, Subscription a Payment. Databáze je vytvořena v kontextu Stripe plateb a Stripe API, a je kompletně end-to-end typovaná, počínaje různými statusy ze Stripe plateb, a konče navázáním jednotlivých PlanId v databázi na konkrétní vlastnosti daných plánů v kódu.
- Model User:
- Každý uživatel je identifikován jedinečným polem id.
- Pole stripeCustomerId uchovává odpovídající ID zákazníka na Stripe pro daného uživatele.
- Pole planId označuje aktuální předplatný plán uživatele.
- Pole Subscription vytváří vztah jedna k mnoha mezi modely User a Subscription.
- Model Subscription:
- Každé předplatné je identifikováno jedinečným polem id.
- Pole userId vytváří vztah cizího klíče s modelem User, což označuje uživatele spojeného s tímto předplatným.
- Pole planId představuje předplatný plán pomocí výčtu PlanId, který definuje různé možnosti plánů.
- Pole stripeSubscriptionId uchovává odpovídající ID předplatného na Stripe.
- Pole status sleduje aktuální stav předplatného pomocí výčtu SubscriptionStatus.
- Pole currentPeriodStart a currentPeriodEnd uchovávají datum začátku a konce aktuálního předplatného období.
- Pole interval označuje fakturační interval (např. měsíční nebo roční) pomocí výčtu SubscriptionInterval.
- Model Payment:
- Každá platba je identifikována automaticky se zvyšujícím polem id.
- Pole stripeSubscriptionId asociuje platbu s konkrétním předplatným.
- Pole stripePaymentIntentId uchovává odpovídající ID platebního záměru na Stripe.
- Pole amount představuje částku platby v nejmenší měnové jednotce (např. centy).
- Pole currency označuje měnu platby pomocí výčtu Currency.
- Pole status sleduje stav platby pomocí výčtu PaymentStatus.
- Model StripeSession:
- Každá relace Stripe je identifikována jedinečným polem id.
- Pole planId představuje předplatný plán spojený s touto relací.
- Pole userId vytváří vztah cizího klíče s modelem User, což označuje uživatele spojeného s touto relací.
- Pole interval označuje fakturační interval pro tuto relaci.
- Pole stripeSubscriptionId uchovává odpovídající ID předplatného na Stripe, pokud je k dispozici.
Tok platby typicky zahrnuje následující kroky:
- Když uživatel zahájí předplatné, vytvoří se nový záznam StripeSession s vybraným planId, userId a interval.
- Uživatel je přesměrován na platební bránu Stripe k dokončení platby.
- Po úspěšné platbě se vytvoří nový záznam Subscription s odpovídajícím userId, planId, stripeSubscriptionId, status, currentPeriodStart, currentPeriodEnd a interval.
- Vytvoří se také nový záznam Payment pro sledování detailů platby, včetně stripeSubscriptionId, stripePaymentIntentId, amount, currency a status.
- Záznam User se aktualizuje s poli stripeCustomerId a planId pro odražení aktuálního stavu předplatného uživatele.
Tento návrh umožňuje sledování uživatelských předplatných, správu předplatných plánů a zaznamenávání platebních transakcí s použitím Stripe jako platební brány a backendovým zpracováním pro realizaci logiky, parsování odpovědí z API atd.. Výčty SubscriptionStatus a PaymentStatus poskytují flexibilitu při správě různých stavů předplatného a plateb, včetně stavu, kdy je předplatné nezaplaceno, po splatnosti, zrušeno apod.
Potřebné technologie
-
Databáze: PostgreSQL a Prisma ORM: PostgreSQL je robustní a výkonná open-source relační databáze, která poskytuje spolehlivé ukládání dat. Prisma ORM zjednodušuje interakci s databází a umožňuje psát databázové dotazy pomocí intuitivního a typesafe API.
Velká výhoda Prisma ORM tkví ve faktu, že lze typescript modely jednotlivých tabulek a relací využívat napříč celým projektem, a tím pádem lze předejít velké řadě chyb.
Na druhou stranu velká nevýhoda tkví v tom, že Prisma je neefektivní v generování SQL dotazů. Příkaz, který lze napsat v jednom dotazu, může vyjít i na tři dotazy. Z toho důvodu je oproti řešením, které jsou blíž čistému SQL, velmi pomalá. [13]
-
Vektorová databáze: Milvus: Vektorové databáze jsou optimalizovány pro ukládání a dotazování se na vektorová ("semantická") data. To umožňuje zakódovat ("embedovat") zprávy uživatel, a následně je semanticky získávat na základě aktuálně probíraného tématu, a přidávat je do kontextu LLM (RAG - Retrieval Augmented Generation).
-
Server: Contabo / Coolify: Self-hosting serveru pomocí Coolify na Contabo VPS poskytuje vyšší rychlost a lepší poměr cena/výkon oproti komerčním řešením.
-
CI/CD - pre-commit hook a Coolify: Implementace CI/CD (Continuous Integration/Continuous Deployment) pomocí pre-commit hooků a Coolify automatizuje proces nasazení. Pre-commit hooky pomáhají udržovat kvalitu kódu pomocí lintování a formátování, a Coolify umožňuje automatizované buildy, deploy status reporting, a nasazení aplikace.
-
Capacitor: Capacitor je open-source framework pro vytváření nativních mobilních aplikací pomocí webových technologií. Umožňuje snadné nasazení webové aplikace jako nativní aplikace pro různé platformy, což rozšiřuje dosah a přístupnost. Je využit pro zabalení webu do balíčků android a iOS aplikace, které jsou následně nahrány do App Store a Play Store.
-
Logování výstupu a výkonu LLM (Langfuse): Langfuse poskytuje nástroje pro logování a analýzu výstupu a výkonu LLM. Pomáhá monitorovat a optimalizovat chování modelů, identifikovat potenciální problémy a zlepšovat celkovou kvalitu a cenovou/výkonnostní efektivitu LLM.
-
Monitorování výkonu aplikace a chyb: Nástroje pro monitorování výkonu, jako je Sentry, umožňují sledovat a analyzovat chování aplikace v reálném čase. Pomáhají identifikovat úzká místa výkonu, chyby a výjimky, což umožňuje proaktivní řešení problémů a optimalizaci.
-
WebSocket server - Self-hosted Soketi na Contabo serveru: WebSocket server umožňuje obousměrnou komunikaci v reálném čase mezi klientem a serverem. Soketi je nadstavba nad Websoketama, která poskytuje různá užitečná API. Zároveň je oproti konkurenčním řešením, jako je Pusher, cenově výrazně lepší.
-
File/object storage (Minio): Minio (Simple Storage Service) poskytuje škálovatelné a spolehlivé úložiště objektů a souborů, kompatibilní s API S3 od Amazonu.
-
Překlady: Lingui a Crowdin: Lingui je knihovna pro internacionalizaci (i18n) v JavaScriptových aplikacích, která usnadňuje správu a integraci překladů. Oproti čisté knihovně i18n a konkurentům poskytuje značné množství API navíc. Crowdin je platforma pro správu překladů, která umožňuje efektivní lokalizaci aplikace do různých jazyků. Poskytuje UI, možnost kolaborace, a API pro push/pull jazykových souborů.
-
E-Mailový client (MXRoute): MXRoute poskytuje spolehlivé a bezpečné e-mailové služby. Umožňuje odesílání a přijímání e-mailů v rámci celé platformy.
-
Platby: Stripe: Stripe je platební brána, která umožňuje bezproblémovou integraci platebních funkcí do webové aplikace. Poskytuje bezpečné a škálovatelné zpracování plateb, správu předplatného a další funkce pro monetizaci platformy.
-
Autentifikace - Firebase: Firebase Authentication poskytuje snadno použitelné SDK a backend služby pro ověřování uživatelů. Nabízí zabezpečené a flexibilní metody ověřování, jako je e-mail/heslo, OAuth, nebo telefonní číslo.
Dalším důvodem pro její zvolení je fakt, že neoperuje na základě session tokenu v cookies, ale manuálně předaného ID v parametrech požadavku. Toto bylo důležité proto, že Capacitor nepodporuje cookies, a cílem aplikace bylo podporovat jak mobil, tak web.- Webové technologie:
- React: Populární JavaScriptová knihovna pro vytváření uživatelských rozhraní, založená na konceptu komponent.
- NextJS: React framework pro vytváření serverově renderovaných Reactových aplikací s možností statického generování a využitím Node.js - na backendu.
- NextUI: Knihovna komponent uživatelského rozhraní pro React postavená na premisách designového systému Atomic.
- tRPC: Typově bezpečný způsob vytváření robustních API serverů a klientských aplikací založený na gRPC.
- Zustand: Jednoduchá knihovna pro správu stavu v Reactových aplikacích.
- React-hook-form: Knihovna pro efektivní práci s formuláři v Reactu.
- AutoAnimate: Přidává automatické animace pro přidávání, odebírání a přesouvání prvků na stránce.
- Biome.js: Linter a formatter.
- dotenv-vault: Cloudový ástroj pro bezpečné ukládání a správu .env proměnnných.
- Husky: Umožňuje spouštět skripty při git příkazech pro lepší kontrolu kvality kódu.
- Tailwind: Objektivně lepší CSS alternativa.
- Zod: Knihovna pro deklarativní validaci a parsování dat.
- React-Query: Knihovna pro efektivní správu asynchronních dat v Reactových aplikacích.
- A další
- Webové technologie:
Praktická část
Struktura složek
Toto je kompletní výčet složek projektu. Každá jedna složka ve skutečnosti obsahuje množství souborů (dohromady více, než 300), ale pro účely kompaktnosti jsou zobrazeny pouze složky:
* src/
+ components/
- ActionBar/
- ActiveChatsDiscoverCategory/
- AppHeader/
- AppHeaderCharSettingsButton/
- AppPage/
- BasePage/
- bot-chat/
- BotChatContent/
- CharacterCard/
- CharsDiscoverCategory/
- CombinedPage/
- ContactUsForm/
- DiscoverHeader/
- ErrorPage/
- Footer/
- forum/
- ForumPostsDiscoverCategory/
- LoginPage/
- MyCharactersDiscoverCategory/
- Navbar/
- NsfwConfirmDialog/
- PageDescription/
- PagePadding/
- PageTitle/
- Persisted/
- PricingPage/
- PricingPlanCard/
- profile-page/
- PublicPage/
- roleplay-roulette/
- TagMultiSelect/
- TagRowSelector/
- ui/
- UserProfileModal/
+ emails/
- components/
- templates/
+ hooks/
+ lib/
+ pages/
+ providers/
+ server/
- ai/
- clients/
- helpers/
- jobs/
- lib/
- routers/
- shared/
+ services/
+ stores/
+ styles/
Implementace jednotlivých funkcionalit
Implementace funkcionality veřejného náhodného 1-to-1 chatu (Roleplay Roulette / RR Chat)
Aplikace obsahuje funkcionalitu veřejného 1-to-1 chatu, podobného veřejně známe chatovací aplikaci Omegle. Uživatelé chat otevřou, a jsou náhodně přiřazeni ("matchmaking") k jinému uživateli. Chat při otevření vygeneruje téma, kterého se uživatelé budou držet při společném hraní rolí ("roleplay"). Můžou se navzájem podívat na svůj profil nebo se přidat do přátel.
Kompletní schéma logického toku rouleplay roulette chatu:

Zjednodušené schéma zaměřené na logický tok přidávání uživatelů do fronty matchmaking:
Hlavní relevantní soubory
- Frontend:
- src/pages/roulette/index.tsx
- src/pages/roulette/chat.tsx
- src/hooks/useRRChannelConnector.ts
- src/hooks/useRRConnection.ts
- src/hooks/useRRMessages.ts
- Další utility komponenty, jako RRMessages, RRUserHeader, RRInput atd.
- Backend:
- src/server/routes/rr-chat/index.ts
- src/server/routes/rr-chat/searchUser.ts
- src/server/routes/rr-chat/sendMessage.ts
Ukázka kódu useRRConnection.ts

Popis
Když uživatel zahájí vyhledávání partnera pro chat, je volán endpoint searchUser. Tento endpoint nejprve zkontroluje, zda uživatel již nevyhledává chat. Pokud ano, je vyvolána chyba, aby se zabránilo duplicitnímu vyhledávání.
Poté se endpoint pokusí najít jiného uživatele, který je také ve frontě a čeká na partnera pro chat. Pokud je k dispozici jiný uživatel, vygeneruje se jedinečné jméno kanálu a náhodné počáteční téma. Oba uživatelé jsou poté přiřazeni k tomuto kanálu a vrací se data kanálu (jméno a téma). Pokud není k dispozici žádný jiný uživatel, aktuální uživatel je přidán do fronty.
Endpoint poté začne vyhledávat po určitou dobu (určenou POLLING_TIME_SECS), každou sekundu kontroluje, zda byl uživateli přiřazen kanál. Pokud je během doby vyhledávání nalezen kanál, vrátí se data kanálu. Pokud po uplynutí doby vyhledávání není přiřazen žádný kanál, uživatel je z fronty odstraněn a vrací se null.
Na frontendové části hook useRRChannelConnector spravuje proces vyhledávání. Když je zavolána funkce search, iniciuje se vyhledávání voláním mutace searchUser. Hook sleduje stav vyhledávání (searching, found, not-found nebo null) a data kanálu (pokud je nalezen).
Hook useRRConnection spravuje připojení k prezenčnímu kanálu Pusher. Když je poskytnuto jméno kanálu, přihlásí se k odběru kanálu a naslouchá různým událostem:
- pusher:subscription_succeeded: Indikuje úspěšné přihlášení k odběru. Pokud je v kanálu více než jeden člen, stav se nastaví na subscribed-w-user, jinak na subscribed-no-user.
- pusher:subscription_error: Indikuje chybu při přihlášení k odběru a stav se nastaví na subscribe-failed.
- pusher:member_removed: Indikuje, že člen opustil kanál, a stav se nastaví na subscribed-user-left.
- pusher:member_added: Indikuje, že se člen připojil ke kanálu. Pokud je v kanálu více než jeden člen, stav se nastaví na subscribed-w-user.
Komponenta RoleplayRoulette vykresluje rozhraní chatu. Využívá hooky useRRChannelConnector a useRRConnection ke správě stavů vyhledávání a připojení. Když je nalezen kanál, čeká krátkou dobu a kontroluje, zda se druhý uživatel připojil. Pokud ne, kanál se resetuje. Správu chatových zpráv zajišťuje hook useRRMessages, který přidává systémové zprávy na základě změn stavu připojení. Komponenta RRInput umožňuje uživateli odesílat zprávy a zahajovat nové vyhledávání. Zprávy se odesílají pomocí endpointu sendMessage, který na kanálu Pusher vyvolá událost message.
Celkový průběh lze shrnout následovně:
- Uživatel zahájí vyhledávání
- Backend vyhledá dostupného uživatele ve frontě (queue)
- Pokud je nalezen, oba uživatelé jsou přiřazeni ke kanálu; pokud ne, uživatel je přidán do fronty a vyhledává se přiřazení kanálu
- Frontend se přihlásí k odběru přiřazeného kanálu a naslouchá událostem členů
- Uživatelé si mohou navzájem posílat zprávy přes kanál
- Pokud uživatel odejde nebo není nalezen partner, kanál se resetuje a uživatel může zahájit nové vyhledávání.
Implementace funkcionality přidávání přátel
Aplikace umožňuje přidávat a odebrávat přátele. Přítele lze přidat z jejich veřejného profilu, a odebrat buď skrz jejich veřejný profil, nebo skrz dedikovanou stránku /friends.
Hlavní relevantní soubory
- friends.tsx: Stránka obsahující seznam přátel
- src/pages/user/[username].tsx: Veřejné profily uživatele, skrz které si lze přítele přidat
- Složka src/server/routers/friends:
- `addFriends.tsx
- isFriend.tsx
- removeFriend.tsx
- retrieveAllFriends.tsx
Stránka přátelé
- Komponenta FriendsPage zobrazuje seznam přátel uživatele.
- Načítá seznam přátel pomocí dotazu api.friends.getAll.
- Pokud uživatel nemá žádné přátele, zobrazí se zpráva o tom.
- Pro každého přítele zobrazuje jeho avatar, uživatelské jméno a bio nebo jméno.
- Každá karta přítele má tlačítko "Odstranit", které umožňuje uživateli odstranit přítele ze svého seznamu.
- Kliknutím na avatar nebo uživatelské jméno přítele se přejde na jeho stránku s uživatelským profilem.
Ukázka kódu friends.tsx
const FriendsPage = () => {
const utils = api.useUtils();
const { data: friends, isLoading } = api.friends.getAll.useQuery();
const removeFriend = api.friends.remove.useMutation({
onSuccess: () => utils.friends.getAll.invalidate(),
});
const handleRemoveFriend = async (friendUsername: string) => {
await removeFriend.mutateAsync({ username: friendUsername });
};
return (
<AppPage backPath={paths.discover} title={t**Friends**}>
{friends?.map((friend) => (
<CardBody className={"flex flex-row gap-4"}>
<Link href={paths.userProfile(friend.username)}>
<Avatar src={friend.image} size="lg" />
</Link>
<div>
<p>{friend.username}</p>
<p className={"text-gray-400"}>{friend.bio || friend.name}</p>
</div>
<Button
color={"danger"}
size={"sm"}
className={"ml-auto mt-auto"}
onClick={() => handleRemoveFriend(friend.username)}
>
Remove
</Button>
</CardBody>
))}
</AppPage>
);
};
export default FriendsPage;
Implementace funkcionality nabídky komunitních postav
Aplikace obsahuje funkcionalitu nabídky komunitních postav. Každá postava, kterou uživatel vytvoří, má specifický modifikátor přístupu: public, friends, a private. V případě, že uživatel vytvoří postavu, která je označena jako public, bude součástí veřejného feedu komunitních postav zobrazeného na hlavní stránce.
Postavy zobrazeny na discover page podporují následující funkce:
- Výběr zdroje postavy (oficiální postavy, komunitní postavy)
- Textový filtr (hledání v názvu, popisu a jméně postavy)
- NSFW (zobrazit/skrýt NSFW postavy)
- Tagy postavy ("anime", "books" apod.)
- Limit výsledků a offset pro stránkování
Postavy se řadí na základě data nahrání, získávají se ze serveru postupně ("lazy-loading"), a refetchují se jen, pokud je to nezbytně potřeba ("stale data").
U každé postavy jsou zobrazeny základní atributy: Obrázek, jméno a popis. Postavu lze rozkliknout a přejít tak do výběru režimu chatu, a posléze do samotného chatového prostřední.
Hlavní relevantní soubory
- Backend:
- src/server/routers/bots/getAllBots.ts
- src/server/routers/bots/getBot.ts
- src/server/routers/bots/getAllUsedBots.ts
- src/server/routers/bots/getUserBots.ts
- src/server/routers/bots/getBotByChatId.ts
- src/server/routers/bots/create: createBot.ts
- src/server/routers/bots/getPopularTags.ts
- src/server/routers/bots/getUsedChatModes.ts
- src/server/routers/bots/markView.ts
- src/server/routers/bots/rateBot.ts
- Frontend:
- src/pages/discover.tsx
- src/components/MyCharactersDiscoverCategory/index.tsx
- src/components/CharacterCard/index.tsx
Ukázkový kód MyCharactersDiscoveryCategory.tsx
type GetUserBotsOutput = inferRouterOutputs<AppRouter>["bots"]["getUserBots"];
export const MyCharactersDiscoverCategory = () => {
const { _ } = useLingui();
const { data: bots } = api.bots.getUserBots.useQuery(
{},
{
// will refetch only if data is stale.
refetchOnMount: true,
staleTime: parse("10 min"),
},
);
return (
<div className="">
<Title description={t**Characters you created**} bold icon={IoIosHeart} className="mb-3">
<Trans>Your Characters</Trans>
{bots && bots.length > 0 && (
<Tooltip content={_(msg**Create a new character**)}>
<Button as={NextLink}
href={paths.createBot}
color={"primary"}
className={"ml-auto md:ml-1 mr-1"}
size="sm"
isIconOnly
>
<AiOutlinePlus fontSize={25} fontWeight={700} />
</Button> </Tooltip> )}
</Title>
{bots && bots.length === 0 && (
<div
className={
"border border-neutral-600 rounded-lg w-52 h-52 bg-neutral-400/5 flex flex-col justify-center text-center items-center p-8"
}
>
<Tooltip closeDelay={0} content={_(msg**Create a new character**)}>
<Button as={NextLink}
href={paths.createBot}
color={"primary"}
className={"font-boldtext-primary-500"}
size="sm"
// @ts-ignore
variant={null}
isIconOnly
>
<AiOutlinePlus fontSize={25} fontWeight={1000} />
</Button> </Tooltip> <p className={"justify-end text-foreground-500"}>Create your first character</p>
</div> )}
<CharacterList bots={bots} />
</div> );
};
const CharacterList = (props: { bots?: GetUserBotsOutput | null }) => {
return (
<div className="flex w-full flex-row gap-5 overflow-scroll overflow-x-visible">
{props.bots?.map((bot) => {
return (
<CharacterCard key={bot.id + bot.createdAt} character={bot} showVisibility={true} />
);
})}
{!props.bots && <CharacterCardSkeleton inline count={2} />}
</div>
);
};
Ukázkový screenshot

Implementace funkcionality statistik postav
Aplikace obsahuje funkcionalitu zobrazování základních statistik o postavě. Tyto statistiky zahrnují
- Počet zobrazení
- Počet likes
- Počet dislikes
Každý uživatel může dát jakékoli veřejné postavě libovolný počet likes a dislikes. Ty jsou pak veřejně zobrazovány na hlavní stránce postavy.
Hlavní relevantní soubory
- Frontend:
- src/pages/character/[botId].tsx
- Backend:
- src/server/routes/bots/rateBot.ts
- src/server/routes/bots/markView.ts
- src/server/routes/bots/getBot.ts
Ukázkový kód rateBot.ts
export default protectedProcedure
.input(
z.object({
botId: z.string(),
rate: z.enum(["like", "dislike"]),
}),
)
.mutation(async ({ input, ctx }) => {
const { botId } = input;
const bot = await ctx.prisma.bot.findFirst({
where: {
id: botId,
},
});
if (!bot) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Bot not found.",
});
}
// Increment the view count of the bot
await ctx.prisma.bot.update({
where: { id: botId },
data: {
likes: input.rate === "like" ? { increment: 1 } : undefined,
dislikes: input.rate === "dislike" ? { increment: 1 } : undefined,
},
});
return { success: true };
});
Implementace funkcionality externího sdílení zpráv z chatu
Aplikace obsahuje funkcionalitu sdílení částí zpráv z chatu s postavou. Uživatel může pomocí dlouhého přidržení spustit režim výběru zpráv, a následně pomocí klepnutí vybrat zprávy, které chce sdílet. Vybrané zprávy jsou barevně zvýrazněny. Pro sdílení lze klepnout na tlačítko "Share" v menu, které se zobrazí na spodu obrazovky, nebo selekci zrušit klepnutím na tlačítko zavření.
Při klepnutí na tlačítko sdílení se otevře nativní vyskakovací okno systému, a uživateli je umožněno dané zprávy sdílet. Příklad exportovaného chatu:
Edward Elric:
Alchemy, adventure, and a quest for the Philosopher's Stone. Ready to join me on this journey?
Matěj Pešl:
Hell yes I am. What awaits us?
Edward Elric:
With our shared passion for the unseen, we'll unlock secrets, confront obstacles, and forge our paths with alchemical power. Don't underestimate my dedication—or my automail arm and leg! Together, we'll push the boundaries of what's possible. So, are you ready to unravel the mysteries of this world with me, partner?
Matěj Pešl:
Let's go. You go first.
Edward Elric:
In the name of the greatest alchemists, we march on! Always remember, true alchemy requires both the mind and the heart working together. As we traverse this path, keep your wits about you and let's learn from each other. The journey may be filled with challenges, but I believe in us. Let's make our mark as alchemical pioneers! Your turn—what wisdom will you bring to our alliance?
Hlavní relevantní soubory
- Frontend:
- src/pages/character/[botId]/[mode].tsx
- src/components/BotChatContent/BotChatContent.tsx
- src/components/BotChatContent/MessageSelection.tsx
Ukázkový kód funkce handleShare
const handleShare = async () => {
const messagesString = selectedMessages.reduce((acc, curr) => {
const { author, message } = curr;
const authorName = author.name;
return `${acc}${authorName}:\n${message}\n\n`;
}, "");
if (!Capacitor.isNativePlatform()) {
await navigator.clipboard.writeText(messagesString);
toast(t`Copied messages to clipboard!`, {
type: "success",
autoClose: 2000,
pauseOnHover: false,
});
} else {
await Share.share({
title: "Waifuu chat",
text: messagesString,
dialogTitle: "Share this chat with a friend!",
});
}
setSelectedMessages([]);
};
Ukázkový screenshot

Implementace funkcionality veřejných uživatelských profilů
Veřejná stránka uživatelského profilu a její dostupné funkce
Veřejná stránka uživatelského profilu je centrálním místem, kde mohou ostatní uživatelé zobrazit informace o konkrétním uživateli a jeho veřejně dostupné postavy. Stránka se skládá z několika klíčových prvků:
- Hlavička profilu:
- Profilová fotka a uživatelské jméno
- Tlačítka "Sdílet" a "Přidat přítele"
- Informační karty:
- Bio: Zobrazuje biografii uživatele, pokud ji má nastavenou.
- Postavy: Zobrazuje veřejně dostupné postavy, které uživatel vytvořil (pokud nějaké). Každá postava je reprezentována kartou s obrázkem, jménem a popisem.
Funkce:
- Sdílení profilu:
- Tlačítko "Sdílet" umožňuje sdílet odkaz na profil uživatele.
- Na nativních platformách (mobilní zařízení) se otevře dialogové okno pro sdílení s předvyplněným textem a odkazem.
- Na webových platformách se odkaz zkopíruje do schránky a zobrazí se oznámení "Odkaz zkopírován do schránky!"
- Přidání/odebrání přítele:
- Tlačítko "Přidat přítele" umožňuje přidat uživatele do seznamu přátel.
- Pokud je uživatel již v seznamu přátel, tlačítko se změní na "Odebrat přítele" a umožňuje odebrat uživatele ze seznamu přátel.
- Zobrazení postavy:
- Kliknutím na kartu postavy je uživatel přesměrován na stránku chatu s danou postavou.
Technické detaily:
- Pokud uživatel neexistuje, je uživatel přesměrován na stránku "Objevit".
- Během načítání dat se zobrazují zástupné prvky (Skeleton), aby se zlepšila uživatelská zkušenost.
- Stránka je responzivní a přizpůsobuje se různým velikostem obrazovky.
Hlavní relevantní soubory
- Frontend:
- src/pages/user/[username].tsx
- src/components/profile-page/InfoCards.tsx
- src/components/profile-page/Header.tsx
- Backend:
- getPublic.ts
Ukázkový kód getPublic.ts
import { publicProcedure } from "@/server/lib/trpc";
import { z } from "zod";
export default publicProcedure
.input(
z.object({
username: z.string(),
}),
)
.query(async ({ input, ctx }) => {
const user = await ctx.prisma.user.findFirst({
where: {
username: {
mode: "insensitive",
endsWith: input.username,
},
},
include: {
Bot: {
where: {
visibility: "PUBLIC",
},
},
},
});
if (!user) return null;
// Hand-pick the returned properties, so we don't leak sensitive information.
return {
id: user.id,
name: user.name,
image: user.image,
bots: user.Bot,
username: user.username,
bio: user.bio,
};
});
Ukázkový screenshot

Implementace funkcionality realtime veřejného skupinového chatu
Veřejný chat je funkce, která uživatelům umožňuje komunikovat s ostatními uživateli v reálném čase. Tato funkce je téměř zcela řízena pomocí služby Pusher, která zajišťuje real-time komunikaci mezi klientem a serverem.
Když uživatel odešle zprávu, funkce handleSendMessage vytvoří nový objekt zprávy s informacemi o uživateli, obsahem zprávy a časovým razítkem. Tato zpráva je poté odeslána na backend pomocí mutace sendMessageMutation, která ji odešle do kanálu "presence-global-chat" přes Pusher.
Když je přijata nová zpráva přes Pusher, posluchač událostí "message" aktualizuje stav messages a přidá novou zprávu do seznamu.
Hlavní relevantní soubory
- Frontend:
- src/pages/global-chat.tsx
- src/pages/global-chat.tsx
- Backend:
- src/server/routers/rr-chat/sendMessage.ts
Ukázkový kód funkce connectToPusher
const connectToPusher = async () => {
const pusher = await getPusherClient();
const channel = pusher.subscribe("presence-global-chat");
channel.bind("user-joined", (data: any) => {
console.log(data);
console.log("user joined");
setOnlineUsers(data.count);
});
channel.bind("user-left", (data: { count: number }) => {
setOnlineUsers(data.count);
});
channel.bind("message", (data: { from: string; message: Message }) => {
console.log("A", data);
setMessages((prevMessages) => {
if (prevMessages.some((msg) => msg.timestamp === data.message.timestamp))
return prevMessages;
return [...prevMessages, data.message];
});
});
setShowLoading(false);
return () => {
pusher.unsubscribe("presence-global-chat");
pusher.disconnect();
};
};
Závěr
Cílem této práce bylo vytvořit webovou a mobilní aplikaci umožňující uživatelům chatovat s fiktivními AI postavami a sdílet tyto zážitky s ostatními. Teoretická část prozkoumala rostoucí zájem o roleplay chatboty, jejich potenciální využití a technické aspekty velkých jazykových modelů, které je poháněly. Praktická část se zaměřila na implementaci klíčových funkcí, jako je veřejný náhodný chat, správa přátel, nabídka komunitních postav, statistiky postav, sdílení zpráv, veřejné profily a realtime skupinový chat.
Aplikace byla vyvinuta s využitím moderních webových technologií, jako jsou React, Next.js a PostgreSQL s Prismou. Mobilní verze byla umožněna díky frameworku Capacitor. Integrace služeb, jako Pusher pro realtime komunikaci, Stripe pro platby a Firebase pro autentizaci, rozšířila funkcionalitu aplikace.
Výsledná aplikace poskytuje zajímavé zážitky z interakce s AI postavami a podporuje kreativitu a sociální vazby mezi uživateli. Umožňuje sdílet a objevovat nové postavy, komunikovat s ostatními v reálném čase a začlenit se do komunity. Ačkoli aplikace stále čelí některým omezením, jako je velikost kontextového okna a halucinace modelů, implementované techniky, jako vektorové databáze, pomáhají tyto výzvy zmírňovat. Tato práce ukázala, jak mohou být moderní webové a mobilní technologie využity k vytvoření atraktivní a uživatelsky přívětivé platformy pro interakci s umělou inteligencí. Budoucí vývoj v oblasti velkých jazykových modelů a souvisejících technik přinese další vylepšení a nové možnosti pro obohacení uživatelského zážitku.
Seznam použité literatury a zdrojů
Seznam použitých symbolů a zkratek
- A.I. - Umělá inteligence (Artificial Intelligence)
- LLM - Velké jazykové modely (Large Language Models)
- RAG - Retrieval Augmented Generation
- PTSD - Posttraumatická stresová porucha (Post-Traumatic Stress Disorder)
- DND - Dungeons & Dragons
- ORM - Objektově-relační mapování (Object-Relational Mapping)
- SQL - Structured Query Language
- CI/CD - Continuous Integration/Continuous Deployment
- NSFW - Not Safe For Work
- API - Application Programming Interface
- UI - Uživatelské rozhraní (User Interface)
- gRPC - Remote Procedure Call
- CSS - Cascading Style Sheets
- VPS - Virtuální privátní server (Virtual Private Server)
- SDK - Sada pro vývoj softwaru (Software Development Kit)
- OAuth - Open Authorization
Seznam obrázků
Seznam příloh
P I Flash disk se zdrojovými kódy projektu
P II Návrh webu
https://www.sciencedirect.com/science/article/abs/pii/S1386505619307166?via%3Dihub ↩︎
https://www.scaler.com/topics/deep-learning/attention-mechanism-deep-learning/ ↩︎
https://blog.google/technology/ai/google-gemini-next-generation-model-february-2024/#context-window ↩︎
https://www.pinecone.io/blog/why-use-retrieval-instead-of-larger-context/ ↩︎
