This commit is contained in:
Timothy Jaeryang Baek
2026-05-11 02:04:09 +09:00
parent 315566064a
commit 4a1064cefd
4 changed files with 157 additions and 42 deletions

View File

@@ -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}

View File

@@ -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>

View File

@@ -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">

View File

@@ -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": "",