mirror of
https://github.com/open-webui/open-webui
synced 2025-05-23 06:14:25 +00:00
refac: think tag
This commit is contained in:
parent
cf470e70e2
commit
9feed97f22
@ -1166,7 +1166,7 @@ async def process_chat_response(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Format reasoning with <details> tag
|
# Format reasoning with <details> tag
|
||||||
content = f"{ongoing_content}<details>\n<summary>Thought for {reasoning_duration} seconds</summary>\n{reasoning_display_content}\n</details>\n"
|
content = f'{ongoing_content}<details type="reasoning" done="true">\n<summary>Thought for {reasoning_duration} seconds</summary>\n{reasoning_display_content}\n</details>\n'
|
||||||
else:
|
else:
|
||||||
content = ""
|
content = ""
|
||||||
|
|
||||||
@ -1183,7 +1183,7 @@ async def process_chat_response(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Show ongoing thought process
|
# Show ongoing thought process
|
||||||
content = f"{ongoing_content}<details>\n<summary>Thinking… <loading/></summary>\n{reasoning_display_content}\n</details>\n"
|
content = f'{ongoing_content}<details type="reasoning" done="false">\n<summary>Thinking…</summary>\n{reasoning_display_content}\n</details>\n'
|
||||||
|
|
||||||
if ENABLE_REALTIME_CHAT_SAVE:
|
if ENABLE_REALTIME_CHAT_SAVE:
|
||||||
# Save message in the database
|
# Save message in the database
|
||||||
|
@ -195,11 +195,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if token.type === 'details'}
|
{:else if token.type === 'details'}
|
||||||
<Collapsible
|
<Collapsible title={token.summary} attributes={token?.attributes} className="w-fit space-y-1">
|
||||||
title={token.summary}
|
|
||||||
isLoading={token?.isLoading ?? false}
|
|
||||||
className="w-fit space-y-1"
|
|
||||||
>
|
|
||||||
<div class=" mb-1.5" slot="content">
|
<div class=" mb-1.5" slot="content">
|
||||||
<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
|
<svelte:self id={`${id}-${tokenIdx}-d`} tokens={marked.lexer(token.text)} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
export let buttonClassName =
|
export let buttonClassName =
|
||||||
'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition';
|
'w-fit text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition';
|
||||||
export let title = null;
|
export let title = null;
|
||||||
export let isLoading = false;
|
export let attributes = null;
|
||||||
|
|
||||||
export let grow = false;
|
export let grow = false;
|
||||||
|
|
||||||
@ -37,12 +37,13 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" w-full font-medium flex items-center justify-between gap-2 {isLoading === true
|
class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done !==
|
||||||
|
'true'
|
||||||
? 'shimmer'
|
? 'shimmer'
|
||||||
: ''}
|
: ''}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{#if isLoading}
|
{#if attributes?.done !== 'true'}
|
||||||
<div>
|
<div>
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Helper function to find matching closing tag
|
// Helper function to find matching closing tag
|
||||||
function findMatchingClosingTag(src, openTag, closeTag) {
|
function findMatchingClosingTag(src: string, openTag: string, closeTag: string): number {
|
||||||
let depth = 1;
|
let depth = 1;
|
||||||
let index = openTag.length;
|
let index = openTag.length;
|
||||||
while (depth > 0 && index < src.length) {
|
while (depth > 0 && index < src.length) {
|
||||||
@ -15,29 +15,37 @@ function findMatchingClosingTag(src, openTag, closeTag) {
|
|||||||
return depth === 0 ? index + closeTag.length : -1;
|
return depth === 0 ? index + closeTag.length : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function detailsTokenizer(src) {
|
// Function to parse attributes from tag
|
||||||
const detailsRegex = /^<details>\n/;
|
function parseAttributes(tag: string): { [key: string]: string } {
|
||||||
const summaryRegex = /^<summary>(.*?)<\/summary>\n/;
|
const attributes: { [key: string]: string } = {};
|
||||||
const loadingRegex = /<loading\s*\/>/; // Detect <loading/>
|
const attrRegex = /(\w+)="(.*?)"/g;
|
||||||
|
let match;
|
||||||
|
while ((match = attrRegex.exec(tag)) !== null) {
|
||||||
|
attributes[match[1]] = match[2];
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
if (detailsRegex.test(src)) {
|
function detailsTokenizer(src: string) {
|
||||||
const endIndex = findMatchingClosingTag(src, '<details>', '</details>');
|
// Updated regex to capture attributes inside <details>
|
||||||
|
const detailsRegex = /^<details(\s+[^>]*)?>\n/;
|
||||||
|
const summaryRegex = /^<summary>(.*?)<\/summary>\n/;
|
||||||
|
|
||||||
|
const detailsMatch = detailsRegex.exec(src);
|
||||||
|
if (detailsMatch) {
|
||||||
|
const endIndex = findMatchingClosingTag(src, '<details', '</details>');
|
||||||
if (endIndex === -1) return;
|
if (endIndex === -1) return;
|
||||||
|
|
||||||
const fullMatch = src.slice(0, endIndex);
|
const fullMatch = src.slice(0, endIndex);
|
||||||
let content = fullMatch.slice(10, -10).trim(); // Remove <details> and </details>
|
const detailsTag = detailsMatch[0];
|
||||||
|
const attributes = parseAttributes(detailsTag); // Parse attributes from <details>
|
||||||
|
|
||||||
|
let content = fullMatch.slice(detailsTag.length, -10).trim(); // Remove <details> and </details>
|
||||||
let summary = '';
|
let summary = '';
|
||||||
let isLoading = false;
|
|
||||||
|
|
||||||
const summaryMatch = summaryRegex.exec(content);
|
const summaryMatch = summaryRegex.exec(content);
|
||||||
if (summaryMatch) {
|
if (summaryMatch) {
|
||||||
summary = summaryMatch[1].trim();
|
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();
|
content = content.slice(summaryMatch[0].length).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,22 +54,29 @@ function detailsTokenizer(src) {
|
|||||||
raw: fullMatch,
|
raw: fullMatch,
|
||||||
summary: summary,
|
summary: summary,
|
||||||
text: content,
|
text: content,
|
||||||
isLoading: isLoading // Include loading property to indicate if <loading/> was present
|
attributes: attributes // Include extracted attributes from <details>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function detailsStart(src) {
|
function detailsStart(src: string) {
|
||||||
return src.match(/^<details>/) ? 0 : -1;
|
return src.match(/^<details>/) ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function detailsRenderer(token) {
|
function detailsRenderer(token: any) {
|
||||||
return `<details>
|
const attributesString = token.attributes
|
||||||
|
? Object.entries(token.attributes)
|
||||||
|
.map(([key, value]) => `${key}="${value}"`)
|
||||||
|
.join(' ')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return `<details ${attributesString}>
|
||||||
${token.summary ? `<summary>${token.summary}</summary>` : ''}
|
${token.summary ? `<summary>${token.summary}</summary>` : ''}
|
||||||
${token.text}
|
${token.text}
|
||||||
</details>`;
|
</details>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extension wrapper function
|
||||||
function detailsExtension() {
|
function detailsExtension() {
|
||||||
return {
|
return {
|
||||||
name: 'details',
|
name: 'details',
|
||||||
|
Loading…
Reference in New Issue
Block a user