diff --git a/public/css/assistant.css b/public/css/assistant.css index 4e5f8073..485e03b5 100644 --- a/public/css/assistant.css +++ b/public/css/assistant.css @@ -23,13 +23,13 @@ #assistant-chat-view:not(:has(.assistant-msg)) { min-height:min(62vh, 640px); } #assistant-chat-view:has(.assistant-msg) { min-height:calc(100vh - 190px); } .assistant-messages:not(:has(.assistant-msg)) { display:flex; flex-direction:column; justify-content:center; } - .assistant-messages:not(:has(.assistant-msg)) .assistant-empty { margin:0 auto; } + .assistant-messages:not(:has(.assistant-msg)) .assistant-empty { margin:0 auto 28px; } } .assistant-messages { padding:16px; max-height:none; overflow-y:auto; overflow-x:hidden; background:transparent; min-width:0; } .assistant-empty { max-width:680px; margin:50px auto; text-align:center; color:var(--g500); } .assistant-empty i { font-size:34px; color:var(--blue); margin-bottom:10px; } .assistant-empty h3 { color:var(--g800); font-size:18px; margin-bottom:6px; } -.assistant-examples { display:flex; gap:8px; flex-wrap:wrap; justify-content:center; margin-top:16px; } +.assistant-examples { display:flex; gap:8px; flex-wrap:wrap; justify-content:center; margin-top:16px; margin-bottom:6px; } .assistant-examples button { border:1px solid var(--g200); background:white; color:var(--g700); border-radius:999px; padding:7px 10px; font-size:12px; cursor:pointer; } .assistant-suggestion-buttons { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; } .assistant-suggestion-buttons button { border:1px solid var(--blue-light); background:var(--blue-light); color:var(--blue); border-radius:999px; padding:7px 10px; font-size:12px; cursor:pointer; text-align:left; } diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js index 594e1065..61fa3c49 100644 --- a/public/js/clinicalAssistant.js +++ b/public/js/clinicalAssistant.js @@ -707,9 +707,14 @@ import { // A header row with nothing under it yet is a table markdown-it would // happily draw — two lines of pipes and an empty body. Not yet. var headerOnly = !growing && TABLE_ROW.test(tail.trim().split('\n')[0] || ''); + // A bold run whose closing ** has not arrived yet shows as literal + // asterisks for a frame or two; closing it for the render keeps the text + // bold from its first character. The real text is untouched. + var balanced = tail; + if ((tail.match(/\*\*/g) || []).length % 2 === 1) balanced = tail + '**'; if (tail && !openFence && !headerOnly) { state.tail.className = 'assistant-stream-tail assistant-stream-partial'; - state.tail.innerHTML = renderAssistantBubbleHtml(growing || tail, sources, false); + state.tail.innerHTML = renderAssistantBubbleHtml(growing || balanced, sources, false); renderEmbeddedBlocks(state.tail); state.tail.hidden = false; } else { diff --git a/test/backend-hardening.test.js b/test/backend-hardening.test.js index c5040f99..2fa2eeec 100644 --- a/test/backend-hardening.test.js +++ b/test/backend-hardening.test.js @@ -332,3 +332,8 @@ test('opening the share panel does not redraw the library, and the Nextcloud upl assert.match(panel, /mr-shared-note/); assert.match(js, /createTextNode\(' Nextcloud'\)/); }); + +test('a streaming tail with an unclosed bold run is rendered closed, and the starter questions keep clear of the box', () => { + assert.match(read('public/js/clinicalAssistant.js'), /if \(\(tail\.match\(\/\\\*\\\*\/g\) \|\| \[\]\)\.length % 2 === 1\) balanced = tail \+ '\*\*';/); + assert.match(read('public/css/assistant.css'), /\.assistant-empty \{ margin:0 auto 28px; \}/); +});