CodeGym /Kursy /ChatGPT Apps /Interakcja z ChatGPT: follow‑upy i „dialogi wokół widżetu...

Interakcja z ChatGPT: follow‑upy i „dialogi wokół widżetu”

ChatGPT Apps
Poziom 3 , Lekcja 3
Dostępny

1. Czym są follow‑upy w ChatGPT App i po co są potrzebne

Zacznijmy od definicji po ludzku, a nie marketingowo. Follow‑up w kontekście ChatGPT App to programowo inicjowany kolejny krok w dialogu. Zwykle to krótka podpowiedź w formie przycisku: po kliknięciu zamienia się w nową wiadomość tekstową od użytkownika na czacie i uruchamia dalszy ciąg scenariusza rozmowy człowieka z SI.

Z punktu widzenia modelu follow‑up to po prostu jeszcze jedna wiadomość użytkownika. Zero magii: gdy użytkownik klika wasz przycisk „Pokaż tańsze opcje”, do historii czatu trafia coś w stylu „Pokaż prezenty tańsze”. Model widzi to jako zwykłą wypowiedź użytkownika, stosuje system‑prompt i opisy tools, decyduje, które narzędzie wywołać, i być może ponownie renderuje wasz widżet już z innymi danymi.

Dlaczego to ważne:

  • Model myśli w kategoriach tekstu. Jeśli po kliknięciu po prostu wywołujecie tool bezpośrednio (bez wiadomości), omijacie główny „mózg” systemu i tracicie część kontekstu. Follow‑up zachowuje postęp rozmowy w historii i pomaga modelowi rozumieć, co dokładnie się dzieje.
  • Użytkownik pozostaje w znanej mu paradygmacie czatu: albo pisze wiadomości, albo naciska podpowiedzi. Follow‑upy to takie same „szybkie odpowiedzi” (quick replies), jak w nowoczesnych komunikatorach.
  • To główny most między waszym UI a tekstową częścią ChatGPT. Bez follow‑upów widżet zamienia się w niemy atol: użytkownik poklika i nie do końca wie, co zrobić dalej.

Można patrzeć na follow‑upy jak na przycisk „Następne pytanie do SI”, tyle że sformułowany przez was. Dalej będziemy traktować follow‑upy nie jako „kolejną funkcję UI”, ale jako podstawowy sposób budowania dialogu wokół widżetu.

2. „Dialog wokół widżetu”: conversational sandwich

Aby łatwiej zobaczyć, jak follow‑upy pomagają budować „dialog wokół widżetu”, warto wyobrazić sobie dialog jako kanapkę z trzech warstw:

  • na górze tekst ChatGPT przed widżetem (pre‑text),
  • w środku wasz widżet (UI),
  • na dole follow‑upy i kolejne wiadomości (post‑interaction).

Schematycznie:

sequenceDiagram
    participant U as Użytkownik
    participant G as ChatGPT
    participant W as Widżet

    U->>G: "Dobierz prezent dla siostry do 100$"
    G->>G: Postanawia wywołać App
    G->>U: Pre-text: "Zaraz otworzę GiftGenius i dobiorę pomysły"
    G->>W: Przekazuje toolOutput do renderowania
    W-->>U: Karty prezentów + przyciski follow‑up
    U->>W: Klik w "Pokaż tańsze opcje"
    W->>G: sendFollowUpMessage("Pokaż prezenty tańsze, do 50$")
    G->>G: Nowy przebieg modelu, wywołanie tools
    G->>W: Nowy toolOutput, odświeżony widżet

Pre‑text zazwyczaj w całości generuje model na podstawie system‑promptu i kontekstu: tam „wyjaśnia”, co zaraz się wydarzy („Zaraz otworzę aplikację, która pomoże wybrać prezent”). Sterowaniem tym poziomem zajmiemy się później, w module o instrukcjach.

Widżet to wasz zwykły komponent React: renderuje toolOutput, korzysta z widgetState, daje przyciski i wybory.

Warstwa post‑interaction to to, czym zajmujemy się dzisiaj. Przez follow‑upy i wysyłkę wiadomości wyznaczacie jasny kurs dalej: „Pokaż droższe”, „Zmień budżet”, „Rozpocznij dobór od nowa”, „Przejdź do finalizacji”.

Upraszczając, follow‑up to wypowiedź sterująca, która domyka cykl: UI → tekst → nowy UI.

3. Model techniczny: jak klik w follow‑up zamienia się w nowe wywołanie tool

Przyjrzyjmy się uważnie łańcuchowi zdarzeń „pod maską”. Wygodnie myśleć o tym jak o hybrydowym cyklu interakcji: klik → API → tekst w historii → nowa decyzja modelu → nowe wywołanie narzędzia → odświeżony UI.

Sekwencja wygląda mniej więcej tak:

  1. Użytkownik naciska przycisk w waszym widżecie.
  2. Widżet wywołuje API window.openai.sendFollowUpMessage lub, w warstwie React, wygodny hook‑opaskę useSendMessage (w przykładach poniżej użyjemy właśnie hooka). Argument to zwykły string: tekst, który jak gdyby napisał użytkownik.
  3. ChatGPT dodaje tę wiadomość do historii jako nowy user_message.
  4. Model wykonuje nowy przebieg: uwzględnia całą historię, w tym poprzedni toolOutput, i decyduje, czy wywołać narzędzie, które i z jakimi argumentami.
  5. Wasz backend/MCP wykonuje wywołanie tool i zwraca toolOutput.
  6. ChatGPT wyświetla nową odpowiedź: być może znów widżet (z nowymi danymi), być może tekst, a być może kombinację.

Ważne, że prawie nigdy nie chcecie bezpośrednio z widżetu wołać tego samego narzędzia, co model, z pominięciem tego cyklu. W przeciwnym razie model „gubi” krok: w historii nie ma tego żądania, które zainicjowało ponowne wywołanie. Później, przy dłuższych scenariuszach, skutkuje to poplątanym kontekstem.

Można to sprowadzić do krótkiego wzoru:

User Click → useSendMessage("...") → ChatGPT (LLM) → Tool Call → New toolOutput → Widget rerender

4. Rodzaje follow‑upów i kto je wymyśla

Zrozumieliśmy, co się dzieje „pod maską” po kliknięciu w follow‑up. Teraz zobaczmy, jakie w ogóle follow‑upy warto dawać użytkownikowi i jakie role mogą pełnić w scenariuszu.

W prawdziwej aplikacji istnieje kilka „rodzin” follow‑upów. Dzielę je wzdłuż różnych osi: statyczne vs dynamiczne, według roli (drill‑down, pivot, commit itd.).

W praktyce wygodniej posłużyć się tabelą.

Typ Co robi Przykład tekstu/przycisku
Podpowiadający (Suggestive) Pomaga, gdy użytkownik nie wie, o co zapytać dalej „Pokaż więcej pomysłów”, „Zawęź według zainteresowań”
Uściślający (Drill‑down / Parametric) Zawęża poprzednie zapytanie według parametrów „Taniej”, „Tylko prezenty cyfrowe”, „Tylko Nike”
Zwrot (Pivot) Zmienia gałąź scenariusza „Zacznij dobór od nowa”, „Pokaż prezenty dla dziecka”
Nawigacyjny (Navigation) Przenosi do innego kroku procesu „Przejdź do finalizacji”, „Wróć do wyboru”
Finalizujący (Commit) Potwierdza działanie „Zamów ten prezent”, „Zapisz dobór”

Z punktu widzenia źródła pomysłów są dwie duże grupy.

Po pierwsze, follow‑upy, które podpowiada sama aplikacja. To nasze przyciski w widżecie powiązane z logiką UI i danymi: na przykład „Pokaż podobne do [Nazwa prezentu]” albo „Filtruj tylko po zainteresowaniu travel”. Takie podpowiedzi można zakodować na sztywno (static) albo tworzyć dynamicznie na podstawie toolOutput (dynamic).

Po drugie, „natywne” podpowiedzi ChatGPT — małe chipy pod wiadomością, które model wymyśla sam. Nie kontrolujecie ich bezpośrednio; traktujcie je jako darmowy bonus, a nie niezawodny mechanizm. Wasza aplikacja i tak musi działać nawet bez nich.

W tej lekcji bardziej interesuje nas pierwsza grupa: przyciski i podpowiedzi, które rysujecie we własnym komponencie React i po kliknięciu wysyłacie przez sendFollowUpMessage.

5. Implementacja follow‑upów w React: przykład GiftGenius

Kontynuujmy naszą przykładową aplikację GiftGenius, która dobiera prezenty. Po wywołaniu narzędzia get_gift_ideas widżet otrzymuje toolOutput z listą prezentów. W poprzednich tematach zrobiliśmy już siatkę kart. Teraz dodamy sekcję follow‑upów.

Załóżmy, że w SDK mamy hooki useWidgetProps i useSendMessage. Nazwy są umowne, ale koncepcja odpowiada temu, jak wygląda to w implementacji referencyjnej:

import { useWidgetProps, useSendMessage } from '@/openai-apps';

export const GiftSuggestions: React.FC = () => {
  const { toolOutput } = useWidgetProps();
  const sendMessage = useSendMessage();

  const gifts = toolOutput?.data?.gifts ?? [];

  if (gifts.length === 0) {
    return <div>Nie znaleziono prezentów. Spróbuj zmienić zapytanie.</div>;
  }

  const handleCheaper = () => {
    sendMessage('Pokaż prezenty tańsze, do 50$');
  };

  const handleDigital = () => {
    sendMessage('Pokaż tylko prezenty cyfrowe: vouchery, subskrypcje itp.');
  };

  return (
    <div className="flex flex-col gap-4">
      <div className="grid grid-cols-2 gap-2">
        {gifts.map((gift: any) => (
          <GiftCard key={gift.id} item={gift} />
        ))}
      </div>

      <div className="border-t pt-3 text-sm">
        <div className="text-xs text-gray-500 mb-2">Co dalej?</div>
        <div className="flex flex-wrap gap-2">
          <button
            onClick={handleCheaper}
            className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
          >
            Taniej
          </button>
          <button
            onClick={handleDigital}
            className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
          >
            Tylko cyfrowe
          </button>
        </div>
      </div>
    </div>
  );
};

Ważnych jest tu kilka rzeczy.

Po pierwsze, sendMessage przyjmuje string — ten tekst pojawi się na czacie tak, jakby wprowadził go użytkownik. Wasza aplikacja nie powinna imitować „wewnętrznego” protokołu systemowego; po prostu formułujcie frazy tak, by model je zrozumiał.

Po drugie, sekcja follow‑upów jest wizualnie oddzielona (obramowanie u góry, drobny tekst „Co dalej?”), aby użytkownik rozumiał: to raczej ciąg dalszy dialogu niż elementy samej karty.

Po trzecie, przycisków jest niewiele — dwa. Rekomendacje UX sugerują trzymać się zakresu mniej więcej od dwóch do czterech opcji: to wystarczy, by pomóc, ale nie przeciążyć.

Dynamiczny follow‑up na podstawie danych

Załóżmy, że chcecie pozwolić użytkownikowi wejść głębiej w konkretny prezent: „Pokaż podobne do tego”. Wtedy tekst follow‑upu powinien wspominać odpowiedni obiekt i możecie wygenerować ten tekst w locie.

const handleShowSimilar = (giftTitle: string) => {
  sendMessage(
    `Pokaż podobne prezenty do "${giftTitle}", może w tym samym budżecie lub trochę drożej`
  );
};

I w karcie:

<button
  onClick={() => handleShowSimilar(gift.title)}
  className="mt-2 text-xs text-blue-600 underline"
>
  Pokaż podobne
</button>

W ten sposób realizujecie dynamiczny follow‑up powiązany z konkretnymi danymi z toolOutput. Właśnie takie przyciski czynią dialog „inteligentnym”, a nie tylko zbiorem uniwersalnych „Dalej / Wstecz”.

6. Dlaczego wysyłamy tekst, a nie wywołujemy tool bezpośrednio

Typowa myśl dewelopera frontendu: „Skoro mam useCallTool, po co wysyłać tekst? Mogę od razu wywołać get_gift_ideas z innymi parametrami”. Czasem faktycznie jest to potrzebne (więcej o tym będzie w module o wywoływaniu narzędzi (tools) z widżetu), ale domyślnie lepiej iść przez tekstowy follow‑up. Powody są bardzo praktyczne.

Po pierwsze, historia czatu pozostaje spójna. Na zewnątrz wszystko wygląda tak, jakby użytkownik sam napisał: „Pokaż prezenty tańsze”. Za tydzień otworzy historię i zrozumie, co się działo. Jeśli wszystko robiliście bezpośrednimi wywołaniami tool, pomiędzy wiadomościami użytkownika będą nagle pojawiać się różne widżety bez widocznych przyczyn.

Po drugie, model może podjąć dodatkowe decyzje. Na przykład zrozumieć, że zamiast ponownie wywoływać to samo narzędzie lepiej najpierw doprecyzować budżet: „Czy na pewno chcesz obniżyć budżet do 5$? Może zostawmy chociaż 20$?”. Takie elastyczne scenariusze stają się niemożliwe, jeśli sztywno kodujecie „klik → ten sam tool z innymi argumentami”.

Po trzecie, model może wywołać zupełnie inne narzędzie. Załóżmy, że użytkownik nacisnął „Skontaktuj z pomocą”, a wasz system‑prompt uczy model, że w takim przypadku trzeba wywołać create_support_ticket, a nie get_gift_ideas. Follow‑up jako tekst daje modelowi swobodę przełączenia się na inne narzędzie.

Dlatego praktyczna zasada modułu 3: po kliknięciu w UI w większości przypadków wysyłamy tekstowy follow‑up, a nie bezpośrednio tool. Bezpośrednie wywołania tool z widżetu zostawiamy na specyficzne przypadki, gdy na pewno nie potrzebujemy nowego kroku użytkownika w historii.

7. Powiązanie follow‑upów i stanów: nie rozjeżdżać UI i tekstu

Ciekawy problem: UI „żyje” swoim życiem, a tekst na czacie — swoim. Załóżmy, że po kliknięciu zmieniacie filtr wewnątrz widżetu i jednocześnie wysyłacie follow‑up. Jeśli zaktualizowaliście tylko UI, ale nie zapisaliście nowego stanu przez widgetState, przy kolejnym renderze ChatGPT odtworzy stary widgetState. W rezultacie widżet znów pokaże stare filtry, choć w historii czatu jest już krok „o tańsze”. To dość dziwne wrażenie.

Dlatego dobry wzorzec to: przy kliknięciu w follow‑up jednocześnie:

  1. aktualizować widgetState,
  2. wysyłać wiadomość follow‑up.

Przykład:

import { useWidgetState, useSendMessage } from '@/openai-apps';

type GiftWidgetState = {
  priceFilter?: 'any' | 'cheap' | 'premium';
};

export const GiftFollowups: React.FC = () => {
  const [widgetState, setWidgetState] = useWidgetState<GiftWidgetState>();
  const sendMessage = useSendMessage();

  const handleCheaper = () => {
    setWidgetState({ ...widgetState, priceFilter: 'cheap' });
    sendMessage('Pokaż prezenty tańsze, mniej więcej do 50$');
  };

  // ...
};

Teraz i ChatGPT, i wasz UI wiedzą, że filtr się zmienił. Jeśli model przebuduje widżet później, zobaczy zaktualizowany widgetState i może na przykład sformułować nowy pre‑text w stylu „Oto pomysły w bardziej budżetowym segmencie”.

8. Projektowanie dobrych follow‑upów

Dobry follow‑up to połowa sukcesu UX. Nie tylko „ładny”, ale oszczędza użytkownikowi energię poznawczą.

Jest kilka praktycznych zasad, które warto mieć zawsze przed oczami.

Po pierwsze, zwięzłość. Follow‑up to nie miejsce na poematy. Jedno krótkie zdanie, zrozumiałe bez kontekstu UI, zwykle jest idealne: „Taniej”, „Tylko premium”, „Zmień odbiorcę”. Jeśli potrzeba czegoś dłuższego, zastanów się, czy nie powinien to być normalny tekst GPT, a nie przycisk.

Po drugie, orientacja na działanie. Formułuj tak, by było jasne, co się wydarzy. „Więcej pomysłów” — w porządku. „Więcej o punkcie 2” — lepiej zamienić na „Opowiedz więcej o drugiej opcji” (model zrozumie, o co chodzi, nawet bez UI).

Po trzecie, ciąg dalszy scenariusza, a nie jego powtórka. Zamiast „Dobierz prezent jeszcze raz” lepiej „Zmień budżet” lub „Zmień hobby odbiorcy”. Follow‑up powinien przesuwać użytkownika naprzód lub w bok, a nie cofać do punktu wyjścia bez potrzeby.

Po czwarte, ograniczona liczba. Dwa–cztery przyciski pod widżetem — prawie zawsze wystarczy. Pasek z dziesięcioma opcjami zamienia się w egzamin z wyboru losu: użytkownik się gubi i nie naciska nic.

Na koniec, uwzględnij ton. Jeśli cała aplikacja komunikuje się przyjaźnie, dziwnie wstawiać przycisk „POTWIERDŹ ZAMÓWIENIE” capslockiem. Follow‑up jest częścią tego samego dialogu co tekst modelu; styl powinien być spójny.

9. „Dialogi wokół widżetu” w ogóle: kto za co odpowiada

Ważne, by nie postrzegać widżetu jako „głównego bohatera”, a ChatGPT jako „ramę wokół niego”. Jest odwrotnie: model dalej prowadzi dialog, a widżet to tylko jeden ze sposobów pokazania i skorygowania danych.

Typowy scenariusz dla GiftGenius wygląda tak:

  1. Użytkownik: „Potrzebny prezent dla siostry‑informatyczki do 100$”.
  2. Model: tekstowe wprowadzenie (pre‑text) — wyjaśnia, że zaraz otworzy GiftGenius i co on robi.
  3. Widżet: pokazuje zestaw pomysłów, daje follow‑upy.
  4. Użytkownik: albo sam pisze wiadomość, albo naciska przycisk follow‑up (np. „Pokaż tylko prezenty cyfrowe”).
  5. Model: interpretuje to jako tekst, wywołuje potrzebny tool, w razie potrzeby komentuje wynik (post‑text) lub ponownie pokazuje widżet.
  6. I tak w kółko, aż zadanie zostanie rozwiązane — aż do potwierdzenia wyboru, zamówienia itd.

Follow‑upy są tu klejem, który spaja każdy obieg tego cyklu. Bez nich użytkownik po widżecie zostaje w zawieszeniu: wszystko ładne, ale „co dalej” — niejasne.

W bardziej złożonych scenariuszach (workflows, agenci) follow‑upy pomagają modelować wieloetapowe lejki: „Najpierw wybierz odbiorcę”, „Teraz doprecyzuj budżet”, „Teraz potwierdź wybór”. Ale w tym module ważne jest po prostu zobaczyć: nawet najprostsza, jednotaktowa aplikacja bardzo zyskuje na dwóch–trzech przemyślanych podpowiedziach.

10. Praktyka: co warto zrobić od razu

Dobre ćwiczenie na utrwalenie — dopracuj swój obecny ćwiczebny widżet.

Jeśli masz już listę wyników (np. tych samych prezentów, hoteli czy dokumentów), dodaj pod nią mały blok „Co dalej?” z dwoma–trzema przyciskami. Postaraj się, by te przyciski odpowiadały typom z tabeli powyżej: jeden powinien być uściślający (drill‑down, np. „Taniej”), drugi — zwrotem (pivot, „Zmień odbiorcę”), trzeci — jeśli potrzeba — nawigacyjny (navigation, „Przejdź do finalizacji”).

W handlerach kliknięć wywołaj useSendMessage z sensownym tekstem w naturalnym języku, w razie potrzeby nie zapomnij zaktualizować widgetState. Następnie uruchom scenariusz w ChatGPT i zobacz, jak wygląda dialog: na ile stało się jaśniejsze, co robić po widżecie.

Spróbuj także celowo zrobić złe follow‑upy: długie, rozmyte, z dziesięcioma opcjami — i porównaj odczucia. To szybki sposób, by poczuć różnicę na sobie.

11. Typowe błędy przy pracy z follow‑upami

Błąd nr 1: „Milczący” widżet.
Deweloper robi świetny UI, ale w ogóle nie daje follow‑upów. Użytkownik widzi karty, myśli „no fajnie”, a dalej sam ma się domyślić, że można np. poprosić: „Pokaż taniej” albo „Zmień odbiorcę”. Większość osób po prostu na to nie wpada i odchodzi. Minimum jedna–dwie podpowiedzi „co dalej” pod widżetem rozwiązują ten problem.

Błąd nr 2: Za dużo przycisków.
Przeciwna skrajność — zasypać użytkownika dziesiątkami opcji: „Zmień budżet”, „Zmień zainteresowania”, „Zmień walutę”, „Zapisz dobór”, „Udostępnij znajomemu”, „Pokaż podobne”, „Zapytaj wsparcie” itd. Powstaje psychologiczny „szwedzki stół”, na którym trudno wybrać. Lepiej zacząć od dwóch–trzech najczęstszych działań, a resztę zostawić modelowi i zwykłemu tekstowi.

Błąd nr 3: Logika dialogu tylko we froncie.
Czasem próbuje się „optymalizować” i zamiast wysyłać follow‑up przez useSendMessage (albo niskopoziomowy sendFollowUpMessage) bezpośrednio wywołuje się ten sam tool z widżetu, aktualizując UI. W historii czatu nie ma wtedy ani słowa o tym, co zaszło. Po kilku krokach model zaczyna się gubić, a wy razem z nim. Właściwa droga: trzymać logikę dialogu na poziomie tekstu i narzędzi, a widżet — jako cienką warstwę UI.

Błąd nr 4: Nieoczywiste lub dwuznaczne sformułowania.
Przycisk „Jeszcze” bez kontekstu może oznaczać cokolwiek: jeszcze prezentów, jeszcze tekstu, jeszcze pieniędzy? Podobnie zwroty typu „Przelicz” czy „Przebuduj” nie są jasne ani dla modelu, ani dla użytkownika. Najlepsze follow‑upy są konkretne: „Pokaż więcej opcji w tym budżecie”, „Pokaż tylko prezenty cyfrowe”.

Błąd nr 5: Niesynchronizowany UI i tekst.
Klasyka: po kliknięciu „Taniej” zaktualizowaliście filtr w UI, ale nie wysłaliście follow‑upu na czat albo nie zaktualizowaliście widgetState. W rezultacie w historii nie ma kroków o zmianie budżetu, a przy kolejnym renderze widżetu filtr „cofnął się” z powrotem. Pojawia się wrażenie „zepsutego” interfejsu. Używajcie kombinacji setWidgetState + sendMessage, aby tekst i UI maszerowały równo.

Błąd nr 6: Próba kontrolowania „natywnych” podpowiedzi ChatGPT.
Czasem deweloperzy liczą, że ChatGPT sam wygeneruje potrzebne chipy‑follow‑up pod wiadomością, i nie dodają własnych. Ale te podpowiedzi nie są gwarantowane i nie są kontrolowane przez aplikację. Traktujcie je jako miły bonus, ale zawsze dawajcie własne, krytycznie ważne przyciski‑follow‑up w widżecie.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION