Blog JSystems - uwalniamy wiedzę!

Szukaj

Kurs Claude Code — Lekcja 10 z 11

Ten artykuł jest częścią bezpłatnego kursu Claude Code — największej polskojęzycznej serii 11 lekcji dla programistów. Od pierwszego uruchomienia do autonomicznych agentów AI.

<-- Spis wszystkich lekcji
<-- Lekcja 9: Claude na produkcji

UX aplikacji z Claude - jak projektować interfejsy AI które działają i budują zaufanie

Claude CodeSeria Claude Code · JSystems

Użytkownik czeka 8 sekund na odpowiedź AI i myśli że coś się zawiesiło. Zamyka zakładkę. Tracisz klienta. Tego da się uniknąć - i pokażemy Ci jak.

Czego się nauczysz z tego artykułu:

Najlepszy model AI w aplikacji z kiepskim UX to przepis na frustrację użytkownika. Aplikacje z LLM mają unikalne wyzwania designu: model odpowiada z opóźnieniem, może się mylić, odpowiedzi są nieprzewidywalnej długości, a użytkownik nie wie jak formulować zapytania. Ten artykuł zbiera lekcje z realnych wdrożeń aplikacji AI - co działa, co nie i dlaczego.

Słowniczek - terminy używane w artykule

Kilka pojęć które warto znać zanim przejdziesz dalej:

  • UX (User Experience) - doświadczenie użytkownika. Jak czuje się używając Twojej aplikacji: intuicyjność, szybkość, przejrzystość
  • Latencja - czas oczekiwania. Od kliknięcia "wyślij" do pierwszej odpowiedzi
  • LLM - Large Language Model (Duży Model Językowy). Claude, GPT i podobne to LLM-y
  • RAG - Retrieval Augmented Generation. Technika gdzie model korzysta z zewnętrznych dokumentów jako kontekstu
  • Halucynacja - gdy model AI wymyśla nieistniejące fakty z przekonaniem że są prawdziwe
6 problemów UX i ich rozwiązania

Problem #1: Latencja - użytkownik nie wie czy coś się dzieje

Claude Sonnet odpowiada typowo w 2-8 sekund. Dla użytkownika przyzwyczajonego do natychmiastowych odpowiedzi - to wieczność. Badania UX pokazują że po 3 sekundach oczekiwania użytkownicy zaczynają wątpić czy aplikacja działa.

Przed i po - konkretny przykład:

ZLY UX (przed) DOBRY UX (po)
Użytkownik klika "Wyślij". Nic się nie dzieje. Nic. Nic. 4 sekundy ciszy. Użytkownik klika ponownie (myśli że nie wysłało). Dostaje dwie odpowiedzi naraz. Użytkownik klika "Wyślij". Natychmiast pojawia się animacja "Claude myśli...". Po 0.5s - pierwsze słowa odpowiedzi zaczynają się pojawiać jedno po drugim. Użytkownik widzi postęp.
Pełna odpowiedź pojawia się nagle po 6 sekundach. Wygląda jak błąd strony. Odpowiedź płynie przez 6 sekund. Użytkownik czyta już podczas gdy Claude pisze.
Bez streamingu
Asystent AI
Napisz funkcję logowania
Przetwarzanie…
def login(user, pwd):
  u = db.find(user)
  if u and verify(pwd, u.hash):
    return token(u)
  raise AuthError()
Ze streamingiem
Claude
Napisz funkcję logowania
Claude myśli
def login(user, pwd):
  u = db.find(user)
  if u and verify(pwd, u.hash):
    return token(u)
  raise AuthError()
Ten sam czas odpowiedzi - inny odbiór. Streaming pokazuje postęp, więc czekanie nie boli.

Streaming - kluczowy pattern dla AI

Nigdy nie czekaj na pełną odpowiedź przed wyświetleniem. Streaming daje użytkownikowi sygnał że coś się dzieje i znacząco poprawia perceived performance - postrzeganą szybkość, nawet jeśli całkowity czas jest taki sam.

Czym jest "perceived performance"? To subiektywne odczucie szybkości - nie to ile faktycznie trwa, ale jak długo czuje się że trwa. Aplikacja która pokazuje pierwsze słowa po 0.3s i resztę po 3s - wydaje się szybsza niż ta która pokazuje całą odpowiedź po 2s naraz. Psychologia UX: aktywność = poczucie postępu = mniejsza frustracja.

Poniżej implementacja streamingu w React - krok po kroku:

Krok 1: Stan komponentu przechowuje aktualny tekst i flagę czy Claude wciąż odpowiada.

Krok 2: Otwieramy połączenie strumieniowe z backendem gdy komponent się pojawi.

Krok 3: Czytamy dane ze strumienia w kawałkach i na bieżąco dodajemy do wyświetlanego tekstu.

Krok 4: Migający kursor pokazuje że Claude wciąż odpowiada - znika gdy skończy.

// React + streaming Claude API
// React to biblioteka JavaScript do budowania interfejsów użytkownika
import { useState, useEffect } from 'react';

// TypeScript: { prompt: string } to typ parametru - musi być stringiem
function ChatMessage({ prompt }: { prompt: string }) {
  // KROK 1: Stan komponentu
  // text = aktualny tekst odpowiedzi (pusty na starcie)
  const [text, setText] = useState('');
  // isLoading = czy Claude wciąż odpowiada (true = tak, pokazuj kursor)
  const [isLoading, setIsLoading] = useState(true);

  // KROK 2: Połączenie strumieniowe gdy komponent się pojawi
  useEffect(() => {
    const stream = async () => {
      // Wywołujemy nasz backend który proxy'uje do Claude API
      const response = await fetch('/api/claude', {
        method: 'POST',
        body: JSON.stringify({ prompt }),
      });

      // KROK 3: Czytamy dane ze strumienia kawałek po kawałku
      // response.body.getReader() = czytnik strumienia danych binarnych
      const reader = response.body!.getReader();
      // TextDecoder zamienia bajty na tekst UTF-8
      const decoder = new TextDecoder();

      // Pętla czytająca dane ze strumienia
      while (true) {
        const { done, value } = await reader.read();
        if (done) { setIsLoading(false); break; } - // Koniec strumienia
        // prev => prev + ... = dodaj nowy fragment do istniejącego tekstu
        setText(prev => prev + decoder.decode(value));
      }
    };
    stream();
  }, [prompt]); - // Zależność: uruchom gdy prompt się zmieni

  return (
    <div className="message">
      {text}
      {/* KROK 4: Migający kursor pokazuje że Claude wciąż odpowiada */}
      {isLoading && <span className="cursor-blink">▊</span>}
    </div>
  );
}

Po wdrożeniu: otwórz DevTools (F12) --> zakładka Network. Wywołaj zapytanie do Claude. Powinieneś zobaczyć request do /api/claude z Type "EventStream" lub "Fetch" który rośnie w czasie - to znak że streaming działa.

Skeleton loading i progress indicators

  • Pokaż "Claude myśli..." z animowanymi kropkami przez pierwsze 500ms - użytkownik wie że aplikacja żyje
  • Dla długich zadań (analiza dokumentu): progress bar z etapami ("Czytam dokument... Analizuję... Przygotowuję odpowiedź...") - każda zmiana fazy redukuje niepewność
  • Optymistyczne UI: pokaż akcję użytkownika natychmiast (np. "Twoja wiadomość"), odpowiedź AI pojawia się gdy jest gotowa

Problem #2: Użytkownicy nie wiedzą co pisać

Puste pole tekstowe z "Zapytaj mnie o cokolwiek" to najgorszy UX dla AI. Użytkownicy paraliżują się - "Co mogę tu wpisać?", "Czy zrozumie moje pytanie?", "Jak to sformułować?". To zjawisko nazywa się blank page syndrome i dotyka nawet zaawansowanych użytkowników.

Przed i po - konkretny przykład:

ZLY UX (przed) DOBRY UX (po)
Puste pole z placeholder: "Wpisz wiadomość...". Żadnych wskazówek. Użytkownik wpisuje "pomoc" i dostaje długą listę co potrafi aplikacja - ale już jest sfrustrowany. 4 klikalne przyciski pod polem: "Wyjaśnij ten kod", "Znajdź błędy", "Napisz testy", "Zoptymalizuj". Użytkownik klika jeden i widzi jak działa - efekt "aha, to tak to działa".
Placeholder: "Wpisz wiadomość" - mówi co zrobić, nie daje przykładu Placeholder: "Np: Znajdź błędy w tym kodzie / Co oznacza ten błąd?" - pokazuje konkretny format
Asystent AI
Od czego zacząć?
Wpisz wiadomość...
Wyjaśnij ten kod Znajdź błędy Napisz testy Zoptymalizuj
Wpisz wiadomość... Znajdź błędy
Puste pole paraliżuje. Gotowe podpowiedzi pokazują, od czego zacząć - i zwiększają zaangażowanie.

Suggested prompts - zawsze, bez wyjątków

Krok 1: Przygotuj zestaw sugestii dla każdego kontekstu (kod, dokument, ogólny).

Krok 2: Wyświetl je jako klikalne przyciski gdy pole jest puste.

Krok 3: Po kliknięciu - wstaw sugestię do pola lub wyślij od razu. Użytkownik może edytować przed wysłaniem.

// KROK 1: Sugerowane pytania - zmieniaj kontekstowo
const SUGGESTIONS = {
  // Tryb "kod" - użytkownik wkleił lub wskazał plik kodu
  'code': [
    'Wyjaśnij ten kod',
    'Znajdź błędy',
    'Napisz testy',
    'Zoptymalizuj wydajność'
  ],
  // Tryb "dokument" - użytkownik załadował PDF lub tekst
  'document': [
    'Podsumuj w 5 punktach',
    'Jakie są kluczowe ryzyka?',
    'Porównaj z poprzednią wersją',
    'Przetłumacz na angielski'
  ]
};

// KROK 2 i 3: Komponent React wyświetlający przyciski z sugestiami
function PromptSuggestions({ context, onSelect }) {
  return (
    <div className="suggestions">
      {/* Dla każdej sugestii - klikalny przycisk */}
      {SUGGESTIONS[context].map(s => (
        // onSelect(s) = gdy użytkownik kliknie, wstaw sugestię do pola tekstowego
        <button key={s} onClick={() => onSelect(s)}>{s}</button>
      ))}
    </div>
  );
}

Placeholder jako instrukcja, nie zachęta

Zamiast "Wpisz wiadomość..." --> "Np: Znajdź błędy w tym kodzie / Podsumuj ten dokument / Co oznacza ten błąd?". Placeholder pokazuje konkretne przykłady, nie abstrakcyjne możliwości - użytkownik widzi format i zakres.

Chcesz zbudować kompletną aplikację AI - od Claude API do działającego frontendu?

Szkolenie AI dla programistów - UX aplikacji AI to jeden z modułów. Termin gwarantowany.

Sprawdź termin szkolenia -->

Problem #3: Model może się mylić - jak to komunikować

Claude halucynuje. Nie często, ale zdarza się - szczególnie dla danych po jego dacie treningowej lub dla bardzo specyficznych faktów. Ukrywanie tego niszczy zaufanie gdy użytkownik odkryje błąd. Transparentność jest kluczem do długotrwałego zaufania.

Przed i po - konkretny przykład komunikacji halucynacji:

ZLY UX (przed) DOBRY UX (po)
Claude odpowiada pewnie: "Wydarzenie X miało miejsce 15 marca 2025." Użytkownik sprawdza - to nieprawda. Traci zaufanie do całej aplikacji. Claude odpowiada: "Nie jestem pewny tej daty - moje dane są do 2024 roku. Według moich informacji wydarzenie X miało miejsce w okolicach marca 2025, ale proszę to zweryfikować."
Brak przycisku feedbacku - użytkownik nie może zgłosić błędu Przyciski pod każdą odpowiedzią - zbierają dane o jakości, poprawiają system prompt
Pokaż pewność i źródło. Przyznanie się do niepewności buduje zaufanie, nie podważa go.
  • Confidence indicators: "Na podstawie Twoich danych..." (wysoka pewność - mamy dokumenty) vs "Zakładam że..." (średnia) vs "Nie jestem pewny, ale..." (niska - ryzyko halucynacji)
  • Source attribution: w RAG zawsze pokaż skąd pochodzi informacja ("Według dokumentu 'Regulamin 2026', str. 12..."). Użytkownik może samodzielnie sprawdzić
  • Uncertainty prompting: skonfiguruj Claude żeby przyznawał się do niepewności: "Jeśli nie jesteś pewny odpowiedzi - powiedz to wyraźnie zamiast zgadywać."
  • Easy feedback: przycisk po każdej odpowiedzi - zbieraj dane o jakości, używaj do poprawy systemu promptów

Problem #4: Obsługa błędów widoczna dla użytkownika

Techniczne błędy API nigdy nie powinny trafiać bezpośrednio do użytkownika. "anthropic.APIStatusError: 529" nie mówi mu nic użytecznego - tylko wywołuje panikę. Każdy błąd powinien mówić użytkownikowi co ma zrobić, nie co poszło nie tak technicznie.

Techniczny błąd API wywołuje panikę użytkownika - mem o obsłudze błędów w aplikacji z AI

Przed i po - obsługa błędu rate limiting:

ZLY UX (przed) DOBRY UX (po)
Pojawia się czerwony komunikat: "anthropic.RateLimitError: 429 Rate limit exceeded. Current request rate: 95 req/min, limit: 100 req/min." Użytkownik nie wie co to znaczy ani co zrobić. Pojawia się: "Asystent jest bardzo zajęty. Spróbuj za kilka sekund. [Przycisk: Spróbuj ponownie]". Automatyczny retry po 5 sekundach.
ERROR 429
anthropic.RateLimitError: 429 - rate limit exceeded (95/100 req/min)
at client.messages.create (sdk/core.js:412)
Asystent jest teraz bardzo zajęty
Ponawiam automatycznie...
Spróbuj ponownie
Kod błędu nic nie mówi użytkownikowi. Powiedz mu, co robić - i zrób to za niego.
// NIE RÓB tak - techniczny błąd trafia bezpośrednio do interfejsu
catch (error) {
  setError(error.message); // "anthropic.APIStatusError: 529" - użytkownik nie wie co to znaczy
}

// RÓB tak - przyjazny komunikat dla użytkownika + techniczne logi dla dewelopera
catch (error) {
  if (error instanceof RateLimitError) {
    // Zbyt wiele żądań - użytkownik dostaje prostą instrukcję co zrobić
    setError('Asystent jest bardzo zajęty. Spróbuj za kilka sekund.');
  } else if (error instanceof APIConnectionError) {
    // Problem z siecią - sugeruj sprawdzenie połączenia
    setError('Problem z połączeniem. Sprawdź internet i spróbuj ponownie.');
  } else {
    // Nieznany błąd - ogólny komunikat + zaloguj technikalia
    setError('Coś poszło nie tak. Odśwież stronę jeśli problem się powtarza.');
    // logger.error loguje szczegóły dla dewelopera (widoczne w logach serwera)
    logger.error('Claude API error:', error);
  }
}
Zasada dla każdej aplikacji: Użytkownik i deweloper potrzebują różnych informacji o błędzie. Użytkownik: "Co powinienem zrobić?". Deweloper: "Co dokładnie poszło nie tak i gdzie?". Rozdziel te dwa komunikaty - jeden idzie do UI, drugi do logów serwera.

Problem #5: Długie odpowiedzi - czytelność i nawigacja

Claude lubi długie, szczegółowe odpowiedzi. W UI często to problem - użytkownik widzi ścianę tekstu i nie wie gdzie patrzeć. Wzorce które działają:

  • Progressive disclosure: pokaż podsumowanie + "Zobacz więcej" dla szczegółów. Większość użytkowników chce skrótu, nie pełnego elaboratu
  • Markdown rendering: Claude domyślnie zwraca markdown (tekst z **pogrubieniami**, # nagłówkami, - listami). Musisz go renderować - surowe gwiazdki w UI wyglądają fatalnie. Biblioteki: react-markdown (React), marked.js (vanilla JS)
  • Code highlighting: dla odpowiedzi z kodem - syntax highlighting (Prism.js lub highlight.js). Kolorowy kod jest 3× czytelniejszy niż czarno-biały
  • Copy button: na każdym bloku kodu - użytkownicy oczekują że mogą skopiować jednym kliknięciem. Bez tego wielokrotnie zaznaczają myszką i klną
Odpowiedź asystenta
Podsumowanie
Zobacz więcej
SZCZEGÓŁY
bash · deploy.sh Kopiuj Skopiowano
# wdrożenie produkcyjne
npm run build && \
  docker push registry/app:latest
Ściana tekstu odpycha. Zwięzłe podsumowanie z opcją „Zobacz więcej" oraz przycisk „Kopiuj" przy bloku kodu robią różnicę.

Problem #6: Kontekst konwersacji - zarządzanie historią

  • Pokaż rozmowę jako wątki - nie stack scrollowalnych wiadomości gdy jest ich 50. Długa lista wiadomości jest nieczytelna
  • Możliwość edycji promptu - "Edytuj i wyślij ponownie" zamiast przepisywania od zera. Oszczędza czas i frustrację
  • Restore context - użytkownik wraca po tygodniu, pokaż mu krótkie podsumowanie poprzedniej rozmowy. Nie musi czytać 50 wiadomości żeby wiedzieć gdzie skończył
  • Limit długości konwersacji - po X wiadomościach zaproponuj "Zacznij nową rozmowę" żeby nie tracić jakości. Długi kontekst = Claude "zapomina" co było na początku (limit okna kontekstowego)
Asystent AI - sesja tokeny
Okno kontekstu limit
Ty AI Ty AI Ty AI Podsumowanie
Im dłuższa rozmowa, tym wyższy koszt i opóźnienie. Podsumowuj starsze tury, zanim sięgniesz limitu.
Human-in-the-loop dla krytycznych akcji

Wzorzec: Human-in-the-loop dla krytycznych akcji

Human-in-the-loop to pattern gdzie dla ważnych, nieodwracalnych akcji - agent musi uzyskać potwierdzenie od człowieka zanim je wykona. Zasada: im bardziej nieodwracalna akcja, tym silniejsze potwierdzenie potrzebne.

Przykłady akcji które ZAWSZE wymagają potwierdzenia: wysłanie emaila do klientów, zapis do produkcyjnej bazy danych, wywołanie płatnego zewnętrznego API, usunięcie pliku, wysłanie wiadomości na Slack. Przykłady akcji które NIE wymagają potwierdzenia: odczyt danych, analiza tekstu, generowanie propozycji (bez zapisywania).
// Komponent React pokazujący prośbę o potwierdzenie akcji agenta
// onConfirm i onReject to funkcje zwrotne (callbacks) wywoływane po kliknięciu
function AgentAction({ action, onConfirm, onReject }) {
  return (
    <div className="action-approval">
      <p>Agent chce wykonać:</p>
      {/* action.description = opis konkretnej akcji w czytelnym języku */}
      <code>{action.description}</code>
      <div className="buttons">
        <button onClick={onConfirm} className="btn-approve">
          Zatwierdz
        </button>
        <button onClick={onReject} className="btn-reject">
          Odrzuc
        </button>
      </div>
    </div>
  );
}
// Używaj tego wzorca gdy agent chce: wysłać email, zapisać do bazy, wywołać zewnętrzne API
Potwierdzenie akcji agenta
Wymagana zgoda
Agent chce wykonać:
Wyślij e-mail do 1 240 klientów
Newsletter · kampania masowa
Nieodwracalne
Odrzuć
Zatwierdź
Im bardziej nieodwracalna akcja, tym mocniejsze potwierdzenie. Człowiek zatwierdza, zanim agent zadziała.

Częste błędy przy projektowaniu UX dla AI

Błąd #1: Brak komunikatu ładowania przez pierwsze sekundy

Użytkownik klika i przez 2-3 sekundy nic się nie dzieje - myśli że przycisk nie zadziałał i klika ponownie. Efekt: dwa równoległe zapytania, podwójny koszt API, zdezorientowany użytkownik. Fix: pokaż loader natychmiast po kliknięciu "Wyślij", zablokuj przycisk do momentu odpowiedzi.

Błąd #2: Renderowanie surowego markdownu

Claude zwraca odpowiedź z **pogrubieniami**, ## nagłówkami, blokami kodu. Wyświetlasz je surowo - użytkownik widzi masę gwiazdek i krzyżyków. Wyglądają jak błąd formatowania. Zawsze używaj biblioteki do renderowania markdownu.

Błąd #3: Brak blokady formularza podczas oczekiwania

Użytkownik może wysłać kolejną wiadomość zanim Claude skończy odpowiadać na poprzednią. Prowadzi to do nakładających się odpowiedzi w UI i niespójnego stanu aplikacji. Blokuj pole input i przycisk Wyślij podczas gdy Claude odpowiada - odblokuj po zakończeniu streamingu.

Checklist UX przed wdrożeniem aplikacji z Claude

Przejdź przez tę listę przed każdym wdrożeniem. Każdy niezaznaczony punkt to potencjalna frustracja użytkownika:

UX gotowe do wdrożenia?
Streaming włączony
Loader od razu po „Wyślij”
Suggested prompts
Przyjazne komunikaty błędów
Markdown + przycisk kopiowania
Human-in-the-loop dla krytycznych akcji
Gotowe do wdrożenia
Przejdź listę przed startem. Każdy niezaznaczony punkt to przyszła frustracja użytkownika.
  • Streaming jest włączony - użytkownik widzi odpowiedź w czasie rzeczywistym
  • Loader pojawia się natychmiast po kliknięciu Wyślij
  • Są suggested prompts lub przykłady - użytkownik wie od czego zacząć
  • Błędy API mają przyjazne komunikaty dla użytkownika (nie kody błędów)
  • Markdown jest renderowany - nie surowy tekst z gwiazdkami i kratkami
  • Kod ma syntax highlighting i przycisk kopiowania
  • Długie odpowiedzi mają progressive disclosure (podsumowanie + "więcej")
  • Krytyczne akcje agenta wymagają potwierdzenia (human-in-the-loop)
  • Jest mechanizm feedbacku (thumbs up/down lub podobny)
  • Stany: loading (ładowanie), success (sukces), error (błąd) są wyraźnie komunikowane
  • Mobile działa poprawnie - szczególnie klawiatura nie zasłania pola input

Kiedy Twoja aplikacja AI reaguje natychmiast, prowadzi użytkownika za rękę i komunikuje błędy jak przyjaciel - użytkownicy wracają. Dobry UX zamienia jednorazowych odwiedzających w lojalnych klientów, a Twój produkt wyróżnia się wśród morza przeciętnych aplikacji AI.

Szkolenie JSystems

5 decyzji UX, które wdrożysz od ręki

Te pięć drobiazgów decyduje, czy użytkownik polubi Twoją aplikację z AI:

  • Streaming zawsze. pokazuj odpowiedź na bieżąco - czekanie na pełny wynik w bezruchu zabija wrażenia.
  • Podpowiadaj przykładowe zapytania. gotowe „suggested prompts” i placeholder-instrukcja - bez nich użytkownik nie wie, co wpisać.
  • Komunikuj, że model może się mylić. przy ważnych akcjach daj łatwe sprawdzenie i cofnięcie (human-in-the-loop).
  • Skeleton zamiast spinnera. pokaż zarys odpowiedzi albo postęp - użytkownik widzi, że „coś się dzieje”.
  • Nie kumuluj całej historii. podsumowuj starsze tury rozmowy - inaczej rosną koszty i opóźnienie.

Newsletter bloga JSystems

Otrzymuj każdą nową lekcję prosto na swoją skrzynkę

Nowe lekcje pojawiają się co poniedziałek i czwartek. Zapisz się do newslettera bloga JSystems - dostaniesz powiadomienie zaraz po publikacji każdej lekcji.

Szkolenie AI dla programistów - od API do gotowej aplikacji

5 dni - budujesz kompletną aplikację AI z Claude włącznie z frontendem, streamingiem i wdrożeniem. UX aplikacji AI to jeden z modułów szkolenia. Termin gwarantowany.

Szkolenie AI dla programistów -->

Następna lekcja kursu Claude Code

Lekcja 11: automatyzacja CI/CD z Claude

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...