diff --git a/src/lib/components/chat/Messages/Markdown.svelte b/src/lib/components/chat/Messages/Markdown.svelte index ed1a0c505c..50c50d5725 100644 --- a/src/lib/components/chat/Messages/Markdown.svelte +++ b/src/lib/components/chat/Messages/Markdown.svelte @@ -8,6 +8,7 @@ import markedKatexExtension from '$lib/utils/marked/katex-extension'; import { disableSingleTilde } from '$lib/utils/marked/strikethrough-extension'; import { mentionExtension } from '$lib/utils/marked/mention-extension'; + import colonFenceExtension from '$lib/utils/marked/colon-fence-extension'; import MarkdownTokens from './Markdown/MarkdownTokens.svelte'; import footnoteExtension from '$lib/utils/marked/footnote-extension'; @@ -48,6 +49,7 @@ marked.use(markedExtension(options)); marked.use(citationExtension(options)); marked.use(footnoteExtension(options)); + marked.use(colonFenceExtension(options)); marked.use(disableSingleTilde); marked.use({ extensions: [ diff --git a/src/lib/components/chat/Messages/Markdown/ColonFenceBlock.svelte b/src/lib/components/chat/Messages/Markdown/ColonFenceBlock.svelte new file mode 100644 index 0000000000..1f140d2977 --- /dev/null +++ b/src/lib/components/chat/Messages/Markdown/ColonFenceBlock.svelte @@ -0,0 +1,76 @@ + + +
+ +
+ + {label} + + + +
+ + +
+ +
+
diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte index 924b8b399d..cac7cdde66 100644 --- a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte +++ b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte @@ -23,6 +23,7 @@ import HtmlToken from './HTMLToken.svelte'; import Clipboard from '$lib/components/icons/Clipboard.svelte'; + import ColonFenceBlock from './ColonFenceBlock.svelte'; export let id: string; export let tokens: Token[]; @@ -434,6 +435,17 @@ {#if token.text} {/if} + {:else if token.type === 'colonFence'} + {:else if token.type === 'space'}
{:else} diff --git a/src/lib/utils/marked/colon-fence-extension.ts b/src/lib/utils/marked/colon-fence-extension.ts new file mode 100644 index 0000000000..c9dca5ee4b --- /dev/null +++ b/src/lib/utils/marked/colon-fence-extension.ts @@ -0,0 +1,56 @@ +/** + * Marked extension for colon-fence blocks (:::type ... :::) + * + * Used by newer OpenAI chat models to wrap semantically distinct content: + * :::writing – reusable prose (letters, articles, docs) + * :::code_execution – code execution output + * :::search_results – web search results + * + * The extension is generic and will tokenize any ::: block. + */ + +function colonFenceTokenizer(this: any, src: string) { + // Match :::type at the start of a line, optionally followed by content, then closing ::: + const match = /^:::([\w-]+)\n([\s\S]*?)(?:\n:::(?:\s*$|\n))/m.exec(src); + if (match) { + const fenceType = match[1]; + const text = match[2].trim(); + const raw = match[0]; + + const tokens: any[] = []; + this.lexer.blockTokens(text, tokens); + + return { + type: 'colonFence', + raw, + fenceType, + text, + tokens + }; + } +} + +function colonFenceStart(src: string) { + const idx = src.match(/^:::\w/m); + return idx ? idx.index! : -1; +} + +function colonFenceRenderer(token: any) { + return `
${token.text}
`; +} + +function colonFenceExtension() { + return { + name: 'colonFence', + level: 'block' as const, + start: colonFenceStart, + tokenizer: colonFenceTokenizer, + renderer: colonFenceRenderer + }; +} + +export default function (options = {}) { + return { + extensions: [colonFenceExtension()] + }; +}