
Cara Streaming Laporan Langsung Tanpa Kedipan
Saat rapat Anda berakhir, ringkasan GeekBye kini terisi secara langsung alih-alih membuat Anda menatap ikon pemuatan. Membuat UI streaming terasa tenang alih-alih bergetar butuh menyelesaikan kedipan dua kali — sekali untuk bidang terstruktur, sekali untuk markdown — dan perbaikan kedua adalah renderer yang sudah kami miliki.
Sebelum rilis ini, mengakhiri rapat GeekBye berarti menonton ikon pemuatan. Aplikasi akan mengirim seluruh transkrip Anda untuk dianalisis, menunggu ringkasan lengkap — skor, poin penting, item tindakan, semuanya — lalu menjatuhkannya ke layar sekaligus. Fungsional, tetapi ia terasa lambat, karena Anda menatap kekosongan selama proses berjalan.
GeekBye v1.6.13–v1.6.15 mengubahnya menjadi laporan yang mengalir masuk secara langsung, bidang demi bidang, seiring dihasilkannya. Bagian yang menarik bukanlah streaming-nya — melainkan segala hal yang harus kami lakukan agar streaming tidak terlihat bergetar. Streaming yang tenang dan streaming yang tersendat adalah fitur yang sama dengan eksekusi yang sangat berbeda.
Kedipan, bagian satu: jangan biarkan setiap potongan mengguncang React
Ringkasan bukanlah satu gumpalan — melainkan bidang-bidang terstruktur (skor keseluruhan, poin penting, item tindakan, dan seterusnya) yang dipancarkan backend satu per satu melalui server-sent events. Cara naif untuk merendernya adalah memperbarui state React pada setiap event begitu tiba.
Lakukan itu, dan Anda akan mendapat kedipan. Setiap bidang yang tiba memicu render ulangnya sendiri; komponen yang menampilkan "kosong" akan mount, lalu unmount, lalu remount sebagai "termuat"; tata letak berkedut pada setiap paket. Laporan merakit dirinya sendiri di depan Anda dengan cara yang paling buruk — terlihat jelas, gugup.
Perbaikannya adalah disiplin dua bagian. Pertama, kumpulkan bidang yang masuk dalam sebuah ref — penampung yang dapat diubah yang tidak memicu render — dan publikasikan ke state React sekali per potongan dengan salinan baru, sehingga tree diperbarui secara sengaja alih-alih pada setiap mikro-event. Kedua, render satu tree komponen yang stabil sepanjang waktu, baik sedang streaming maupun selesai, dengan placeholder inline untuk bidang yang belum tiba:
// 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>
Komponen yang menampilkan skor tidak pernah unmount — ia hanya menampilkan … sampai angkanya tiba, lalu angkanya. Tidak ada yang berkedut. Dan objek yang telah dirakit sepenuhnya tetap di-cache ke basis data lokal pada akhirnya, sehingga UI streaming dan penyimpanan tahan lama hidup berdampingan tanpa berbenturan.
Kedipan, bagian dua: renderer yang sudah kami miliki
Kedipan kedua lebih halus dan hidup di dalam markdown itu sendiri. Laporan merender markdown kaya — heading, tebal, daftar, blok kode. Tetapi markdown yang tiba di tengah aliran, pada saat tertentu, setengah jadi. Sebuah daftar dengan satu butir sejauh ini. Sebuah pagar kode yang telah dibuka tetapi belum ditutup. Sebuah penanda tebal yang belum punya pasangan.
Renderer markdown konvensional mengurai ulang seluruh string pada setiap token, dan keadaan setengah jadi itu terurai menjadi sesuatu yang berbeda setiap kali — sehingga daftar berkedip, blok kode berkelip buka-tutup, tata letak melompat seiring token selesai. Ini adalah perakitan gugup yang sama seperti sebelumnya, satu lapisan lebih dalam.
Perbaikannya nyaris memalukan karena begitu tersedia: kami sudah menggunakan renderer markdown yang sadar-streaming untuk obrolan AI langsung. Sebuah renderer yang dibangun untuk menoleransi token tidak lengkap — untuk merender markdown setengah jadi secara stabil dan baru menetapkannya saat token selesai — alih-alih mengurai ulang dari nol setiap kali. Laporan hanya perlu menggunakan yang sama. Kami telah menyelesaikan masalah yang persis ini untuk obrolan berbulan-bulan sebelumnya; "perbaikan" untuk laporan adalah menyadari bahwa kami sudah punya alatnya dan mengarahkannya ke permukaan kedua. Menggunakan kembali mengalahkan membangun ulang.
Bonusnya: salin kaya adalah masalah format papan klip
Begitu laporan terlihat bagus, orang ingin menempelkannya — ke dokumen, email — dan mempertahankan formatnya serta tangkapan layarnya. Nalurinya adalah menserialkan laporan menjadi string markdown dan berharap tujuannya merendernya. Biasanya tidak.
Jawaban sebenarnya adalah bahwa papan klip menyimpan beberapa representasi sekaligus. Jadi kami menulis dua: sebuah versi HTML dengan format utuh dan tangkapan layar tertanam sebagai gambar inline, dan sebuah cadangan teks biasa untuk tujuan yang tidak menerima HTML. Tempel ke editor kaya dan Anda mendapatkan laporan berformat dengan gambar; tempel ke kotak teks biasa dan Anda mendapatkan teks bersih. "Salin kaya" tidak pernah menjadi masalah serialisasi — melainkan masalah pilih-format-papan-klip-yang-tepat.
Rilis yang sama juga memberi laporan label gaya-obrolan Me / Them dengan kedua pembicara disejajarkan ke sisi berlawanan, sehingga sebuah transkrip terbaca seperti percakapan yang sebenarnya, dan memindahkan tangkapan layar Anda sendiri ke sisinya sendiri agar cocok. (Satu rilis dalam jendela ini, v1.6.14, adalah pembangunan ulang murni — tidak ada cerita di situ, dan jujur untuk mengatakannya.)
Tiga hal yang diajarkan UI streaming kepada kami
- Buffer bidang yang di-stream, publikasikan sekali per potongan. Membiarkan setiap server-sent event mengendalikan pembaruan state-nya sendiri adalah sumber kedipan. Sebuah akumulator ref plus satu publikasi per potongan, ke dalam tree yang tidak pernah unmount, mengubah perakitan gugup menjadi pengisian yang tenang.
- Apa pun yang di-stream membutuhkan renderer yang sadar-streaming. Renderer yang mengurai ulang seluruh string per token akan berkedip pada markdown setengah jadi. Gunakan yang dibangun untuk input tidak lengkap — dan jika Anda sudah punya satu untuk permukaan lain, gunakan kembali sebelum membangun yang kedua.
- Salin kaya adalah tentang format papan klip, bukan serialisasi. Tulis
text/htmldengan gambar tertanam dan cadangantext/plain. Papan klip dirancang untuk membawa keduanya; gunakan itu.
Ini adalah bab kelima dari kisah keandalan-dan-pemolesan yang menjadi GeekBye v2. Untuk bab sebelumnya, lihat bug earbud (v1.6.12); untuk tempat di mana ide server-sent events yang sama kemudian menjadi seluruh transport cadangan, transkripsi langsung saat firewall memblokir WebSockets (v2.0.8); dan untuk keseluruhan alurnya, anatomi merilis perangkat lunak hingga kesempurnaan.