enh: renderMarkdownInPreviews

This commit is contained in:
Timothy Jaeryang Baek
2026-02-16 01:23:56 -06:00
parent 9e85055b8b
commit c26e8110af
4 changed files with 100 additions and 19 deletions
@@ -2,6 +2,7 @@
import { getContext, onMount, tick } from 'svelte'; import { getContext, onMount, tick } from 'svelte';
import Modal from '$lib/components/common/Modal.svelte'; import Modal from '$lib/components/common/Modal.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte';
import Markdown from '$lib/components/chat/Messages/Markdown.svelte';
import { WEBUI_API_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL } from '$lib/constants';
import { settings } from '$lib/stores'; import { settings } from '$lib/stores';
@@ -10,6 +11,9 @@
const i18n = getContext('i18n'); const i18n = getContext('i18n');
const CONTENT_PREVIEW_LIMIT = 10000;
let expandedDocs: Set<number> = new Set();
export let show = false; export let show = false;
export let citation; export let citation;
export let showPercentage = false; export let showPercentage = false;
@@ -35,6 +39,7 @@
} }
$: if (citation) { $: if (citation) {
expandedDocs = new Set();
mergedDocuments = citation.document?.map((c, i) => { mergedDocuments = citation.document?.map((c, i) => {
return { return {
source: citation.source, source: citation.source,
@@ -206,20 +211,37 @@
</div> </div>
{#if document.metadata?.html} {#if document.metadata?.html}
<iframe <iframe
class="w-full border-0 h-auto rounded-none" class="w-full border-0 h-auto rounded-none"
sandbox="allow-scripts allow-forms{($settings?.iframeSandboxAllowSameOrigin ?? sandbox="allow-scripts allow-forms{($settings?.iframeSandboxAllowSameOrigin ??
false) false)
? ' allow-same-origin' ? ' allow-same-origin'
: ''}" : ''}"
srcdoc={document.document} srcdoc={document.document}
title={$i18n.t('Content')} title={$i18n.t('Content')}
></iframe> ></iframe>
{:else} {:else}
<pre class="text-sm dark:text-gray-400 whitespace-pre-line">{document.document {@const rawContent = document.document.trim().replace(/\n\n+/g, '\n\n')}
.trim() {@const isTruncated = ($settings?.renderMarkdownInPreviews ?? true) && rawContent.length > CONTENT_PREVIEW_LIMIT && !expandedDocs.has(documentIdx)}
.replace(/\n\n+/g, '\n\n')}</pre> {#if $settings?.renderMarkdownInPreviews ?? true}
<div class="text-sm prose dark:prose-invert max-w-full">
<Markdown content={isTruncated ? rawContent.slice(0, CONTENT_PREVIEW_LIMIT) : rawContent} id="citation-{documentIdx}" />
</div>
{#if isTruncated}
<button
class="mt-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
on:click={() => {
expandedDocs.add(documentIdx);
expandedDocs = expandedDocs;
}}
>
{$i18n.t('Show all ({{COUNT}} characters)', { COUNT: rawContent.length.toLocaleString() })}
</button>
{/if} {/if}
{:else}
<pre class="text-sm dark:text-gray-400 whitespace-pre-line">{rawContent}</pre>
{/if}
{/if}
</div> </div>
</div> </div>
{/each} {/each}
@@ -70,6 +70,7 @@
let chatFadeStreamingText = true; let chatFadeStreamingText = true;
let collapseCodeBlocks = false; let collapseCodeBlocks = false;
let expandDetails = false; let expandDetails = false;
let renderMarkdownInPreviews = true;
let showChatTitleInTab = true; let showChatTitleInTab = true;
let showFloatingActionButtons = true; let showFloatingActionButtons = true;
@@ -232,6 +233,7 @@
collapseCodeBlocks = $settings?.collapseCodeBlocks ?? false; collapseCodeBlocks = $settings?.collapseCodeBlocks ?? false;
expandDetails = $settings?.expandDetails ?? false; expandDetails = $settings?.expandDetails ?? false;
renderMarkdownInPreviews = $settings?.renderMarkdownInPreviews ?? true;
landingPageMode = $settings?.landingPageMode ?? ''; landingPageMode = $settings?.landingPageMode ?? '';
chatBubble = $settings?.chatBubble ?? true; chatBubble = $settings?.chatBubble ?? true;
@@ -964,6 +966,25 @@
</div> </div>
</div> </div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="render-markdown-in-previews-label" class=" self-center text-xs">
{$i18n.t('Render Markdown in Previews')}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="render-markdown-in-previews-label"
tooltip={true}
bind:state={renderMarkdownInPreviews}
on:change={() => {
saveSettings({ renderMarkdownInPreviews });
}}
/>
</div>
</div>
</div>
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div id="keep-followup-prompts-label" class=" self-center text-xs"> <div id="keep-followup-prompts-label" class=" self-center text-xs">
+43 -6
View File
@@ -6,6 +6,7 @@
import { formatFileSize, getLineCount } from '$lib/utils'; import { formatFileSize, getLineCount } from '$lib/utils';
import { WEBUI_API_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL } from '$lib/constants';
import { settings } from '$lib/stores';
import { getKnowledgeById } from '$lib/apis/knowledge'; import { getKnowledgeById } from '$lib/apis/knowledge';
import { getFileById, getFileContentById } from '$lib/apis/files'; import { getFileById, getFileContentById } from '$lib/apis/files';
@@ -14,6 +15,9 @@
const i18n = getContext('i18n'); const i18n = getContext('i18n');
const CONTENT_PREVIEW_LIMIT = 10000;
let expandedContent = false;
import Modal from './Modal.svelte'; import Modal from './Modal.svelte';
import XMark from '../icons/XMark.svelte'; import XMark from '../icons/XMark.svelte';
import Switch from './Switch.svelte'; import Switch from './Switch.svelte';
@@ -147,6 +151,7 @@
const loadContent = async () => { const loadContent = async () => {
selectedTab = ''; selectedTab = '';
expandedContent = false;
if (item?.type === 'collection') { if (item?.type === 'collection') {
loading = true; loading = true;
@@ -365,13 +370,45 @@
</div> </div>
{:else if selectedTab === ''} {:else if selectedTab === ''}
{#if item?.file?.data} {#if item?.file?.data}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap"> {@const rawContent = (item?.file?.data?.content ?? '').trim() || 'No content'}
{(item?.file?.data?.content ?? '').trim() || 'No content'} {@const isTruncated = ($settings?.renderMarkdownInPreviews ?? true) && rawContent.length > CONTENT_PREVIEW_LIMIT && !expandedContent}
</div> {#if $settings?.renderMarkdownInPreviews ?? true}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-sm prose dark:prose-invert max-w-full">
<Markdown content={isTruncated ? rawContent.slice(0, CONTENT_PREVIEW_LIMIT) : rawContent} id="file-preview" />
</div>
{#if isTruncated}
<button
class="mt-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
on:click={() => { expandedContent = true; }}
>
{$i18n.t('Show all ({{COUNT}} characters)', { COUNT: rawContent.length.toLocaleString() })}
</button>
{/if}
{:else}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{rawContent}
</div>
{/if}
{:else if item?.content} {:else if item?.content}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap"> {@const rawContent = (item?.content ?? '').trim() || 'No content'}
{(item?.content ?? '').trim() || 'No content'} {@const isTruncated = ($settings?.renderMarkdownInPreviews ?? true) && rawContent.length > CONTENT_PREVIEW_LIMIT && !expandedContent}
</div> {#if $settings?.renderMarkdownInPreviews ?? true}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-sm prose dark:prose-invert max-w-full">
<Markdown content={isTruncated ? rawContent.slice(0, CONTENT_PREVIEW_LIMIT) : rawContent} id="file-preview-content" />
</div>
{#if isTruncated}
<button
class="mt-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
on:click={() => { expandedContent = true; }}
>
{$i18n.t('Show all ({{COUNT}} characters)', { COUNT: rawContent.length.toLocaleString() })}
</button>
{/if}
{:else}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{rawContent}
</div>
{/if}
{/if} {/if}
{:else if selectedTab === 'preview'} {:else if selectedTab === 'preview'}
{#if isAudio} {#if isAudio}
+1
View File
@@ -205,6 +205,7 @@ type Settings = {
splitLargeDeltas?: boolean; splitLargeDeltas?: boolean;
chatDirection?: 'LTR' | 'RTL' | 'auto'; chatDirection?: 'LTR' | 'RTL' | 'auto';
ctrlEnterToSend?: boolean; ctrlEnterToSend?: boolean;
renderMarkdownInPreviews?: boolean;
system?: string; system?: string;
seed?: number; seed?: number;