(role, text)
| 363 | } |
| 364 | |
| 365 | function addMessage(role, text) { |
| 366 | if (text.ok) { |
| 367 | text = text.data; |
| 368 | } |
| 369 | if (typeof text !== "string") { |
| 370 | text = JSON.stringify(text, null, 2); |
| 371 | } |
| 372 | const wrap = document.createElement("div"); |
| 373 | wrap.className = `msg ${role}`; |
| 374 | wrap.innerHTML = ` |
| 375 | <div class="role ${role}">${role}${role === "tool" ? '<span class="chevron">▶</span>' : ''}</div> |
| 376 | <div class="bubble"></div>`; |
| 377 | const bubble = wrap.querySelector(".bubble"); |
| 378 | if (role === "assistant") { |
| 379 | bubble.classList.add("markdown-content"); |
| 380 | bubble.innerHTML = renderMarkdown(text); |
| 381 | } else { |
| 382 | bubble.textContent = text; |
| 383 | } |
| 384 | bubble.onclick = function () { |
| 385 | if (bubble.scrollHeight > 100 && role === "tool") { |
| 386 | const expanded = bubble.style.maxHeight === 'none'; |
| 387 | bubble.style.maxHeight = expanded ? '' : 'none'; |
| 388 | bubble.style.borderBottom = expanded ? '' : 'dotted 2px gray'; |
| 389 | wrap.querySelector(".chevron").style.transform = expanded ? '' : 'rotate(90deg)'; |
| 390 | } |
| 391 | } |
| 392 | msgsEl.insertBefore(wrap, thinkingIndicatorEl); |
| 393 | msgsEl.scrollTop = msgsEl.scrollHeight; |
| 394 | } |
| 395 | |
| 396 | function setStatus(text) { |
| 397 | statusEl.textContent = text; |
no test coverage detected