Skip to content

Commit 8472321

Browse files
authored
feat(ui): display thinking tags appropriately (#5540)
* fix(streaming): stream complete runes Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(ui): display thinking tags separately Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Signed-off-by: Ettore Di Giacinto <mudler@users.noreply.github.com>
1 parent 3bac472 commit 8472321

File tree

2 files changed

+69
-3
lines changed

2 files changed

+69
-3
lines changed

core/http/static/chat.js

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ async function promptGPT(systemPrompt, input) {
280280

281281
let buffer = "";
282282
let contentBuffer = [];
283+
let thinkingContent = "";
284+
let isThinking = false;
285+
let lastThinkingMessageIndex = -1;
283286

284287
try {
285288
while (true) {
@@ -303,7 +306,44 @@ async function promptGPT(systemPrompt, input) {
303306
const token = jsonData.choices[0].delta.content;
304307

305308
if (token) {
306-
contentBuffer.push(token);
309+
// Check for thinking tags
310+
if (token.includes("<thinking>") || token.includes("<think>")) {
311+
isThinking = true;
312+
thinkingContent = "";
313+
lastThinkingMessageIndex = -1;
314+
return;
315+
}
316+
if (token.includes("</thinking>") || token.includes("</think>")) {
317+
isThinking = false;
318+
if (thinkingContent.trim()) {
319+
// Only add the final thinking message if we don't already have one
320+
if (lastThinkingMessageIndex === -1) {
321+
Alpine.store("chat").add("thinking", thinkingContent);
322+
}
323+
}
324+
return;
325+
}
326+
327+
// Handle content based on thinking state
328+
if (isThinking) {
329+
thinkingContent += token;
330+
// Update the last thinking message or create a new one
331+
if (lastThinkingMessageIndex === -1) {
332+
// Create new thinking message
333+
Alpine.store("chat").add("thinking", thinkingContent);
334+
lastThinkingMessageIndex = Alpine.store("chat").history.length - 1;
335+
} else {
336+
// Update existing thinking message
337+
const chatStore = Alpine.store("chat");
338+
const lastMessage = chatStore.history[lastThinkingMessageIndex];
339+
if (lastMessage && lastMessage.role === "thinking") {
340+
lastMessage.content = thinkingContent;
341+
lastMessage.html = DOMPurify.sanitize(marked.parse(thinkingContent));
342+
}
343+
}
344+
} else {
345+
contentBuffer.push(token);
346+
}
307347
}
308348
} catch (error) {
309349
console.error("Failed to parse line:", line, error);
@@ -322,6 +362,9 @@ async function promptGPT(systemPrompt, input) {
322362
if (contentBuffer.length > 0) {
323363
addToChat(contentBuffer.join(""));
324364
}
365+
if (thinkingContent.trim() && lastThinkingMessageIndex === -1) {
366+
Alpine.store("chat").add("thinking", thinkingContent);
367+
}
325368

326369
// Highlight all code blocks once at the end
327370
hljs.highlightAll();
@@ -375,7 +418,17 @@ document.addEventListener("alpine:init", () => {
375418
},
376419
add(role, content, image, audio) {
377420
const N = this.history.length - 1;
378-
if (this.history.length && this.history[N].role === role) {
421+
// For thinking messages, always create a new message
422+
if (role === "thinking") {
423+
let c = "";
424+
const lines = content.split("\n");
425+
lines.forEach((line) => {
426+
c += DOMPurify.sanitize(marked.parse(line));
427+
});
428+
this.history.push({ role, content, html: c, image, audio });
429+
}
430+
// For other messages, merge if same role
431+
else if (this.history.length && this.history[N].role === role) {
379432
this.history[N].content += content;
380433
this.history[N].html = DOMPurify.sanitize(
381434
marked.parse(this.history[N].content)

core/http/views/chat.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,20 @@ <h1 class="text-lg font-semibold">
257257
</div>
258258
</div>
259259
</template>
260-
<template x-if="message.role != 'user'">
260+
<template x-if="message.role === 'thinking'">
261+
<div class="flex items-center space-x-2 w-full">
262+
<div class="flex flex-col flex-1">
263+
<div class="p-2 flex-1 rounded bg-blue-900/50 text-blue-100 border border-blue-700/50">
264+
<div class="flex items-center space-x-2">
265+
<i class="fa-solid fa-brain text-blue-400"></i>
266+
<span class="text-xs font-semibold text-blue-300">Thinking</span>
267+
</div>
268+
<div class="mt-1" x-html="message.html"></div>
269+
</div>
270+
</div>
271+
</div>
272+
</template>
273+
<template x-if="message.role != 'user' && message.role != 'thinking'">
261274
<div class="flex items-center space-x-2">
262275
{{ if $galleryConfig }}
263276
{{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg mt-2 max-w-8 max-h-8">{{end}}

0 commit comments

Comments
 (0)