mirror of
https://github.com/open-webui/open-webui
synced 2025-04-30 03:02:06 +00:00
enh: <think> tag support
This commit is contained in:
parent
8d3c73aed5
commit
c9dc7299c5
@ -1072,6 +1072,12 @@ async def process_chat_response(
|
||||
},
|
||||
)
|
||||
|
||||
# We might want to disable this by default
|
||||
detect_reasoning = True
|
||||
|
||||
reasoning_start_time = None
|
||||
reasoning_content = ""
|
||||
|
||||
async for line in response.body_iterator:
|
||||
line = line.decode("utf-8") if isinstance(line, bytes) else line
|
||||
data = line
|
||||
@ -1098,7 +1104,6 @@ async def process_chat_response(
|
||||
"selectedModelId": data["selected_model_id"],
|
||||
},
|
||||
)
|
||||
|
||||
else:
|
||||
value = (
|
||||
data.get("choices", [])[0]
|
||||
@ -1109,6 +1114,39 @@ async def process_chat_response(
|
||||
if value:
|
||||
content = f"{content}{value}"
|
||||
|
||||
if detect_reasoning:
|
||||
if "<think>\n" in content:
|
||||
reasoning_start_time = time.time()
|
||||
reasoning_content = ""
|
||||
content = content.replace("<think>\n", "")
|
||||
|
||||
if reasoning_start_time is not None:
|
||||
reasoning_content += value
|
||||
|
||||
if "</think>\n" in reasoning_content:
|
||||
reasoning_end_time = time.time()
|
||||
reasoning_duration = int(
|
||||
reasoning_end_time
|
||||
- reasoning_start_time
|
||||
)
|
||||
|
||||
reasoning_content = (
|
||||
reasoning_content.strip("<think>\n")
|
||||
.strip("</think>\n")
|
||||
.strip()
|
||||
)
|
||||
|
||||
if reasoning_content:
|
||||
# Format reasoning with <details> tag
|
||||
content = f"<details>\n<summary>Thought for {reasoning_duration} seconds</summary>\n{reasoning_content}\n</details>\n"
|
||||
else:
|
||||
content = ""
|
||||
|
||||
reasoning_start_time = None
|
||||
else:
|
||||
# Show ongoing thought process
|
||||
content = f"<details>\n<summary>Thinking… <loading/></summary>\n{reasoning_content}\n</details>\n"
|
||||
|
||||
if ENABLE_REALTIME_CHAT_SAVE:
|
||||
# Save message in the database
|
||||
Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||
@ -1129,10 +1167,8 @@ async def process_chat_response(
|
||||
"data": data,
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
done = "data: [DONE]" in line
|
||||
|
||||
if done:
|
||||
pass
|
||||
else:
|
||||
|
44
src/app.css
44
src/app.css
@ -127,6 +127,50 @@ select {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shimmer {
|
||||
background: linear-gradient(90deg, #9a9b9e 25%, #2a2929 50%, #9a9b9e 75%);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 4s linear infinite;
|
||||
color: #818286; /* Fallback color */
|
||||
}
|
||||
|
||||
:global(.dark) .shimmer {
|
||||
background: linear-gradient(90deg, #818286 25%, #eae5e5 50%, #818286 75%);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 4s linear infinite;
|
||||
color: #a1a3a7; /* Darker fallback color for dark mode */
|
||||
}
|
||||
|
||||
@keyframes smoothFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.status-description {
|
||||
animation: smoothFadeIn 0.2s forwards;
|
||||
}
|
||||
|
||||
.katex-mathml {
|
||||
display: none;
|
||||
}
|
||||
|
@ -195,7 +195,11 @@
|
||||
</ul>
|
||||
{/if}
|
||||
{:else if token.type === 'details'}
|
||||
<Collapsible title={token.summary} className="w-fit space-y-1">
|
||||
<Collapsible
|
||||
title={token.summary}
|
||||
isLoading={token?.isLoading ?? false}
|
||||
className="w-fit space-y-1"
|
||||
>
|
||||
<div class=" mb-1.5" slot="content">
|
||||
<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
|
||||
</div>
|
||||
|
@ -1,3 +1,3 @@
|
||||
<div class=" self-center font-semibold mb-0.5 line-clamp-1 flex gap-1 items-center">
|
||||
<div class=" self-center font-semibold line-clamp-1 flex gap-1 items-center">
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -1234,48 +1234,4 @@
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shimmer {
|
||||
background: linear-gradient(90deg, #9a9b9e 25%, #2a2929 50%, #9a9b9e 75%);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 4s linear infinite;
|
||||
color: #818286; /* Fallback color */
|
||||
}
|
||||
|
||||
:global(.dark) .shimmer {
|
||||
background: linear-gradient(90deg, #818286 25%, #eae5e5 50%, #818286 75%);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 4s linear infinite;
|
||||
color: #a1a3a7; /* Darker fallback color for dark mode */
|
||||
}
|
||||
|
||||
@keyframes smoothFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.status-description {
|
||||
animation: smoothFadeIn 0.2s forwards;
|
||||
}
|
||||
</style>
|
||||
|
@ -9,12 +9,14 @@
|
||||
|
||||
import ChevronUp from '../icons/ChevronUp.svelte';
|
||||
import ChevronDown from '../icons/ChevronDown.svelte';
|
||||
import Spinner from './Spinner.svelte';
|
||||
|
||||
export let open = false;
|
||||
export let className = '';
|
||||
export let buttonClassName =
|
||||
'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition';
|
||||
export let title = null;
|
||||
export let isLoading = false;
|
||||
|
||||
export let grow = false;
|
||||
|
||||
@ -34,12 +36,23 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class=" w-full font-medium flex items-center justify-between gap-2">
|
||||
<div
|
||||
class=" w-full font-medium flex items-center justify-between gap-2 {isLoading === true
|
||||
? 'shimmer'
|
||||
: ''}
|
||||
"
|
||||
>
|
||||
{#if isLoading}
|
||||
<div>
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="">
|
||||
{title}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex self-center translate-y-[1px]">
|
||||
{#if open}
|
||||
<ChevronUp strokeWidth="3.5" className="size-3.5" />
|
||||
{:else}
|
||||
|
@ -18,18 +18,26 @@ function findMatchingClosingTag(src, openTag, closeTag) {
|
||||
function detailsTokenizer(src) {
|
||||
const detailsRegex = /^<details>\n/;
|
||||
const summaryRegex = /^<summary>(.*?)<\/summary>\n/;
|
||||
const loadingRegex = /<loading\s*\/>/; // Detect <loading/>
|
||||
|
||||
if (detailsRegex.test(src)) {
|
||||
const endIndex = findMatchingClosingTag(src, '<details>', '</details>');
|
||||
if (endIndex === -1) return;
|
||||
|
||||
const fullMatch = src.slice(0, endIndex);
|
||||
let content = fullMatch.slice(10, -10).trim(); // Remove <details> and </details>
|
||||
|
||||
let summary = '';
|
||||
let isLoading = false;
|
||||
|
||||
const summaryMatch = summaryRegex.exec(content);
|
||||
if (summaryMatch) {
|
||||
summary = summaryMatch[1].trim();
|
||||
|
||||
// Detect and remove <loading/>
|
||||
if (loadingRegex.test(summary)) {
|
||||
isLoading = true;
|
||||
summary = summary.replace(loadingRegex, '').trim(); // Remove <loading/> from summary
|
||||
}
|
||||
|
||||
content = content.slice(summaryMatch[0].length).trim();
|
||||
}
|
||||
|
||||
@ -37,7 +45,8 @@ function detailsTokenizer(src) {
|
||||
type: 'details',
|
||||
raw: fullMatch,
|
||||
summary: summary,
|
||||
text: content
|
||||
text: content,
|
||||
isLoading: isLoading // Include loading property to indicate if <loading/> was present
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user