@@ -147,6 +147,78 @@ function ensureCopyButtons() {
147147 } ) ;
148148}
149149
150+ function ensureTagPreProcess ( content ) {
151+ let processedContent = content ;
152+
153+ // Handle unclosed <think> tags
154+ if ( processedContent . includes ( '<think>' ) && ! processedContent . includes ( '</think>' ) ) {
155+ processedContent += '</think>' ;
156+ }
157+
158+ // Handle <tool_call> tags
159+ const lastToolCallIndex = processedContent . lastIndexOf ( '<tool_call>' ) ;
160+ if ( lastToolCallIndex !== - 1 ) {
161+ // Check if there's a </tool_call> after the last <tool_call>
162+ const contentAfterLastToolCall = processedContent . slice ( lastToolCallIndex ) ;
163+ if ( ! contentAfterLastToolCall . includes ( '</tool_call>' ) ) {
164+ processedContent += '</tool_call>' ;
165+ }
166+ }
167+
168+ return processedContent ;
169+ }
170+
171+ function processMathBlocks ( input ) {
172+ // Regex to match %%...%% blocks
173+ const percentPairRegex = / % % ( [ \s \S ] * ?) % % / g;
174+
175+ // Process the input string
176+ return input . replace ( percentPairRegex , ( percentMatch , percentContent ) => {
177+ // Regex to match $$...$$ blocks within %% content
178+ const mathBlockRegex = / \$ \$ ( [ \s \S ] * ?) \$ \$ / g;
179+
180+ // Process $$...$$ blocks within the %% content
181+ const processedContent = percentContent . replace ( mathBlockRegex , ( mathMatch , mathContent ) => {
182+ // Trim and check if the math content has newlines
183+ const trimmedContent = mathContent . trim ( ) ;
184+ if ( ! trimmedContent . includes ( '\n' ) ) {
185+ // No newlines, return unchanged
186+ return `$$${ trimmedContent } $$` ;
187+ }
188+
189+ // Replace newlines with \\ and wrap in aligned
190+ const singleLineContent = trimmedContent
191+ . split ( '\n' )
192+ . map ( line => line . trim ( ) )
193+ . filter ( line => line . length > 0 )
194+ . join ( ' \\\\ ' ) ;
195+
196+ return `$$ \\begin{aligned} ${ singleLineContent } \\end{aligned} $$` ;
197+ } ) ;
198+
199+ // Return the processed %% block
200+ return `%%${ processedContent } %%` ;
201+ } ) ;
202+ }
203+
204+ function separateThinkContent ( input ) {
205+ // Initialize outputs
206+ let thinkStr = '' ;
207+ let answerStr = input ;
208+
209+ // Regex to match <think>...</think> (non-greedy)
210+ const thinkRegex = / < t h i n k > ( [ \s \S ] * ?) < \/ t h i n k > / ;
211+
212+ // Find the first <think>...</think> match
213+ const match = input . match ( thinkRegex ) ;
214+ if ( match ) {
215+ thinkStr = match [ 0 ] ; // Full <think>...</think> content
216+ answerStr = input . replace ( thinkRegex , '' ) ; // Remove <think>...</think>
217+ }
218+
219+ return { thinkStr, answerStr } ;
220+ }
221+
150222// 渲染消息
151223async function renderMessage ( role , content , index ) {
152224 const lastChild = chat . lastElementChild ;
@@ -164,14 +236,16 @@ async function renderMessage(role, content, index) {
164236
165237 if ( role === 'model' ) {
166238 let markdownContent = targetDiv . dataset . markdownContent ;
167-
168- if ( markdownContent . includes ( '<think>' ) && ! markdownContent . includes ( '</think>' ) ) {
169- markdownContent += "</think>" ;
170- }
239+ markdownContent = ensureTagPreProcess ( markdownContent ) ;
171240
172241 markdownContent = markdownContent . replace ( / \$ \$ 包 裹 / g, '&doller; &doller; 包裹' ) ;
173242
174- targetDiv . innerHTML = marked . parse ( markdownContent , {
243+ markdownContent = processMathBlocks ( markdownContent ) ;
244+
245+ const { thinkStr, answerStr } = separateThinkContent ( markdownContent ) ;
246+ markdownContent = answerStr ;
247+
248+ targetDiv . innerHTML = thinkStr + marked . parse ( markdownContent , {
175249 breaks : false ,
176250 mangle : false ,
177251 headerIds : false ,
0 commit comments