Steven
Steven5 min čtení

Jak streamovat živý report bez blikání

Když vaše schůzka skončí, souhrn od GeekBye se teď doplňuje živě, místo aby vás nutil zírat na točící se kolečko. Aby streamované rozhraní působilo klidně, a ne trhaně, jsme museli vyřešit blikání dvakrát — jednou u strukturovaných polí, jednou u markdownu — a druhá oprava byl renderer, který jsme už měli.

Inženýrství
Frontend
Streamování
Vydání GeekBye
Jak streamovat živý report bez blikání

Před tímto vydáním znamenalo ukončení schůzky v GeekBye zírání na točící se kolečko. Aplikace odeslala celý váš přepis k analýze, počkala na celý souhrn — skóre, klíčové body, akční položky, celý balík — a pak ho na obrazovku vysypala najednou. Funkční, ale působilo to pomale, protože jste zatímco pracovala zírali do prázdna.

GeekBye v1.6.13–v1.6.15 to proměnilo v report, který se streamuje živě, pole po poli, tak jak se generuje. Zajímavá část není streamování — je to všechno, co jsme museli udělat, aby streamování nevypadalo trhaně. Klidné streamování a rozklepané streamování jsou tatáž funkce s velmi odlišným provedením.

Blikání, část první: nenech každý chunk lomcovat Reactem

Souhrn není jeden blok — jsou to strukturovaná pole (celkové skóre, klíčové body, akční položky a tak dále), která backend vysílá po jednom přes server-sent events. Naivní způsob, jak to renderovat, je aktualizovat stav Reactu při každé příchozí události.

Udělejte to a dostanete blikání. Každé příchozí pole spustí své vlastní překreslení; komponenty, které zobrazovaly „prázdno", se namontují, pak odmontují, pak znovu namontují jako „načtené"; rozvržení sebou při každém paketu cukne. Report se skládá sám před vámi tím nejhorším možným způsobem — viditelně, nervózně.

Oprava je dvoudílná disciplína. Zaprvé, akumulujte příchozí pole v refu — proměnlivém držáku, který nespouští překreslení — a publikujte do stavu Reactu jednou za chunk s čerstvou kopií, aby se strom aktualizoval záměrně, ne při každé mikroudálosti. Zadruhé, renderujte jeden stabilní strom komponent po celou dobu, ať už streamuje, nebo je hotovo, s inline zástupnými znaky pro pole, která ještě nedorazila:

// 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>

Komponenta zobrazující skóre se nikdy neodmontuje — jen ukazuje , dokud číslo nepřistane, a pak číslo. Nic sebou necukne. A plně složený objekt se nakonec stále ukládá do mezipaměti v lokální databázi, takže streamované rozhraní a trvalé úložiště koexistují, aniž by spolu bojovaly.

Blikání, část druhá: renderer, který jsme už měli

Druhé blikání je jemnější a žije v samotném markdownu. Report renderuje formátovaný markdown — nadpisy, tučné písmo, seznamy, bloky kódu. Ale markdown přicházející uprostřed streamu je v každém daném okamžiku nedokončený. Seznam s jednou odrážkou zatím. Blok kódu, který byl otevřen, ale ne uzavřen. Značka tučného písma, která zatím nemá partnera.

Konvenční renderer markdownu znovu parsuje celý řetězec při každém tokenu, a ten nedokončený stav se pokaždé zparsuje na něco jiného — takže seznam bliká, blok kódu problikává otevřený a zavřený, rozvržení skáče, jak se tokeny dokončují. Je to tatáž nervózní montáž jako předtím, o vrstvu níž.

Oprava byla téměř trapně po ruce: už jsme používali renderer markdownu vědomý si streamingu pro živý AI chat. Renderer postavený tak, aby toleroval neúplné tokeny — aby renderoval nedokončený markdown stabilně a usadil ho, až se tokeny dokončí — místo aby pokaždé parsoval od nuly. Report jen potřeboval použít ten samý. Přesně tenhle problém jsme pro chat vyřešili o měsíce dřív; „opravou" pro report bylo rozpoznat, že už ten nástroj máme, a namířit ho na druhou plochu. Znovupoužití porazilo přestavbu.

Bonus: formátované kopírování je problém formátu schránky

Jakmile report vypadal dobře, lidé ho chtěli vložit — do dokumentu, do e-mailu — a zachovat formátování i snímky obrazovky. Instinkt je serializovat report do markdownového řetězce a doufat, že ho cíl vyrenderuje. Obvykle to neudělá.

Skutečná odpověď je, že schránka drží více reprezentací najednou. Tak zapíšeme dvě: HTML verzi s neporušeným formátováním a snímky obrazovky vloženými jako inline obrázky, a záložní prostý text pro cíle, které HTML nepřijmou. Vložte do formátovaného editoru a dostanete zformátovaný report s obrázky; vložte do pole s prostým textem a dostanete čistý text. „Formátované kopírování" nikdy nebylo problémem serializace — byl to problém výběru-správného-formátu-schránky.

Totéž vydání dalo reportu i chatové štítky Já / Oni se dvěma mluvčími zarovnanými na opačné strany, aby se přepis četl jako konverzace, kterou byl, a přesunulo vaše vlastní snímky obrazovky na jejich stranu, aby to sedělo. (Jedno vydání v tomto okně, v1.6.14, byla čistá přestavba — žádný příběh tam není, a je poctivé to říct.)

Tři věci, které nás streamované rozhraní naučilo

  1. Bufferuj příchozí pole, publikuj jednou za chunk. Nechat každou server-sent event řídit vlastní aktualizaci stavu je to blikání. Ref akumulátor plus jediná publikace za chunk, do stromu, který se nikdy neodmontuje, promění nervózní montáž v klidné vyplňování.
  2. Cokoli, co streamuje, potřebuje renderer vědomý si streamingu. Renderer, který při každém tokenu znovu parsuje celý řetězec, bude na nedokončeném markdownu blikat. Použij takový, který je postaven pro neúplný vstup — a pokud už jeden pro jinou plochu máš, znovu ho použij, než postavíš druhý.
  3. Formátované kopírování je o formátech schránky, ne o serializaci. Zapiš text/html s vloženými obrázky a záložní text/plain. Schránka byla navržena tak, aby nesla oboje; využij to.

Tohle je pátá kapitola příběhu o spolehlivosti a vyladění, který se stává GeekBye v2. Pro předchozí kapitolu viz chyba se sluchátky (v1.6.12); pro to, kde se stejný nápad se server-sent-events později stal celým záložním transportem, živý přepis, když firewall blokuje WebSockety (v2.0.8); a pro celý oblouk anatomie dodávání softwaru k dokonalosti.

Související články

Ticho bylo nosné
Steven
Steven7 min čtení

Ticho bylo nosné

Poslední dvě vydání GeekBye v1 jsou o téže nepříjemné pravdě: přepis v reálném čase přes skutečnou síť není bezeztrátový a poctivý krok je přestat předstírat, že je. v1.8.20 uchovávalo kopii každého audio chunku na disku, než ho během reconnectu zahodilo, a začalo mezery v přepisu označovat nahlas. v1.9.0 přestalo posílat ticho kvůli úspoře šířky pásma — a zjistilo, že ticho bylo přesně ten signál, podle kterého přepisovač poznal, že věta skončila. Dvě vydání o ceně zahazování věcí.

Inženýrství
Audio
Spolehlivost
Tři slovesa, která udržují Web Audio naživu
Steven
Steven8 min čtení

Tři slovesa, která udržují Web Audio naživu

Dvě bodová vydání GeekBye, dva měsíce od sebe a ve dvou různých souborech, naučila náš audio kód stejné lekci z opačných konců: přestaň zacházet s AudioContext prohlížeče jako s jednorázovým. Jedno vydání se naučilo volat resume() na kontextu, který macOS tiše suspendoval uprostřed nahrávání; druhé se naučilo volat suspend() místo close(), aby po sobě jdoucí sesiony přestaly narážet do stropu Chromia zhruba šesti kontextů. Resume, suspend, close — to je celá zápletka.

Inženýrství
Audio
Desktop
Rozeznat hovor od otevřené aplikace
Steven
Steven8 min čtení

Rozeznat hovor od otevřené aplikace

GeekBye si umí všimnout, že ses připojil k videoschůzce, a nabídnout, že ji nahraje. Detekce se ukazuje být tou snadnou polovinou — Swift binary čtoucí titulky oken každých deset sekund. Tvrdá polovina je přesnost: nespustit se, když je Zoom jen otevřený, neptat se na schůzku, kterou už nahráváš, a neztlumit mikrofon v hovoru, ve kterém opravdu jsi. Tři vydání, a každé je pojistka, která se musela naučit neporazit sama sebe.

Inženýrství
macOS
Desktop