Search CTRL + K

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í:

  1. Terapie a mentální zdraví
  1. Vzdělávání a trénink
  1. Zábava a kreativita

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]

retrieval-accuracy-2.png
Zdroj: https://www.researchgate.net/publication/356309523_Reducing_slice_cross-talk_effect_in_metamaterial_assisted_fast_spin-echo_MRI

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

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í:

llm-hallucinations-diagram-2.png
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.

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.

waifuu-db-diagram-simple-overview.png

Ukázka: popis databázové implementace Stripe plateb

Userid: Stringname: Stringemail: Stringimage: Stringusername: StringstripeCustomerId: StringpreferredModelId: StringplanId: PlanIdcreatedAt: DateTimeSubscriptionid: StringuserId: StringplanId: PlanIdstripeSubscriptionId: Stringstatus: SubscriptionStatuscurrentPeriodStart: DateTimecurrentPeriodEnd: DateTimeinterval: SubscriptionIntervalcreatedAt: DateTimeupdatedAt: DateTimePaymentid: IntstripeSubscriptionId: StringstripePaymentIntentId: Stringamount: Intcurrency: Currencystatus: PaymentStatuscreatedAt: DateTimeupdatedAt: DateTimeStripeSessionid: StringplanId: PlanIduserId: Stringinterval: SubscriptionIntervalstripeSubscriptionId: StringcreatedAt: DateTimePlanIdSUBSCRIPTION_PLAN_FREE_V1SUBSCRIPTION_PLAN_PLUS_V1SUBSCRIPTION_PLAN_PRO_V1SubscriptionStatusincompleteincomplete_expiredtrialingactivepast_duecanceledunpaidpausedPaymentStatussucceededpendingfailedcanceledrequires_payment_methodrequires_confirmationrequires_actionprocessingSubscriptionIntervalmonthyearCurrencyUSDCZKEUR

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. Když uživatel zahájí předplatné, vytvoří se nový záznam StripeSession s vybraným planIduserId a interval.
  2. Uživatel je přesměrován na platební bránu Stripe k dokončení platby.
  3. Po úspěšné platbě se vytvoří nový záznam Subscription s odpovídajícím userIdplanIdstripeSubscriptionIdstatuscurrentPeriodStartcurrentPeriodEnd a interval.
  4. Vytvoří se také nový záznam Payment pro sledování detailů platby, včetně stripeSubscriptionIdstripePaymentIntentIdamountcurrency a status.
  5. 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


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:

waifuu-db-rr-chat-full-schema.png

Zjednodušené schéma zaměřené na logický tok přidávání uživatelů do fronty matchmaking:

User initiates searchIs user already polling?yesnoThrow errorAvailable user in queue?yesnoGenerate channel dataAssign both users to channelReturn channel dataAdd user to queueChannel assigned?yesnoReturn channel dataWait 1 secondno channel assignedPoll for channel assignmentRemove user from queueReturn null
Hlavní relevantní soubory
Ukázka kódu useRRConnection.ts

code-1.png

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:

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ě:

  1. Uživatel zahájí vyhledávání
  2. Backend vyhledá dostupného uživatele ve frontě (queue)
  3. 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
  4. Frontend se přihlásí k odběru přiřazeného kanálu a naslouchá událostem členů
  5. Uživatelé si mohou navzájem posílat zprávy přes kanál
  6. 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
Stránka přátelé
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:

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
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

waifuu-discover-screenshot-pc.png|500

Implementace funkcionality statistik postav

Aplikace obsahuje funkcionalitu zobrazování základních statistik o postavě. Tyto statistiky zahrnují

Hlavní relevantní soubory
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
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

waifuu-chat-screenshot.png|300

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ů:

  1. Hlavička profilu:
    • Profilová fotka a uživatelské jméno
    • Tlačítka "Sdílet" a "Přidat přítele"
  2. 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:

  1. 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!"
  2. 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.
  3. Zobrazení postavy:
    • Kliknutím na kartu postavy je uživatel přesměrován na stránku chatu s danou postavou.

Technické detaily:

Hlavní relevantní soubory
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

waifuu-profile-screenshot.png|300

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.

UserFrontendBackendPusherConnect to chatSend messageDisplay messagesChannel subscribedNew message receivedUpdate messages state
Hlavní relevantní soubory
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

Seznam obrázků

Seznam příloh

P I Flash disk se zdrojovými kódy projektu
P II Návrh webu


  1. https://www.demandsage.com/character-ai-statistics/ ↩︎

  2. https://humanfactors.jmir.org/2022/4/e38876 ↩︎

  3. https://academic.oup.com/hcr/article/48/3/404/6572120 ↩︎

  4. https://www.sciencedirect.com/science/article/abs/pii/S1386505619307166?via%3Dihub ↩︎

  5. https://is.muni.cz/th/ndw0z/BP_-_Dvorakova.pdf ↩︎ ↩︎

  6. https://arxiv.org/abs/2311.04939 ↩︎ ↩︎

  7. https://www.scaler.com/topics/deep-learning/attention-mechanism-deep-learning/ ↩︎

  8. https://arxiv.org/abs/2309.06794 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  9. https://www.anthropic.com/news/claude-3-family ↩︎ ↩︎

  10. https://blog.google/technology/ai/google-gemini-next-generation-model-february-2024/#context-window ↩︎

  11. https://www.pinecone.io/blog/why-use-retrieval-instead-of-larger-context/ ↩︎

  12. https://www.pinecone.io/learn/vector-database/ ↩︎

  13. https://orm.drizzle.team/benchmarks ↩︎