
Näin striimaat live-raportin ilman välkyntää
Kun palaverisi päättyy, GeekByen yhteenveto täyttyy nyt reaaliajassa sen sijaan, että tuijottaisit latausanimaatiota. Rauhallisen striimaavan käyttöliittymän saaminen nykivän sijaan vaati välkynnän ratkaisemista kahdesti — kerran rakenteisille kentille, kerran markdownille — ja toinen korjaus oli renderöijä, joka meillä oli jo valmiina.
Ennen tätä julkaisua GeekBye-palaverin lopettaminen tarkoitti latausanimaation tuijottamista. Sovellus lähetti koko transkriptisi analysoitavaksi, odotti koko yhteenvedon — pisteet, avainkohdat, toimenpiteet, kaikki — ja pudotti sen sitten ruudulle yhdellä kertaa. Toimivaa, mutta se tuntui hitaalta, koska tuijotit tyhjyyteen sen työskennellessä.
GeekBye v1.6.13–v1.6.15 muutti tuon raportiksi, joka striimaa sisään reaaliajassa, kenttä kentältä, sitä mukaa kun se syntyy. Mielenkiintoinen osa ei ole itse striimaus — vaan kaikki se, mitä meidän piti tehdä, jotta striimaus ei näyttäisi nykivältä. Rauhallinen striimaus ja töksähtelevä striimaus ovat sama ominaisuus hyvin erilaisella toteutuksella.
Välkyntä, osa yksi: älä anna jokaisen palan hakata Reactia
Yhteenveto ei ole yksi könttä — se on rakenteisia kenttiä (kokonaispisteet, avainkohdat, toimenpiteet ja niin edelleen), jotka backend lähettää yksi kerrallaan server-sent eventsin yli. Naiivi tapa renderöidä se on päivittää React-tila jokaisella saapuvalla tapahtumalla.
Tee niin, ja saat välkyntää. Jokainen saapuva kenttä laukaisee oman uudelleenrenderöintinsä; komponentit, jotka näyttivät "tyhjää", asennetaan, sitten puretaan, sitten asennetaan uudelleen "ladattuina"; asettelu nykii jokaisella paketilla. Raportti kokoaa itseään silmiesi edessä pahimmalla mahdollisella tavalla — näkyvästi, hermostuneesti.
Korjaus on kaksiosainen kuri. Ensin, kerää saapuvat kentät refiin — muuttuvaan säiliöön, joka ei laukaise renderöintejä — ja julkaise React-tilaan kerran per pala tuoreena kopiona, jotta puu päivittyy harkitusti eikä jokaisella mikrotapahtumalla. Toiseksi, renderöi yksi vakaa komponenttipuu koko ajan, olipa striimattaessa tai valmiina, sisäisillä paikanpitäjillä kentille, jotka eivät ole vielä saapuneet:
// 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>
Komponentti, joka näyttää pisteet, ei koskaan purkaudu — se vain näyttää …, kunnes numero saapuu, ja sitten numeron. Mikään ei nyki. Ja täysin koottu objekti tallentuu silti lopuksi paikalliseen tietokantaan välimuistiin, joten striimaava käyttöliittymä ja kestävä tallennus elävät rinnakkain taistelematta.
Välkyntä, osa kaksi: renderöijä, joka meillä oli jo
Toinen välkyntä on hienovaraisempi ja elää itse markdownissa. Raportti renderöi rikasta markdownia — otsikoita, lihavointia, listoja, koodilohkoja. Mutta kesken striimin saapuva markdown on minä tahansa hetkenä keskeneräistä. Lista, jossa on tähän mennessä yksi luoti. Koodiaita, joka on avattu mutta ei suljettu. Lihavointimerkki, jolla ei ole vielä paria.
Perinteinen markdown-renderöijä jäsentää koko merkkijonon uudelleen jokaisella tokenilla, ja tuo keskeneräinen tila jäsentyy joka kerta joksikin erilaiseksi — joten lista välkkyy, koodilohko välähtää auki ja kiinni, asettelu hyppää tokeneiden valmistuessa. Se on sama hermostunut kokoaminen kuin ennenkin, yksi kerros syvemmällä.
Korjaus oli lähes noloudessaan käsillä: käytimme striimaustietoista markdown-renderöijää jo live-tekoälychatissa. Renderöijä, joka on rakennettu sietämään keskeneräisiä tokeneita — renderöimään keskeneräistä markdownia vakaasti ja vakiinnuttamaan sen vasta, kun tokenit valmistuvat — sen sijaan että jäsentäisi joka kerta alusta. Raportin piti vain käyttää samaa. Olimme ratkaisseet tämän täsmälleen saman ongelman chatissa kuukausia aiemmin; raportin "korjaus" oli sen tajuaminen, että työkalu oli jo olemassa, ja sen suuntaaminen toiselle pinnalle. Uudelleenkäyttö voitti uudelleenrakentamisen.
Bonus: muotoiltu kopiointi on leikepöydän formaattikysymys
Kun raportti näytti hyvältä, ihmiset halusivat liittää sen — dokumenttiin, sähköpostiin — ja säilyttää muotoilun ja kuvakaappaukset. Vaisto on sarjallistaa raportti markdown-merkkijonoksi ja toivoa, että kohde renderöi sen. Yleensä se ei renderöi.
Todellinen vastaus on, että leikepöytä pitää useita esityksiä yhtä aikaa. Joten kirjoitamme kaksi: HTML-version, jossa muotoilu on tallella ja kuvakaappaukset upotettuina sisäisinä kuvina, ja pelkkä teksti -varajärjestelyn kohteille, jotka eivät ota HTML:ää vastaan. Liitä rikkaaseen editoriin ja saat muotoillun raportin kuvineen; liitä pelkkään tekstikenttään ja saat puhdasta tekstiä. "Muotoiltu kopiointi" ei ollut koskaan sarjallistamisongelma — se oli valitse-oikea-leikepöytäformaatti -ongelma.
Sama julkaisu antoi raportille myös chat-tyyliset Me / Them -nimikkeet kahden puhujan asettuessa vastakkaisille puolille, jotta transkripti lukeutuu kuin se keskustelu, joka se oli, ja siirsi omat kuvakaappauksesi omalle puolelleen sitä vastaamaan. (Yksi tämän ikkunan julkaisuista, v1.6.14, oli puhdas uudelleenkoonti — ei tarinaa siinä, ja on rehellistä sanoa se ääneen.)
Kolme asiaa, jotka striimaava käyttöliittymä opetti meille
- Puskuroi striimatut kentät, julkaise kerran per pala. Jokaisen server-sent eventin annattaminen ohjata omaa tilapäivitystään on välkyntä. Ref-kerääjä plus yksi julkaisu per pala, puuhun joka ei koskaan purkaudu, muuttaa hermostuneen kokoamisen rauhalliseksi täyttymiseksi.
- Kaikki mikä striimaa tarvitsee striimaustietoisen renderöijän. Renderöijä, joka jäsentää koko merkkijonon uudelleen per token, välkkyy keskeneräisellä markdownilla. Käytä sellaista, joka on rakennettu keskeneräiselle syötteelle — ja jos sinulla on jo sellainen toiselle pinnalle, käytä sitä uudelleen ennen kuin rakennat toisen.
- Muotoiltu kopiointi koskee leikepöytäformaatteja, ei sarjallistamista. Kirjoita
text/htmlupotetuilla kuvilla jatext/plain-varajärjestely. Leikepöytä suunniteltiin kantamaan molemmat; käytä sitä.
Tämä on viides luku luotettavuus- ja viimeistelytarinassa, josta tulee GeekBye v2. Edellisen luvun näet täältä: earbuds-bugi (v1.6.12); siitä, missä sama server-sent-events-idea muuttui myöhemmin kokonaiseksi varakuljetukseksi: live-transkribointi kun palomuuri estää WebSocketit (v2.0.8); ja koko kaaren näet täältä: ohjelmiston toimittamisen anatomia täydellisyyteen.