shadcn/ui ve Vercel AI SDK ile gerçek kullanıcılara dayanan bir yapay zekâ sohbet arayüzü nasıl kurulur
Akan bir yapay zekâ sohbetini demo etmek kolay, göndermek zordur. Durdurma ve yeniden denemeden araç onaylarına ve CJK girişine kadar, bir shadcn/ui ve AI SDK sohbetinin ele alması gereken durumlar.
Preet Suthar · · 6 dk okuma
- AI
- React
- shadcn/ui
- Next.js
Demo bir öğleden sonra sürer. AI SDK’dan useChat, bir mesaj listesi, bir textarea ve token’lar ekrana akar. Ekipteki herkes etkilenir.
Sonra gerçek bir insan kullanır. Japonca yazarken Enter’a basar ve yarım bir kelime gönderir. Bir yanıtı durdurur ve arayüz hiçbir şey göstermez. Bir araç çağrısı akışın ortasında onay ister ve bunun için bir arayüz yoktur. Bir şeyi yeniden okumak için yukarı kaydırır ve görünüm her token’da onu en alta geri çeker.
Bu ekranı istediğimden çok daha fazla kez kurdum; en son HextaUI’ın arkasındaki bloklar olarak. Bu, bir sohbet arayüzünün bitmiş sayılmadan önce ele alması gerekenlerin kontrol listesi, her biri için shadcn/ui ve AI SDK ayrıntılarıyla birlikte.
String’lerden değil, mesaj parçalarından başlayın
AI SDK’nın useChat kancası, parçalardan oluşan mesajlar döndürür: metin, reasoning, durumlarıyla birlikte araç çağrıları, kaynaklar, dosyalar. Birleştirilmiş bir string’i değil, parçaları render edin. Aşağıdaki her özellik buna dayanır.
Araç parçaları tool-<name> olarak tiplenir, dolayısıyla güvenilir kontrol isToolUIPart kontrolüdür.
Bloklarınızı SDK içe aktarmasından uzak tutun. Parçaları prop olarak alın. Böylece çıktısını aynı biçime eşlediğiniz anda aynı bileşenler bir LangGraph backend’i ya da kendi sunucunuzla çalışır.
Composer
Hataların çoğu textarea’da yaşar.
Enter gönderir, Shift+Enter satır atlar. Herkes bunu yapar. Neredeyse hiç kimse kompozisyonu ele almaz. Biri Japonca, Çince ya da Korece yazarken IME açıktır ve Enter mesajı değil adayı onaylar. event.nativeEvent.isComposing değerini kontrol edip çıkın:
Metinle birlikte, sınırlar içinde büyüyün. Başlangıçta bir satır, yaklaşık sekiz satıra kadar, sonra kaydırma. Yükseklik değişimini animasyonla yapın ve bunu asla her tuş vuruşunda React state üzerinden yapmayın. scrollHeight değerini bir layout effect içinde ölçüp yüksekliği DOM düğümüne uygulayın. Yerel bir input dinleyicisindeki her setState, yük altında er ya da geç tuşları düşürür ya da geciktirir.
Taslaklar yeniden yüklemeden sağ çıkar. Taslağı sohbet id’sine göre anahtarlanmış şekilde, debounce ederek localStorage içine kaydedin. İnsanlar uzun istemleri yanlışlıkla yenilenen sayfalara kaptırır ve sizi asla affetmez.
Yanıt sırasında yazılan mesajları kuyruğa alın. Model hâlâ yazarken kullanıcı başka bir mesaj gönderirse ya göndermeyi devre dışı bırakın ya da kuyruğa alıp akış bitince gönderin. Sessizce düşürmek en kötü seçenektir ve varsayılan olan da odur.
Ekler ilerleme ve yeniden deneme gösterir. Yüklemeler başarısız olur. Hatayı kaybolan bir toast olarak değil, yeniden deneme seçeneğiyle ek kartının üzerinde gösterin.
Rahat okunan akış
Modeller token’ları patlamalar halinde üretir. Geleni olduğu gibi render ederseniz metin takılır: hiçbir şey, sonra on iki kelime, sonra yine hiçbir şey. Yumuşatın. Karakterleri, tampon büyüdükçe hızlanan sabit bir tempoda ortaya çıkarın; böylece akışın ne çok gerisinde kalırsınız ne de takılırsınız.
Akış sırasında Markdown’ın kendi tuzağı vardır: yarım yazılmış sözdizimi. Model **bold yazmış ama henüz kapatmamıştır; render edici bir kare boyunca düz yıldız işaretleri, sonra kalın metin gösterir. Tamamlanmamış satır içi sözdizimini kapanana ya da akış bitene kadar bekletin.
Kod blokları aynı özeni ister. Artımlı olarak vurgulayın ve her token’da tüm bloğu asla yeniden vurgulamayın.
Durdurma, yeniden deneme, düzenleme ve sürümler
Durdur, kısmi yanıtı ekranda bırakmalı ve kullanıcı eksik olduğunu bilsin diye görünür bir "Stopped" işareti göstermelidir. Onu silmeyin.
Başarısız bir yanıtta Yeniden dene, gizli bir jest değil, mesajın üzerindeki bir düğmedir. Onu regenerate() çağrısına bağlayın.
Önceki bir mesajı düzenlemek bir dal oluşturur. Hem eski hem yeni yanıt var olur. Kullanıcı sürümler arasında geçiş yapabilsin diye mesajın üzerinde küçük bir 1 / 2 değiştirici gösterin. Pek çok ev yapımı sohbet arayüzünün pes ettiği yer burasıdır ve insanların ChatGPT ile Claude’dan en çok özlediği özellik de budur.
Başarısız yanıtlar bir gerekçe ve yeniden deneme alır. Eylemi olmayan bir "Bir şeyler ters gitti" çıkmaz sokaktır.
Kaydırma
Kullanıcı en alttayken yeni token’ları takip edin. Yukarı kaydırdığı anda takibi bırakın ve kaçırdıklarının sayısıyla birlikte bir "en yeniye atla" düğmesi gösterin. Atladığında yeniden takip edin.
Eski geçmişi üstten yüklemek görünümü oynatmamalı. Başa eklemeden önce ve sonra kaydırma yüksekliğini ölçün ve aynı karede scrollTop değerini aradaki fark kadar düzeltin.
Kullanıcı bir mesaj gönderirse, mesajı görünüm alanının üstüne sabitlenecek ve yanıt altında akacak şekilde kaydırın. İki büyük sohbet uygulaması artık böyle davranıyor ve kullanıcılar bunu bekliyor.
Araç çağrıları ve onaylar
AI SDK her araç parçasına bir durum verir: input-streaming, input-available, output-available, output-error ve döngüde insan varsa approval-requested, approval-responded ve output-denied. Her durumun bir görünümü olmalı.
Okumalar ve aramalar tek satırlık bir özete katlanabilir. Düzenlemeler, komutlar ve hatalar açık kalmalıdır. Onaylar akışı durdurur; net bir Allow ve Deny’a, ayrıca reddetme gerekçesi olarak modele metin gönderen bir "bunun yerine şunu yap" yoluna ihtiyaç duyar.
Sonucu işe yaradığında gösterin. Bir dosya düzenlemesi diff’e açılır. Bir komut çıktısıyla birlikte terminale açılır. Bir arama sonuçlara açılır. Yalnızca "Done" diyen bir araç çağrısı kullanıcılara araç çağrılarını görmezden gelmeyi öğretir.
Düşünme ve kaynaklar
Reasoning parçaları daraltılabilir olmalı ve yanıt başladığında varsayılan olarak daraltılmalıdır. Model hâlâ düşünürken her adımı izleyen canlı bir etiket ("Dokümanlar aranıyor", "Üç dosya okunuyor") bir spinner’dan çok daha güven vericidir.
Alıntılar, kimsenin okumadığı alttaki bir listede değil, destekledikleri iddiaların hemen yanında, satır içinde durmalıdır.
Atlayamayacağınız erişilebilirlik
- Yeni mesajlar bir
role="log"bölgesi üzerinden duyurulur, böylece ekran okuyucu kullanıcıları yanıtları duyar. - Durdur düğmesine klavyeyle erişilebilir ve bir etiketi vardır.
- Akan metin yüz tane canlı bölge duyurusu tetiklememeli. Mesaj tamamlandığında ya da cümle sınırlarında duyurun.
- Yalnızca simgeden oluşan her düğmenin
aria-labelözniteliği vardır.
Hepsini bir araya getirmek
Parçaları istiyorsanız, shadcn/ui Ekim 2026’da AI Elements’ı yayınladı: Chat, Prompt Input, Reasoning ve Tool dahil, projenize kopyalayıp kendiniz birleştirdiğiniz 21 desen. Vercel’in önceki AI Elements kütüphanesi, bunlar lehine Kasım 2026 sonunda kullanımdan kaldırılıyor.
Ekranları, yukarıdaki durumlar çözülmüş şekilde hazır birleştirilmiş istiyorsanız HextaUI blokları tam olarak budur: durdurma, yeniden deneme ve sürümleri olan bir Chat Thread; her AI SDK durumunu ve onayları olan Tool Calls; asla yarım yazılmış sözdizimi göstermeyen Streaming ve Markdown; CJK girişi, yüklemeler, kuyruklar ve taslakları ele alan bir Prompt Input. Prompt Input ücretsizdir. Geri kalanı tek seferlik bir satın almadır. Hepsi AI SDK mesaj parçalarını okur ve hiçbiri SDK’yı içe aktarmaz; dolayısıyla eşleyebildiğiniz her backend ile çalışır.
Hiçbir şey kurmadan önce hepsini bir sohbet uygulaması olarak birleştirilmiş halde hextaui.com/blocks/hexta-ai adresinde deneyebilirsiniz.
SSS
Bu desenleri kullanmak için Vercel AI SDK’ya ihtiyacım var mı? Hayır. İşe yarayan kısım mesaj parçaları biçimidir. Çıktısını parçalara eşlediğiniz anda her backend çalışır.
IME ile Enter’ı nasıl ele alırım? keydown işleyicinizde event.nativeEvent.isComposing değerini kontrol edin ve erken dönün.
Akan metin her token’da bir canlı bölgeyi güncellemeli mi? Hayır. Tamamlanınca ya da cümle sınırlarında duyurun, yoksa ekran okuyucular kullanılamaz hale gelir.
Durdurulan bir yanıta ne olur? Kısmi metni tutun ve durdurulduğunu işaretleyin. Kullanıcının zaten okuduğu şeyi asla silmeyin.