mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-16 14:39:31 -05:00
refac
This commit is contained in:
@@ -15,6 +15,57 @@
|
||||
import FloatingButtons from '../ContentRenderer/FloatingButtons.svelte';
|
||||
import { createMessagesList } from '$lib/utils';
|
||||
|
||||
/**
|
||||
* Extracts all top-level <details>...</details> blocks from content,
|
||||
* handling nested <details> via depth tracking.
|
||||
* Returns { detailsContent, plainContent }.
|
||||
*/
|
||||
const extractDetailsBlocks = (text) => {
|
||||
const blocks = [];
|
||||
let remaining = text;
|
||||
let result = '';
|
||||
const openTag = '<details';
|
||||
const closeTag = '</details>';
|
||||
|
||||
while (true) {
|
||||
const start = remaining.indexOf(openTag);
|
||||
if (start === -1) {
|
||||
result += remaining;
|
||||
break;
|
||||
}
|
||||
|
||||
result += remaining.slice(0, start);
|
||||
|
||||
// Find matching closing tag with depth tracking
|
||||
let depth = 1;
|
||||
let idx = start + openTag.length;
|
||||
while (depth > 0 && idx < remaining.length) {
|
||||
if (remaining.startsWith(openTag, idx)) {
|
||||
depth++;
|
||||
} else if (remaining.startsWith(closeTag, idx)) {
|
||||
depth--;
|
||||
}
|
||||
if (depth > 0) idx++;
|
||||
}
|
||||
|
||||
if (depth === 0) {
|
||||
const end = idx + closeTag.length;
|
||||
blocks.push(remaining.slice(start, end));
|
||||
remaining = remaining.slice(end);
|
||||
} else {
|
||||
// Unmatched opening tag, treat as plain text
|
||||
result += remaining.slice(start);
|
||||
remaining = '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
detailsContent: blocks.join('\n'),
|
||||
plainContent: result.trim()
|
||||
};
|
||||
};
|
||||
|
||||
export let id;
|
||||
export let content;
|
||||
|
||||
@@ -174,43 +225,59 @@
|
||||
</script>
|
||||
|
||||
<div bind:this={contentContainerElement}>
|
||||
<Markdown
|
||||
{id}
|
||||
content={model?.info?.meta?.capabilities?.citations == false
|
||||
? content.replace(/\s*(\[(?:\d+(?:#[^,\]\s]+)?(?:,\s*\d+(?:#[^,\]\s]+)?)*)\])+/g, '')
|
||||
: content}
|
||||
{model}
|
||||
{save}
|
||||
{preview}
|
||||
{done}
|
||||
{editCodeBlock}
|
||||
{topPadding}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
{onTaskClick}
|
||||
{onSave}
|
||||
onUpdate={async (token) => {
|
||||
const { lang, text: code } = token;
|
||||
{#if $settings?.renderMarkdownInAssistantMessages ?? true}
|
||||
<Markdown
|
||||
{id}
|
||||
content={model?.info?.meta?.capabilities?.citations == false
|
||||
? content.replace(/\s*(\[(?:\d+(?:#[^,\]\s]+)?(?:,\s*\d+(?:#[^,\]\s]+)?)*)\])+/g, '')
|
||||
: content}
|
||||
{model}
|
||||
{save}
|
||||
{preview}
|
||||
{done}
|
||||
{editCodeBlock}
|
||||
{topPadding}
|
||||
{sourceIds}
|
||||
{onSourceClick}
|
||||
{onTaskClick}
|
||||
{onSave}
|
||||
onUpdate={async (token) => {
|
||||
const { lang, text: code } = token;
|
||||
|
||||
if (
|
||||
($settings?.detectArtifacts ?? true) &&
|
||||
(['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) &&
|
||||
!$mobile &&
|
||||
$chatId
|
||||
) {
|
||||
await tick();
|
||||
showArtifacts.set(true);
|
||||
showControls.set(true);
|
||||
}
|
||||
}}
|
||||
onPreview={async (value) => {
|
||||
console.log('Preview', value);
|
||||
await artifactCode.set(value);
|
||||
await showControls.set(true);
|
||||
await showArtifacts.set(true);
|
||||
await showEmbeds.set(false);
|
||||
}}
|
||||
/>
|
||||
if (
|
||||
($settings?.detectArtifacts ?? true) &&
|
||||
(['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) &&
|
||||
!$mobile &&
|
||||
$chatId
|
||||
) {
|
||||
await tick();
|
||||
showArtifacts.set(true);
|
||||
showControls.set(true);
|
||||
}
|
||||
}}
|
||||
onPreview={async (value) => {
|
||||
console.log('Preview', value);
|
||||
await artifactCode.set(value);
|
||||
await showControls.set(true);
|
||||
await showArtifacts.set(true);
|
||||
await showEmbeds.set(false);
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
{@const extracted = extractDetailsBlocks(content)}
|
||||
|
||||
{#if extracted.detailsContent}
|
||||
<!-- Render structural blocks (tool calls, reasoning, etc.) through Markdown -->
|
||||
<Markdown
|
||||
{id}
|
||||
content={extracted.detailsContent}
|
||||
{done}
|
||||
/>
|
||||
{/if}
|
||||
{#if extracted.plainContent}
|
||||
<div class="whitespace-pre-wrap">{extracted.plainContent}</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if floatingButtons}
|
||||
|
||||
@@ -376,12 +376,16 @@
|
||||
: ' w-full'}"
|
||||
>
|
||||
{#if message.content}
|
||||
<Markdown
|
||||
id={`${chatId}-${message.id}`}
|
||||
content={message.content}
|
||||
{editCodeBlock}
|
||||
{topPadding}
|
||||
/>
|
||||
{#if $settings?.renderMarkdownInUserMessages ?? true}
|
||||
<Markdown
|
||||
id={`${chatId}-${message.id}`}
|
||||
content={message.content}
|
||||
{editCodeBlock}
|
||||
{topPadding}
|
||||
/>
|
||||
{:else}
|
||||
<div class="whitespace-pre-wrap" dir={$settings?.chatDirection ?? 'auto'}>{message.content}</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,6 +69,8 @@
|
||||
let temporaryChatByDefault = false;
|
||||
let chatFadeStreamingText = true;
|
||||
let collapseCodeBlocks = false;
|
||||
let renderMarkdownInUserMessages = true;
|
||||
let renderMarkdownInAssistantMessages = true;
|
||||
let expandDetails = false;
|
||||
let renderMarkdownInPreviews = true;
|
||||
let showChatTitleInTab = true;
|
||||
@@ -232,6 +234,8 @@
|
||||
copyFormatted = $settings?.copyFormatted ?? false;
|
||||
|
||||
collapseCodeBlocks = $settings?.collapseCodeBlocks ?? false;
|
||||
renderMarkdownInUserMessages = $settings?.renderMarkdownInUserMessages ?? true;
|
||||
renderMarkdownInAssistantMessages = $settings?.renderMarkdownInAssistantMessages ?? true;
|
||||
expandDetails = $settings?.expandDetails ?? false;
|
||||
renderMarkdownInPreviews = $settings?.renderMarkdownInPreviews ?? true;
|
||||
|
||||
@@ -776,6 +780,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div id="render-markdown-user-label" class=" self-center text-xs">
|
||||
{$i18n.t('Render Markdown in User Messages')}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 p-1">
|
||||
<Switch
|
||||
ariaLabelledbyId="render-markdown-user-label"
|
||||
tooltip={true}
|
||||
bind:state={renderMarkdownInUserMessages}
|
||||
on:change={() => {
|
||||
saveSettings({ renderMarkdownInUserMessages });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div id="render-markdown-assistant-label" class=" self-center text-xs">
|
||||
{$i18n.t('Render Markdown in Assistant Messages')}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 p-1">
|
||||
<Switch
|
||||
ariaLabelledbyId="render-markdown-assistant-label"
|
||||
tooltip={true}
|
||||
bind:state={renderMarkdownInAssistantMessages}
|
||||
on:change={() => {
|
||||
saveSettings({ renderMarkdownInAssistantMessages });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div id="auto-generation-label" class=" self-center text-xs">
|
||||
|
||||
@@ -1697,6 +1697,8 @@
|
||||
"Remove Model": "",
|
||||
"Rename": "",
|
||||
"Renamed to {{name}}": "",
|
||||
"Render Markdown in Assistant Messages": "",
|
||||
"Render Markdown in User Messages": "",
|
||||
"Render Markdown in Previews": "",
|
||||
"Reorder Models": "",
|
||||
"Repeats": "",
|
||||
|
||||
Reference in New Issue
Block a user