Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

aa: stream replies progressively + drop log sender

The aa bubble is now created on the first text chunk and its text is
mutated in place as more chunks arrive, so the reply appears live
instead of landing as one block on done. Aborts mid-stream keep the
partial text; empty streams are cleaned up.

Sender unified: log (smaller MatrixChunky8 font) and @aa (with leading
@) both collapsed into "aa" — system status lines render as regular
chat bubbles from the bot itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+30 -17
+30 -17
system/public/aesthetic.computer/disks/aa.mjs
··· 179 179 180 180 function pushMessage(from, text, { sound = false } = {}) { 181 181 const msg = { from, text, id: nextId(), sub: from }; 182 - // Render log/system messages in the smaller MatrixChunky8 font so they 183 - // feel like unobtrusive chrome, not loud chat turns. 184 - if (from === "log") msg.font = "MatrixChunky8"; 185 182 client.system.messages.push(msg); 186 183 if (client.system.messages.length > 500) client.system.messages.shift(); 187 184 client.system.receiver?.( ··· 194 191 } 195 192 196 193 function pushSystem(text) { 197 - return pushMessage("log", text); 194 + return pushMessage("aa", text); 198 195 } 199 196 200 197 function removeMessage(msg) { ··· 246 243 if (ev.type === "assistant" && ev.message?.content) { 247 244 for (const b of ev.message.content) { 248 245 if (b.type === "text" && b.text) { 249 - pushMessage("@aa", b.text); 246 + pushMessage("aa", b.text); 250 247 produced++; 251 248 } 252 249 } ··· 276 273 pending = true; 277 274 abortCtrl = new AbortController(); 278 275 279 - // One unobtrusive placeholder while streaming. Removed on done/error so the 280 - // chat stays at the "basic chat" level — no tool traces, no stderr spam. 281 - const thinkingMsg = pushMessage("log", "thinking…"); 276 + // Streamed reply: the aa bubble is created lazily on the first text chunk, 277 + // then mutated in place so the reply appears progressively. 278 + let streamMsg = null; 282 279 let pendingText = ""; 280 + const applyStream = () => { 281 + if (!streamMsg) streamMsg = pushMessage("aa", pendingText); 282 + else { 283 + streamMsg.text = pendingText; 284 + invalidate(); 285 + } 286 + }; 283 287 284 288 try { 285 289 const res = await fetch(`${ENDPOINT}/api/chat`, { ··· 293 297 }); 294 298 if (!res.ok) { 295 299 const body = await res.text().catch(() => `${res.status}`); 296 - removeMessage(thinkingMsg); 297 300 if (res.status === 401 || res.status === 403) { 298 301 isAdmin = false; 299 302 token = null; ··· 321 324 const ev = evt.data; 322 325 if (ev.type === "assistant" && ev.message?.content) { 323 326 for (const b of ev.message.content) { 324 - if (b.type === "text" && b.text) pendingText += b.text; 327 + if (b.type === "text" && b.text) { 328 + pendingText += b.text; 329 + applyStream(); 330 + } 325 331 // tool_use / thinking / other blocks deliberately suppressed 326 332 } 327 333 } else if (ev.type === "result" && ev.result) { 328 334 pendingText = ev.result; 335 + applyStream(); 329 336 } 330 337 // tool_result (in user events) also suppressed 331 338 } else if (evt.event === "error") { 332 - pushMessage("log", `! ${evt.data.message || "error"}`); 339 + pushMessage("aa", `! ${evt.data.message || "error"}`); 333 340 } else if (evt.event === "done") { 334 - removeMessage(thinkingMsg); 335 - if (pendingText) { 336 - pushMessage("@aa", pendingText, { sound: true }); 337 - pendingText = ""; 341 + if (streamMsg && pendingText) { 342 + streamMsg.text = pendingText; 343 + // Final refresh with sound cue — matches the old "message arrived" beep. 344 + client.system.receiver?.( 345 + streamMsg.id, 346 + "message", 347 + streamMsg, 348 + { layoutChanged: true }, 349 + ); 350 + } else if (streamMsg && !pendingText) { 351 + removeMessage(streamMsg); 338 352 } 339 353 } 340 354 // stderr events ignored — bridge warnings are not user-facing 341 355 } 342 356 } 343 357 } catch (err) { 344 - removeMessage(thinkingMsg); 358 + if (streamMsg && !pendingText) removeMessage(streamMsg); 345 359 if (err.name === "AbortError") pushSystem("cancelled."); 346 360 else pushSystem(`error: ${err.message}`); 347 361 } finally { 348 - removeMessage(thinkingMsg); // idempotent — no-op if already removed 349 362 pending = false; 350 363 abortCtrl = null; 351 364 }