
Titremeden Canlı Rapor Akışı Nasıl Yapılır
Toplantınız bittiğinde, GeekBye'nin özeti artık bir bekleme çarkına bakmanızı sağlamak yerine canlı olarak dolar. Akış arayüzünü titrek yerine sakin hissettirmek, titremeyi iki kez çözmeyi gerektirdi — bir kez yapılandırılmış alanlar için, bir kez markdown için — ve ikinci çözüm zaten sahip olduğumuz bir renderlayıcıydı.
Bu sürümden önce, bir GeekBye toplantısını bitirmek bir bekleme çarkını izlemek demekti. Uygulama tüm transkriptinizi analiz için gönderir, özetin tamamını beklerdi — puan, önemli noktalar, yapılacaklar, hepsi — ve sonra hepsini birden ekrana bırakırdı. İşlevsel, ama yavaş hissettiriyordu, çünkü o çalışırken siz hiçliğe bakıyordunuz.
GeekBye v1.6.13–v1.6.15 bunu, üretildikçe alan alan canlı olarak akan bir rapora dönüştürdü. İlginç olan kısım akış değil — akışın titrek görünmesini engellemek için yapmamız gereken her şey. Sakin akış ve tutuk akış, çok farklı bir uygulamayla aynı özelliktir.
Titreme, birinci bölüm: her parçanın React'i hırpalamasına izin verme
Özet tek bir yığın değil — backend'in server-sent events üzerinden birer birer yaydığı yapılandırılmış alanlardır (genel bir puan, önemli noktalar, yapılacaklar vb.). Bunu renderlamanın saf yolu, her olay geldikçe React durumunu güncellemektir.
Bunu yaparsan titreme alırsın. Gelen her alan kendi yeniden render'ını tetikler; "boş" gösteren bileşenler mount olur, sonra unmount olur, sonra "yüklendi" olarak yeniden mount olur; düzen her pakette sarsılır. Rapor gözünüzün önünde mümkün olan en kötü şekilde kendini monte eder — görünür bir biçimde, gergince.
Çözüm iki parçalı bir disiplindir. İlk olarak, gelen alanları bir ref içinde biriktir — render tetiklemeyen değiştirilebilir bir tutucu — ve her parça başına bir kez taze bir kopyayla React durumuna yayınla, böylece ağaç her mikro-olayda değil, kasıtlı olarak güncellenir. İkinci olarak, akış halinde de bitmiş de olsa, henüz gelmemiş alanlar için satır içi yer tutucularla her zaman tek bir kararlı bileşen ağacı renderla:
// accumulate in a ref, publish once per chunk
partialRef.current = { ...partialRef.current, [field]: value }
setPartial({ ...partialRef.current })
// one tree, never swapped; missing fields show a placeholder in place
const report = isStreaming ? partial : saved
<Score>{report.overallScore ?? '…'}</Score>
Puanı gösteren bileşen asla unmount olmaz — sadece sayı gelene kadar … gösterir, sonra sayıyı. Hiçbir şey sarsılmaz. Ve tamamen monte edilmiş nesne yine de sonunda yerel veritabanına önbelleğe alınır, böylece akış arayüzü ve kalıcı depolama kavga etmeden bir arada var olur.
Titreme, ikinci bölüm: zaten sahip olduğumuz renderlayıcı
İkinci titreme daha inceliklidir ve markdown'ın kendisinde yaşar. Rapor zengin markdown renderlar — başlıklar, kalın, listeler, kod blokları. Ama akışın ortasında gelen markdown, herhangi bir anda yarı bitmiştir. Şimdiye kadar tek maddeli bir liste. Açılmış ama kapatılmamış bir kod çiti. Henüz eşi olmayan bir kalın işaretçisi.
Geleneksel bir markdown renderlayıcısı her token'da tüm dizeyi yeniden ayrıştırır ve bu yarı bitmiş durum her seferinde farklı bir şeye ayrışır — bu yüzden liste titrer, kod bloğu açılıp kapanır, token'lar tamamlandıkça düzen zıplar. Bu, bir kat aşağıda, öncekiyle aynı gergin montajdır.
Çözüm neredeyse utandırıcı derecede el altındaydı: canlı yapay zeka sohbeti için zaten akış-farkında bir markdown renderlayıcısı kullanıyorduk. Her seferinde sıfırdan yeniden ayrıştırmak yerine, eksik token'ları tolere etmek için kurulmuş bir renderlayıcı — yarı bitmiş markdown'ı kararlı biçimde renderlar ve yalnızca token'lar tamamlandıkça oturtur. Raporun sadece aynısını kullanması gerekiyordu. Bu tam sorunu aylar önce sohbet için çözmüştük; rapor için "çözüm", aracı zaten elimizde olduğunu fark edip onu ikinci bir yüzeye yöneltmekti. Yeniden kullanmak, yeniden inşa etmeyi yendi.
Bonus: zengin kopyalama bir pano formatı sorunudur
Rapor iyi görünmeye başlayınca, insanlar onu yapıştırmak istedi — bir belgeye, bir e-postaya — ve biçimlendirmeyi ve ekran görüntülerini korumak. İçgüdü, raporu bir markdown dizesine serileştirip hedefin onu renderlamasını ummaktır. Genellikle renderlamaz.
Gerçek yanıt, panonun aynı anda birden fazla temsili tuttuğudur. Bu yüzden ikisini yazıyoruz: biçimlendirmesi korunmuş ve ekran görüntüleri satır içi görseller olarak gömülü bir HTML sürümü ve HTML kabul etmeyen hedefler için bir düz metin yedeği. Zengin bir editöre yapıştır, biçimlendirilmiş raporu görsellerle alırsın; düz bir metin kutusuna yapıştır, temiz metin alırsın. "Zengin kopyalama" hiçbir zaman bir serileştirme sorunu değildi — bir doğru-pano-formatını-seç sorunuydu.
Aynı sürüm ayrıca rapora, iki konuşmacı zıt taraflara hizalanmış sohbet tarzı Me / Them etiketleri verdi, böylece bir transkript, olduğu konuşma gibi okunur, ve eşleşmesi için kendi ekran görüntülerinizi kendi tarafına taşıdı. (Bu penceredeki bir sürüm, v1.6.14, saf bir yeniden yapılandırmaydı — orada hikâye yok, ve bunu söylemek dürüstçe.)
Akış arayüzünün bize öğrettiği üç şey
- Akan alanları tamponla, parça başına bir kez yayınla. Her server-sent event'in kendi durum güncellemesini sürüklemesine izin vermek, titremenin ta kendisidir. Bir ref biriktirici artı parça başına tek bir yayın, asla unmount olmayan bir ağaca, gergin montajı sakin bir doldurmaya dönüştürür.
- Akan her şeyin akış-farkında bir renderlayıcıya ihtiyacı vardır. Token başına tüm dizeyi yeniden ayrıştıran bir renderlayıcı, yarı bitmiş markdown'da titrer. Eksik girdi için kurulmuş birini kullan — ve başka bir yüzey için zaten bir tane varsa, ikincisini inşa etmeden önce onu yeniden kullan.
- Zengin kopyalama serileştirmeyle değil, pano formatlarıyla ilgilidir. Gömülü görsellerle
text/htmlve birtext/plainyedeği yaz. Pano ikisini de taşımak için tasarlandı; kullan.
Bu, GeekBye v2 olan güvenilirlik-ve-cila hikâyesinin beşinci bölümüdür. Önceki bölüm için kulaklık bug'ı (v1.6.12); aynı server-sent events fikrinin daha sonra baştan sona bir yedek taşımaya dönüştüğü yer için güvenlik duvarı WebSockets'i engellediğinde canlı transkripsiyon (v2.0.8); ve tüm yay için yazılımı kusursuzluğa kadar göndermenin anatomisi yazılarına bak.