mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +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
 | 
			
		||||
                                                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:
 | 
			
		||||
                                                content = ""
 | 
			
		||||
 | 
			
		||||
@ -1183,7 +1183,7 @@ async def process_chat_response(
 | 
			
		||||
                                            )
 | 
			
		||||
 | 
			
		||||
                                            # 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:
 | 
			
		||||
                                    # Save message in the database
 | 
			
		||||
 | 
			
		||||
@ -195,11 +195,7 @@
 | 
			
		||||
			</ul>
 | 
			
		||||
		{/if}
 | 
			
		||||
	{:else if token.type === 'details'}
 | 
			
		||||
		<Collapsible
 | 
			
		||||
			title={token.summary}
 | 
			
		||||
			isLoading={token?.isLoading ?? false}
 | 
			
		||||
			className="w-fit space-y-1"
 | 
			
		||||
		>
 | 
			
		||||
		<Collapsible title={token.summary} attributes={token?.attributes} 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>
 | 
			
		||||
 | 
			
		||||
@ -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 @@
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			<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'
 | 
			
		||||
					: ''}
 | 
			
		||||
			"
 | 
			
		||||
			>
 | 
			
		||||
				{#if isLoading}
 | 
			
		||||
				{#if attributes?.done !== 'true'}
 | 
			
		||||
					<div>
 | 
			
		||||
						<Spinner className="size-4" />
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
@ -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 = /^<details>\n/;
 | 
			
		||||
	const summaryRegex = /^<summary>(.*?)<\/summary>\n/;
 | 
			
		||||
	const loadingRegex = /<loading\s*\/>/; // Detect <loading/>
 | 
			
		||||
// 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, '<details>', '</details>');
 | 
			
		||||
function detailsTokenizer(src: string) {
 | 
			
		||||
	// 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;
 | 
			
		||||
 | 
			
		||||
		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 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();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -46,22 +54,29 @@ function detailsTokenizer(src) {
 | 
			
		||||
			raw: fullMatch,
 | 
			
		||||
			summary: summary,
 | 
			
		||||
			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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function detailsRenderer(token) {
 | 
			
		||||
	return `<details>
 | 
			
		||||
function detailsRenderer(token: any) {
 | 
			
		||||
	const attributesString = token.attributes
 | 
			
		||||
		? Object.entries(token.attributes)
 | 
			
		||||
				.map(([key, value]) => `${key}="${value}"`)
 | 
			
		||||
				.join(' ')
 | 
			
		||||
		: '';
 | 
			
		||||
 | 
			
		||||
	return `<details ${attributesString}>
 | 
			
		||||
  ${token.summary ? `<summary>${token.summary}</summary>` : ''}
 | 
			
		||||
  ${token.text}
 | 
			
		||||
  </details>`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Extension wrapper function
 | 
			
		||||
function detailsExtension() {
 | 
			
		||||
	return {
 | 
			
		||||
		name: 'details',
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user