From 8ff5d83e14b8e1e062c7c154ffd3737548d1bd45 Mon Sep 17 00:00:00 2001 From: TaraTheStar Date: Wed, 8 Apr 2026 16:51:57 +0000 Subject: [PATCH 1/2] feat: add support for displaying thinking/reasoning blocks in chat --- static/messages.js | 27 ++++++++++++++++++++++++++- static/ui.js | 16 ++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/static/messages.js b/static/messages.js index 284ca5b..47461f6 100644 --- a/static/messages.js +++ b/static/messages.js @@ -84,6 +84,11 @@ async function send(){ let assistantText=''; let assistantRow=null; let assistantBody=null; + // Thinking tag patterns for streaming display + const _thinkPairs=[ + {open:'',close:''}, + {open:'<|channel>thought\n',close:''} + ]; function ensureAssistantRow(){ if(assistantRow)return; @@ -106,12 +111,32 @@ async function send(){ // rAF-throttled rendering: buffer tokens, render at most once per frame let _renderPending=false; + // Extract display text from assistantText, stripping completed thinking blocks + // and hiding content still inside an open thinking block. + function _streamDisplay(){ + let t=assistantText; + for(const {open,close} of _thinkPairs){ + if(!t.startsWith(open)) continue; + const ci=t.indexOf(close,open.length); + if(ci!==-1){ + // Thinking block complete — strip it, show the rest + return t.slice(ci+close.length).replace(/^\s+/,''); + } + // Still inside thinking block — show placeholder + return ''; + } + return t; + } function _scheduleRender(){ if(_renderPending) return; _renderPending=true; requestAnimationFrame(()=>{ _renderPending=false; - if(assistantBody) assistantBody.innerHTML=renderMd(assistantText); + if(assistantBody){ + const txt=_streamDisplay(); + const isThinking=!txt&&assistantText.length>0; + assistantBody.innerHTML=txt?renderMd(txt):(isThinking?'Thinking\u2026':''); + } scrollIfPinned(); }); } diff --git a/static/ui.js b/static/ui.js index f318a32..3b5ab78 100644 --- a/static/ui.js +++ b/static/ui.js @@ -503,6 +503,22 @@ function renderMessages(){ if(!thinkingText && m.reasoning){ thinkingText=m.reasoning; } + // Parse inline thinking tags from plain text: ... (DeepSeek, QwQ, etc.) + // and Gemma 4 channel tokens: <|channel>thought\n... + if(!thinkingText && typeof content==='string'){ + const thinkMatch=content.match(/^([\s\S]*?)<\/think>\s*/); + if(thinkMatch){ + thinkingText=thinkMatch[1].trim(); + content=content.slice(thinkMatch[0].length); + } + if(!thinkingText){ + const gemmaMatch=content.match(/^<\|channel>thought\n([\s\S]*?)\s*/); + if(gemmaMatch){ + thinkingText=gemmaMatch[1].trim(); + content=content.slice(gemmaMatch[0].length); + } + } + } const isUser=m.role==='user'; const isLastAssistant=!isUser&&vi===visWithIdx.length-1; // Render thinking card before the assistant message (collapsed by default) From 5f7564e8bba6a6ffbb6b25d5a33400e40f4d15fa Mon Sep 17 00:00:00 2001 From: Nathan Esquenazi Date: Wed, 8 Apr 2026 18:14:47 +0000 Subject: [PATCH 2/2] fix: harden thinking block streaming display Hide partial tag prefixes during streaming and rename the local display variable for clarity. References #181. --- static/messages.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/static/messages.js b/static/messages.js index 47461f6..0209c2f 100644 --- a/static/messages.js +++ b/static/messages.js @@ -114,18 +114,22 @@ async function send(){ // Extract display text from assistantText, stripping completed thinking blocks // and hiding content still inside an open thinking block. function _streamDisplay(){ - let t=assistantText; + const raw=assistantText; for(const {open,close} of _thinkPairs){ - if(!t.startsWith(open)) continue; - const ci=t.indexOf(close,open.length); - if(ci!==-1){ - // Thinking block complete — strip it, show the rest - return t.slice(ci+close.length).replace(/^\s+/,''); + if(raw.startsWith(open)){ + const ci=raw.indexOf(close,open.length); + if(ci!==-1){ + // Thinking block complete — strip it, show the rest + return raw.slice(ci+close.length).replace(/^\s+/,''); + } + // Still inside thinking block — show placeholder + return ''; } - // Still inside thinking block — show placeholder - return ''; + // Hide partial tag prefixes while streaming so users don't see + // `