diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 06cd1d4a4..5d15f62fc 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -1166,7 +1166,7 @@ async def process_chat_response( ) # Format reasoning with
tag - content = f"{ongoing_content}
\nThought for {reasoning_duration} seconds\n{reasoning_display_content}\n
\n" + content = f'{ongoing_content}
\nThought for {reasoning_duration} seconds\n{reasoning_display_content}\n
\n' else: content = "" @@ -1183,7 +1183,7 @@ async def process_chat_response( ) # Show ongoing thought process - content = f"{ongoing_content}
\nThinking… \n{reasoning_display_content}\n
\n" + content = f'{ongoing_content}
\nThinking…\n{reasoning_display_content}\n
\n' if ENABLE_REALTIME_CHAT_SAVE: # Save message in the database diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte index 1ea7644d0..c295773a9 100644 --- a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte +++ b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte @@ -195,11 +195,7 @@ {/if} {:else if token.type === 'details'} - +
diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte index 579e10260..5ccc8ffc2 100644 --- a/src/lib/components/common/Collapsible.svelte +++ b/src/lib/components/common/Collapsible.svelte @@ -16,7 +16,7 @@ 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 attributes = null; export let grow = false; @@ -37,12 +37,13 @@ }} >
- {#if isLoading} + {#if attributes?.done !== 'true'}
diff --git a/src/lib/utils/marked/extension.ts b/src/lib/utils/marked/extension.ts index 7a6fea8f0..eec17adbd 100644 --- a/src/lib/utils/marked/extension.ts +++ b/src/lib/utils/marked/extension.ts @@ -1,5 +1,5 @@ // 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 index = openTag.length; while (depth > 0 && index < src.length) { @@ -15,29 +15,37 @@ function findMatchingClosingTag(src, openTag, closeTag) { return depth === 0 ? index + closeTag.length : -1; } -function detailsTokenizer(src) { - const detailsRegex = /^
\n/; - const summaryRegex = /^(.*?)<\/summary>\n/; - const loadingRegex = //; // Detect +// Function to parse attributes from tag +function parseAttributes(tag: string): { [key: string]: string } { + const attributes: { [key: string]: string } = {}; + const attrRegex = /(\w+)="(.*?)"/g; + let match; + while ((match = attrRegex.exec(tag)) !== null) { + attributes[match[1]] = match[2]; + } + return attributes; +} - if (detailsRegex.test(src)) { - const endIndex = findMatchingClosingTag(src, '
', '
'); +function detailsTokenizer(src: string) { + // Updated regex to capture attributes inside
+ const detailsRegex = /^]*)?>\n/; + const summaryRegex = /^(.*?)<\/summary>\n/; + + const detailsMatch = detailsRegex.exec(src); + if (detailsMatch) { + const endIndex = findMatchingClosingTag(src, ''); if (endIndex === -1) return; + const fullMatch = src.slice(0, endIndex); - let content = fullMatch.slice(10, -10).trim(); // Remove
and
+ const detailsTag = detailsMatch[0]; + const attributes = parseAttributes(detailsTag); // Parse attributes from
+ + let content = fullMatch.slice(detailsTag.length, -10).trim(); // Remove
and
let summary = ''; - let isLoading = false; const summaryMatch = summaryRegex.exec(content); if (summaryMatch) { summary = summaryMatch[1].trim(); - - // Detect and remove - if (loadingRegex.test(summary)) { - isLoading = true; - summary = summary.replace(loadingRegex, '').trim(); // Remove from summary - } - content = content.slice(summaryMatch[0].length).trim(); } @@ -46,22 +54,29 @@ function detailsTokenizer(src) { raw: fullMatch, summary: summary, text: content, - isLoading: isLoading // Include loading property to indicate if was present + attributes: attributes // Include extracted attributes from
}; } } -function detailsStart(src) { +function detailsStart(src: string) { return src.match(/^
/) ? 0 : -1; } -function detailsRenderer(token) { - return `
+function detailsRenderer(token: any) { + const attributesString = token.attributes + ? Object.entries(token.attributes) + .map(([key, value]) => `${key}="${value}"`) + .join(' ') + : ''; + + return `
${token.summary ? `${token.summary}` : ''} ${token.text}
`; } +// Extension wrapper function function detailsExtension() { return { name: 'details',