Orice interfață de chat self-hosted îți dă exact același lucru: un zid de markdown. Îi ceri modelului cifrele de trimestrul trecut, îți scrie un tabel în monospace, și dacă vrei un grafic adevărat te duci și dai paste cifrelor în altă parte, ca și cum am fi în 2011. Alea mai fițoase pun modelul să scuipe un PNG în base64, adică o poză cu un grafic pe care modelul l-a desenat în cap, cu axe inventate, pe care nu poți nici să dai click, nici să filtrezi, nici să te bazezi.
chatz face chestia pe care nu se obosește nimeni să o facă: asistentul desenează interfață reală, vie direct în chat. Nu poza unui grafic. O componentă randată de-adevăratelea, responsive, construită de browser.
Asistentul îți desenează dashboard-ul
Uite mecanismul, pentru că e mai prost și mai bun decât ai bănui. Răspunsul modelului poate să conțină un bloc ```spec, iar înăuntru sunt patch-uri JSON RFC-6902. Browserul se uită la stream, prinde blocul client-side, și asamblează patch-urile într-o componentă vie dintr-un catalog fix. Modelul nu rulează cod. Nu desenează pixeli. Emite o specificație declarativă mică, iar frontend-ul o randează, exact cum ar randa orice altă componentă.
Catalogul are 26 de componente (am numărat fișierele, sunt fix 26), e închis și tipat, așa că modelul alege dintr-un meniu în loc să inventeze markup care poate parsează, poate nu. Dincolo de text, layout, status și tabele, e fix analytics-ul la care ai vrea să apeleze un model: time-series, area, sparkline, bar, donut, funnel, gauge, scatter, heatmap, histogram, box-plot, treemap, un graf de rețea și un log-viewer mare. Fiecare grafic e SVG nativ, cu zero dependințe de runtime de charting. Fără d3, fără chart.js, fără 400kb de bibliotecă livrată ca să deseneze o bară.
Și e cinstit în singurul fel care contează: e o componentă vie, alimentată cu valori reale, nu poza unui grafic halucinat de model. Dacă un tool MCP se întoarce cu cifre, modelul le bagă direct într-un gauge sau într-un treemap și primești ceva la care te poți uita, în loc de un paragraf care descrie ceva.
O tură care nu te minte despre ce s-a întâmplat
Streaming-ul e SSE în stil Anthropic, un singur fir de la backend la browser, iar ce mă interesează pe mine e că o tură se randează în ordinea în care sosește. Textul, raționamentul și apelurile de tool vin amestecate exact cum le emite modelul, text → tool → text → tool, în loc să fie îngrămădite într-un zid nediferențiat la final, cum fac majoritatea interfețelor. Raționamentul, la modelele care îl expun, curge în propriul bloc de gândire pliabil. Fiecare apel de tool e un card care trece prin CALLING → DONE/ERROR, cu numele, argumentele streamuite și rezultatul. Vezi forma reală a turei cum se întâmplă.
Și nu îți mănâncă munca. Mesajul tău se salvează înainte să pornească stream-ul, așa că dacă dai stop sau dai refresh la mijlocul răspunsului nu se evaporă ce tocmai ai scris. Un răspuns care încă streamuia rămâne salvat ca parțial marcat explicit ca întrerupt. Un refresh îl păstrează, dar nu e niciodată strecurat pe furiș într-o cerere ulterioară către model ca și cum ar fi complet. Când aterizează răspunsul adevărat, îl înlocuiește atomic. Fără „hopa, s-a dus”, fără jumătăți de răspuns care se dau drept întregi.
Un singur lucru de rulat și o bază de date la alegerea ta
Acum partea cu care README-ul lui se laudă, pe bună dreptate. E un singur lucru de rulat. Interfața e coaptă în aplicație, deci nu există un frontend separat de construit, de găzduit undeva și de legat înapoi la backend. Chat-ul și API-ul sunt același container. Și îl rulezi cu Docker, așa cum e gândit: tragi imaginea multi-arch publicată (psyb0t/chatz pe Docker Hub, un tag fixat :v0.7.9, nu :latest) și dai docker run cu SQLite pe un volum, sau dai docker compose up dintr-un checkout dacă vrei Postgres.
Iar rularea livrată e închisă la sânge din start, nu lăsată ca temă pentru acasă. Comanda docker run din documentație vine cu filesystem root read-only, --cap-drop ALL, user non-root, no-new-privileges și limite de memorie, CPU și PID, iar singurele lucruri în care poate scrie sunt /data și un fișier de log montat. E genul ăla rar de proiect în care comanda de deploy pe care o dai copy-paste e deja cea securizată.
Persistența e Postgres implicit, sau mod SQLite într-un singur proces: pui CHATZ_DB_DRIVER=sqlite și gata, e un fișier pe un volum, fără server de bază de date de ținut în viață, pentru când ești doar tu. Și cam ăsta e tot stack-ul. Fără Temporal. Fără NATS. Fără message broker. Fără service mesh. Fără compose de cincisprezece containere de care ți-e frică să te atingi. E o aplicație de chat și e construită ca una, ceea ce în 2026 se pare că trece drept poziție de design.
Rahaturile plictisitoare pe care chiar le-a nimerit
Casetă de chat poate livra oricine. Semnele se văd în părțile pe care lumea le sare:
- Secretele MCP chiar sunt criptate. Adaugi servere de tool-uri MCP (stdio sau HTTP), sau imporți un
.mcp.jsonîn stil Claude, iar secretele din headerele HTTP și env-ul de stdio sunt sigilate la repaus cu AES-256-GCM sub o cheieCHATZ_SECRETS_KEYde 32 de octeți. Nu base64 și-o rugăciune, chiar criptate, iar dacă nu pui cheia refuză categoric să stocheze secretul în loc să îl țină în clar. ValorileAuthorizationsalvate rămân mascate în interfața de admin. - Nu se înscrie nimeni singur. La prima pornire aplicația e în stare de setup, cu zero utilizatori;
/setupface singurul admin, iar adminul îi creează pe toți ceilalți. Nu există înregistrare publică pe care să uiți să o închizi. Ești singur?CHATZ_AUTH_PASSWORDLESS=trueîl loghează automat pe singurul admin, ca să nu tastezi o parolă ca să vorbești cu propria ta mașină. - Istoricul e plafonat, ca un chat lung să nu ajungă o factură cu patru cifre. Fiecare chat limitează istoricul trimis (100.000 de tokeni implicit). Cel mai vechi mesaj de sistem rămâne lipit și tura curentă rămâne fixată; istoricul mai vechi se adaugă de la nou spre vechi până când următoarea unitate întreagă de mesaj ar sări peste plafon. Doar unități întregi, ca un rezultat de tool să nu rămână niciodată orfan de apelul care l-a produs. Iar caseta de scris îți arată selecția reală pentru tura următoare: sistemul lipit, istoricul păstrat, ciorna curentă, tokenii consumați și cei liberi, și ce ture complete au picat. Fără ghicit cât plătești de fapt ca să trimiți.
Toți providerii pe care îi ai, într-o singură listă
Asta e partea cu care aș începe un demo. CHATZ_UPSTREAMS e un array JSON și înghesui în el câți provideri vrei, toți activi în același timp. Ollama care învârte modele locale pe mașina ta. aigate, propriul tău gateway care stă în fața a ce găzduiești. Anthropic. z.ai. Orice endpoint compatibil OpenAI la care ajungi. Chatz întreabă fiecare endpoint de modele ce are de oferit, descoperă ce servește de fapt, și le contopește pe toate într-un singur selector.
Deci deschizi dropdown-ul de modele și llama ta locală stă fix lângă Claude, lângă ce-o fi în spatele gateway-ului tău, iar când alegi unul tura pleacă direct înapoi spre upstream-ul din care a venit. Fiecare upstream își numește propriul driver (openai, care acoperă și gateway-urile compatibile OpenAI, sau anthropic) și propria cheie prin numele variabilei de mediu, niciodată inline, iar fiecare driver e izolat de credențialele celorlalți, ca să nu se scurgă cheia unui provider în apelurile altuia. Aliasurile prietenoase stau deasupra fără să înlocuiască vreodată ID-ul real al modelului, iar controalele de raționament se sting pentru modelele care nu anunță că îl suportă, ca selectorul să nu îți ofere un buton care nu face nimic.
Un mod de demo pentru când modelul nu cooperează în fața camerei
Un lucru mic, dar care arată că cineva chiar a încercat să înregistreze treaba asta: modul showcase. Demo-urile cu model live o iau razna fix când pornește camera, așa că pui CHATZ_SHOWCASE_MODE=true și ridici stack-ul. Păstrează lista reală de modele, setarea MCP și comportamentul chat-ului, dar interceptează anumite prompturi din catalog cu gândire deterministă, activitate de tool sintetică și apoi dashboard-urile încorporate, fiecare metrică afișată fiind ancorată în rezultatele sintetice vizibile, nu scoasă din burtă. Răspunsurile se salvează ca la orice chat normal, așa că o înregistrare poate să dea refresh sau să continue. E diferența dintre a demonstra interfață generativă și a te ruga ca modelul să îți deseneze graficul de care ai nevoie abia la a patra dublă.
E devreme, v0.7.9 și se mișcă repede. Dar pariul de bază e deja acolo și e unul bun: un chat self-hosted în care asistentul randează componente reale, vii, în loc să le descrie, livrat ca un singur container mic și securizat (un binar Go cu interfața coaptă înăuntru) plus o bază de date la alegerea ta și nimic altceva. Îndreaptă-l spre endpointurile tale compatibile OpenAI sau Anthropic, leagă-ți tool-urile MCP, și lasă modelul să îți deseneze un dashboard care nu e o minciună.
github.com/psyb0t/chatz