Browse Source

Merge branch 'dev' of https://github.com/open-webui/open-webui into remove-ollama

Michael Poluektov 9 months ago
parent
commit
547611b703
51 changed files with 450 additions and 229 deletions
  1. 2 2
      backend/apps/images/main.py
  2. 18 0
      package-lock.json
  3. 1 0
      package.json
  4. 9 6
      src/lib/components/chat/Messages/CodeBlock.svelte
  5. 9 0
      src/lib/components/chat/Messages/KatexRenderer.svelte
  6. 13 3
      src/lib/components/chat/Messages/MarkdownInlineTokens.svelte
  7. 106 119
      src/lib/components/chat/Messages/MarkdownTokens.svelte
  8. 80 91
      src/lib/components/chat/Messages/ResponseMessage.svelte
  9. 3 0
      src/lib/i18n/locales/ar-BH/translation.json
  10. 3 0
      src/lib/i18n/locales/bg-BG/translation.json
  11. 3 0
      src/lib/i18n/locales/bn-BD/translation.json
  12. 5 2
      src/lib/i18n/locales/ca-ES/translation.json
  13. 3 0
      src/lib/i18n/locales/ceb-PH/translation.json
  14. 3 0
      src/lib/i18n/locales/de-DE/translation.json
  15. 3 0
      src/lib/i18n/locales/dg-DG/translation.json
  16. 3 0
      src/lib/i18n/locales/en-GB/translation.json
  17. 3 0
      src/lib/i18n/locales/en-US/translation.json
  18. 3 0
      src/lib/i18n/locales/es-ES/translation.json
  19. 3 0
      src/lib/i18n/locales/fa-IR/translation.json
  20. 3 0
      src/lib/i18n/locales/fi-FI/translation.json
  21. 3 0
      src/lib/i18n/locales/fr-CA/translation.json
  22. 3 0
      src/lib/i18n/locales/fr-FR/translation.json
  23. 3 0
      src/lib/i18n/locales/he-IL/translation.json
  24. 3 0
      src/lib/i18n/locales/hi-IN/translation.json
  25. 3 0
      src/lib/i18n/locales/hr-HR/translation.json
  26. 3 0
      src/lib/i18n/locales/id-ID/translation.json
  27. 3 0
      src/lib/i18n/locales/it-IT/translation.json
  28. 3 0
      src/lib/i18n/locales/ja-JP/translation.json
  29. 3 0
      src/lib/i18n/locales/ka-GE/translation.json
  30. 3 0
      src/lib/i18n/locales/ko-KR/translation.json
  31. 3 0
      src/lib/i18n/locales/lt-LT/translation.json
  32. 3 0
      src/lib/i18n/locales/ms-MY/translation.json
  33. 3 0
      src/lib/i18n/locales/nb-NO/translation.json
  34. 3 0
      src/lib/i18n/locales/nl-NL/translation.json
  35. 3 0
      src/lib/i18n/locales/pa-IN/translation.json
  36. 3 0
      src/lib/i18n/locales/pl-PL/translation.json
  37. 3 0
      src/lib/i18n/locales/pt-BR/translation.json
  38. 3 0
      src/lib/i18n/locales/pt-PT/translation.json
  39. 3 0
      src/lib/i18n/locales/ro-RO/translation.json
  40. 3 0
      src/lib/i18n/locales/ru-RU/translation.json
  41. 3 0
      src/lib/i18n/locales/sr-RS/translation.json
  42. 3 0
      src/lib/i18n/locales/sv-SE/translation.json
  43. 3 0
      src/lib/i18n/locales/th-TH/translation.json
  44. 3 0
      src/lib/i18n/locales/tk-TW/translation.json
  45. 3 0
      src/lib/i18n/locales/tr-TR/translation.json
  46. 5 2
      src/lib/i18n/locales/uk-UA/translation.json
  47. 3 0
      src/lib/i18n/locales/vi-VN/translation.json
  48. 4 1
      src/lib/i18n/locales/zh-CN/translation.json
  49. 3 0
      src/lib/i18n/locales/zh-TW/translation.json
  50. 4 3
      src/lib/utils/index.ts
  51. 80 0
      src/lib/utils/katex-extension.ts

+ 2 - 2
backend/apps/images/main.py

@@ -150,10 +150,10 @@ async def update_engine_url(
     else:
         url = form_data.AUTOMATIC1111_BASE_URL.strip("/")
         try:
-            r = requests.head(url)
+            r = requests.head(url) 
             app.state.config.AUTOMATIC1111_BASE_URL = url
         except Exception as e:
-            raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
+            raise HTTPException(status_code=400, detail="Invalid URL provided.")
 
     if form_data.COMFYUI_BASE_URL == None:
         app.state.config.COMFYUI_BASE_URL = COMFYUI_BASE_URL

+ 18 - 0
package-lock.json

@@ -29,6 +29,7 @@
 				"js-sha256": "^0.10.1",
 				"katex": "^0.16.9",
 				"marked": "^9.1.0",
+				"marked-katex-extension": "^5.1.1",
 				"mermaid": "^10.9.1",
 				"pyodide": "^0.26.1",
 				"socket.io-client": "^4.2.0",
@@ -1544,6 +1545,11 @@
 			"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
 			"dev": true
 		},
+		"node_modules/@types/katex": {
+			"version": "0.16.7",
+			"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
+			"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="
+		},
 		"node_modules/@types/mdast": {
 			"version": "3.0.15",
 			"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
@@ -6036,6 +6042,18 @@
 				"node": ">= 16"
 			}
 		},
+		"node_modules/marked-katex-extension": {
+			"version": "5.1.1",
+			"resolved": "https://registry.npmjs.org/marked-katex-extension/-/marked-katex-extension-5.1.1.tgz",
+			"integrity": "sha512-piquiCyZpZ1aiocoJlJkRXr+hkk5UI4xw9GhRZiIAAgvX5rhzUDSJ0seup1JcsgueC8MLNDuqe5cRcAzkFE42Q==",
+			"dependencies": {
+				"@types/katex": "^0.16.7"
+			},
+			"peerDependencies": {
+				"katex": ">=0.16 <0.17",
+				"marked": ">=4 <15"
+			}
+		},
 		"node_modules/matcher-collection": {
 			"version": "2.0.1",
 			"resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz",

+ 1 - 0
package.json

@@ -70,6 +70,7 @@
 		"js-sha256": "^0.10.1",
 		"katex": "^0.16.9",
 		"marked": "^9.1.0",
+		"marked-katex-extension": "^5.1.1",
 		"mermaid": "^10.9.1",
 		"pyodide": "^0.26.1",
 		"socket.io-client": "^4.2.0",

+ 9 - 6
src/lib/components/chat/Messages/CodeBlock.svelte

@@ -1,12 +1,15 @@
 <script lang="ts">
-	import Spinner from '$lib/components/common/Spinner.svelte';
-	import { copyToClipboard } from '$lib/utils';
 	import hljs from 'highlight.js';
-	import 'highlight.js/styles/github-dark.min.css';
 	import { loadPyodide } from 'pyodide';
-	import { onMount, tick } from 'svelte';
+	import { getContext, getAllContexts } from 'svelte';
+	import { copyToClipboard } from '$lib/utils';
+
+	import 'highlight.js/styles/github-dark.min.css';
+
 	import PyodideWorker from '$lib/workers/pyodide.worker?worker';
 
+	const i18n = getContext('i18n');
+
 	export let id = '';
 
 	export let lang = '';
@@ -233,12 +236,12 @@ __builtins__.input = input`);
 						class="copy-code-button bg-none border-none p-1"
 						on:click={() => {
 							executePython(code);
-						}}>Run</button
+						}}>{$i18n.t('Run')}</button
 					>
 				{/if}
 			{/if}
 			<button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
-				>{copied ? 'Copied' : 'Copy Code'}</button
+				>{copied ? $i18n.t('Copied') : $i18n.t('Copy Code')}</button
 			>
 		</div>
 	</div>

+ 9 - 0
src/lib/components/chat/Messages/KatexRenderer.svelte

@@ -0,0 +1,9 @@
+<script lang="ts">
+	import katex from 'katex';
+	import 'katex/contrib/mhchem';
+
+	export let content: string;
+	export let displayMode: boolean = false;
+</script>
+
+{@html katex.renderToString(content, { displayMode, throwOnError: false })}

+ 13 - 3
src/lib/components/chat/Messages/MarkdownInlineTokens.svelte

@@ -1,8 +1,11 @@
 <script lang="ts">
 	import type { Token } from 'marked';
-	import { unescapeHtml } from '$lib/utils';
+	import { revertSanitizedResponseContent, unescapeHtml } from '$lib/utils';
+	import { onMount } from 'svelte';
 	import Image from '$lib/components/common/Image.svelte';
 
+	import KatexRenderer from './KatexRenderer.svelte';
+
 	export let id: string;
 	export let tokens: Token[];
 </script>
@@ -25,14 +28,21 @@
 			<svelte:self id={`${id}-em`} tokens={token.tokens} />
 		</em>
 	{:else if token.type === 'codespan'}
-		<code class="codespan">{unescapeHtml(token.text.replaceAll('&amp;', '&'))}</code>
+		<code class="codespan">{revertSanitizedResponseContent(token.raw)}</code>
 	{:else if token.type === 'br'}
 		<br />
 	{:else if token.type === 'del'}
 		<del>
 			<svelte:self id={`${id}-del`} tokens={token.tokens} />
 		</del>
+	{:else if token.type === 'inlineKatex'}
+		{#if token.text}
+			<KatexRenderer
+				content={revertSanitizedResponseContent(token.text)}
+				displayMode={token?.displayMode ?? false}
+			/>
+		{/if}
 	{:else if token.type === 'text'}
-		{unescapeHtml(token.text)}
+		{token.raw}
 	{/if}
 {/each}

+ 106 - 119
src/lib/components/chat/Messages/MarkdownTokens.svelte

@@ -1,137 +1,124 @@
 <script lang="ts">
-	import { marked } from 'marked';
+	import { onMount } from 'svelte';
 	import type { Token } from 'marked';
 	import { revertSanitizedResponseContent, unescapeHtml } from '$lib/utils';
 
-	import { onMount } from 'svelte';
-
-	import Image from '$lib/components/common/Image.svelte';
 	import CodeBlock from '$lib/components/chat/Messages/CodeBlock.svelte';
-
 	import MarkdownInlineTokens from '$lib/components/chat/Messages/MarkdownInlineTokens.svelte';
+	import KatexRenderer from './KatexRenderer.svelte';
 
 	export let id: string;
 	export let tokens: Token[];
 	export let top = true;
 
-	let containerElement;
-
 	const headerComponent = (depth: number) => {
 		return 'h' + depth;
 	};
-
-	const renderer = new marked.Renderer();
-	// For code blocks with simple backticks
-	renderer.codespan = (code) => {
-		return `<code class="codespan">${code.replaceAll('&amp;', '&')}</code>`;
-	};
-
-	let codes = [];
-	renderer.code = (code, lang) => {
-		codes.push({
-			code: code,
-			lang: lang
-		});
-		codes = codes;
-		const codeId = `${id}-${codes.length}`;
-
-		const interval = setInterval(() => {
-			const codeElement = document.getElementById(`code-${codeId}`);
-			if (codeElement) {
-				clearInterval(interval);
-				// If the code is already loaded, don't load it again
-				if (codeElement.innerHTML) {
-					return;
-				}
-
-				new CodeBlock({
-					target: codeElement,
-					props: {
-						id: `${id}-${codes.length}`,
-						lang: lang,
-						code: revertSanitizedResponseContent(code)
-					},
-					hydrate: true,
-					$$inline: true
-				});
-			}
-		}, 10);
-
-		return `<div id="code-${id}-${codes.length}"></div>`;
-	};
-
-	let images = [];
-	renderer.image = (href, title, text) => {
-		images.push({
-			href: href,
-			title: title,
-			text: text
-		});
-		images = images;
-
-		const imageId = `${id}-${images.length}`;
-		const interval = setInterval(() => {
-			const imageElement = document.getElementById(`image-${imageId}`);
-			if (imageElement) {
-				clearInterval(interval);
-
-				// If the image is already loaded, don't load it again
-				if (imageElement.innerHTML) {
-					return;
-				}
-
-				console.log('image', href, text);
-				new Image({
-					target: imageElement,
-					props: {
-						src: href,
-						alt: text
-					},
-					$$inline: true
-				});
-			}
-		}, 10);
-
-		return `<div id="image-${id}-${images.length}"></div>`;
-	};
-
-	// Open all links in a new tab/window (from https://github.com/markedjs/marked/issues/655#issuecomment-383226346)
-	const origLinkRenderer = renderer.link;
-	renderer.link = (href, title, text) => {
-		const html = origLinkRenderer.call(renderer, href, title, text);
-		return html.replace(/^<a /, '<a target="_blank" rel="nofollow" ');
-	};
-
-	const { extensions, ...defaults } = marked.getDefaults() as marked.MarkedOptions & {
-		// eslint-disable-next-line @typescript-eslint/no-explicit-any
-		extensions: any;
-	};
-
-	$: if (tokens) {
-		images = [];
-		codes = [];
-	}
 </script>
 
-<div bind:this={containerElement} class="flex flex-col">
-	{#each tokens as token, tokenIdx (`${id}-${tokenIdx}`)}
-		{#if token.type === 'code'}
-			{#if token.lang === 'mermaid'}
-				<pre class="mermaid">{revertSanitizedResponseContent(token.text)}</pre>
-			{:else}
-				<CodeBlock
-					id={`${id}-${tokenIdx}`}
-					lang={token?.lang ?? ''}
-					code={revertSanitizedResponseContent(token?.text ?? '')}
-				/>
-			{/if}
+<!-- {JSON.stringify(tokens)} -->
+{#each tokens as token, tokenIdx}
+	{#if token.type === 'hr'}
+		<hr />
+	{:else if token.type === 'heading'}
+		<svelte:element this={headerComponent(token.depth)}>
+			<MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} />
+		</svelte:element>
+	{:else if token.type === 'code'}
+		<CodeBlock
+			{id}
+			lang={token?.lang ?? ''}
+			code={revertSanitizedResponseContent(token?.text ?? '')}
+		/>
+	{:else if token.type === 'table'}
+		<table>
+			<thead>
+				<tr>
+					{#each token.header as header, headerIdx}
+						<th style={token.align[headerIdx] ? '' : `text-align: ${token.align[headerIdx]}`}>
+							<MarkdownInlineTokens
+								id={`${id}-${tokenIdx}-header-${headerIdx}`}
+								tokens={header.tokens}
+							/>
+						</th>
+					{/each}
+				</tr>
+			</thead>
+			<tbody>
+				{#each token.rows as row, rowIdx}
+					<tr>
+						{#each row ?? [] as cell, cellIdx}
+							<td style={token.align[cellIdx] ? '' : `text-align: ${token.align[cellIdx]}`}>
+								<MarkdownInlineTokens
+									id={`${id}-${tokenIdx}-row-${rowIdx}-${cellIdx}`}
+									tokens={cell.tokens}
+								/>
+							</td>
+						{/each}
+					</tr>
+				{/each}
+			</tbody>
+		</table>
+	{:else if token.type === 'blockquote'}
+		<blockquote>
+			<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} />
+		</blockquote>
+	{:else if token.type === 'list'}
+		{#if token.ordered}
+			<ol start={token.start || 1}>
+				{#each token.items as item, itemIdx}
+					<li>
+						<svelte:self
+							id={`${id}-${tokenIdx}-${itemIdx}`}
+							tokens={item.tokens}
+							top={token.loose}
+						/>
+					</li>
+				{/each}
+			</ol>
 		{:else}
-			{@html marked.parse(token.raw, {
-				...defaults,
-				gfm: true,
-				breaks: true,
-				renderer
-			})}
+			<ul>
+				{#each token.items as item, itemIdx}
+					<li>
+						<svelte:self
+							id={`${id}-${tokenIdx}-${itemIdx}`}
+							tokens={item.tokens}
+							top={token.loose}
+						/>
+					</li>
+				{/each}
+			</ul>
+		{/if}
+	{:else if token.type === 'html'}
+		{@html token.text}
+	{:else if token.type === 'paragraph'}
+		<p>
+			<MarkdownInlineTokens id={`${id}-${tokenIdx}-p`} tokens={token.tokens ?? []} />
+		</p>
+	{:else if token.type === 'text'}
+		{#if top}
+			<p>
+				{#if token.tokens}
+					<MarkdownInlineTokens id={`${id}-${tokenIdx}-t`} tokens={token.tokens} />
+				{:else}
+					{unescapeHtml(token.text)}
+				{/if}
+			</p>
+		{:else if token.tokens}
+			<MarkdownInlineTokens id={`${id}-${tokenIdx}-p`} tokens={token.tokens ?? []} />
+		{:else}
+			{unescapeHtml(token.text)}
+		{/if}
+	{:else if token.type === 'inlineKatex'}
+		{#if token.text}
+			<KatexRenderer
+				content={revertSanitizedResponseContent(token.text)}
+				displayMode={token?.displayMode ?? false}
+			/>
 		{/if}
-	{/each}
-</div>
+	{:else if token.type === 'space'}
+		{''}
+	{:else}
+		{console.log('Unknown token', token)}
+	{/if}
+{/each}

+ 80 - 91
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -4,7 +4,6 @@
 	import { marked } from 'marked';
 	import tippy from 'tippy.js';
 	import auto_render from 'katex/dist/contrib/auto-render.mjs';
-	import 'katex/dist/katex.min.css';
 	import mermaid from 'mermaid';
 
 	import { fade } from 'svelte/transition';
@@ -79,76 +78,26 @@
 
 	let tokens;
 
+	import 'katex/dist/katex.min.css';
+
+	import markedKatex from '$lib/utils/katex-extension';
+	const options = {
+		throwOnError: false
+	};
+
+	marked.use(markedKatex(options));
+
 	$: (async () => {
 		if (message?.content) {
 			tokens = marked.lexer(
 				replaceTokens(sanitizeResponseContent(message?.content), model?.name, $user?.name)
 			);
-			// console.log(message?.content, tokens);
 		}
 	})();
 
-	$: if (message) {
-		renderStyling();
-	}
-
-	const renderStyling = async () => {
-		await tick();
-
-		if (tooltipInstance) {
-			tooltipInstance[0]?.destroy();
-		}
-
+	$: if (message?.done ?? false) {
 		renderLatex();
-
-		if (message.info) {
-			let tooltipContent = '';
-			if (message.info.openai) {
-				tooltipContent = `prompt_tokens: ${message.info.prompt_tokens ?? 'N/A'}<br/>
-													completion_tokens: ${message.info.completion_tokens ?? 'N/A'}<br/>
-													total_tokens: ${message.info.total_tokens ?? 'N/A'}`;
-			} else {
-				tooltipContent = `response_token/s: ${
-					`${
-						Math.round(
-							((message.info.eval_count ?? 0) / (message.info.eval_duration / 1000000000)) * 100
-						) / 100
-					} tokens` ?? 'N/A'
-				}<br/>
-					prompt_token/s: ${
-						Math.round(
-							((message.info.prompt_eval_count ?? 0) /
-								(message.info.prompt_eval_duration / 1000000000)) *
-								100
-						) / 100 ?? 'N/A'
-					} tokens<br/>
-                    total_duration: ${
-											Math.round(((message.info.total_duration ?? 0) / 1000000) * 100) / 100 ??
-											'N/A'
-										}ms<br/>
-                    load_duration: ${
-											Math.round(((message.info.load_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
-										}ms<br/>
-                    prompt_eval_count: ${message.info.prompt_eval_count ?? 'N/A'}<br/>
-                    prompt_eval_duration: ${
-											Math.round(((message.info.prompt_eval_duration ?? 0) / 1000000) * 100) /
-												100 ?? 'N/A'
-										}ms<br/>
-                    eval_count: ${message.info.eval_count ?? 'N/A'}<br/>
-                    eval_duration: ${
-											Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
-										}ms<br/>
-                    approximate_total: ${approximateToHumanReadable(message.info.total_duration)}`;
-			}
-			tooltipInstance = tippy(`#info-${message.id}`, {
-				content: `<span class="text-xs" id="tooltip-${message.id}">${tooltipContent}</span>`,
-				allowHTML: true,
-				theme: 'dark',
-				arrow: false,
-				offset: [0, 4]
-			});
-		}
-	};
+	}
 
 	const renderLatex = () => {
 		let chatMessageElements = document
@@ -330,14 +279,14 @@
 		editedContent = '';
 
 		await tick();
-		renderStyling();
+		renderLatex();
 	};
 
 	const cancelEditMessage = async () => {
 		edit = false;
 		editedContent = '';
 		await tick();
-		renderStyling();
+		renderLatex();
 	};
 
 	const generateImage = async (message) => {
@@ -362,7 +311,7 @@
 	$: if (!edit) {
 		(async () => {
 			await tick();
-			renderStyling();
+			renderLatex();
 
 			await mermaid.run({
 				querySelector: '.mermaid'
@@ -372,7 +321,7 @@
 
 	onMount(async () => {
 		await tick();
-		renderStyling();
+		renderLatex();
 
 		await mermaid.run({
 			querySelector: '.mermaid'
@@ -420,7 +369,7 @@
 				{/if}
 
 				<div
-					class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-2 prose-ol:-my-2 prose-li:-my-3 whitespace-pre-line"
+					class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-p:my-0 prose-img:my-1 prose-headings:my-1 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-ul:-my-0 prose-ol:-my-0 prose-li:-my-0 whitespace-pre-line"
 				>
 					<div>
 						{#if (message?.statusHistory ?? [...(message?.status ? [message?.status] : [])]).length > 0}
@@ -841,31 +790,71 @@
 								{/if}
 
 								{#if message.info}
-									<Tooltip content={$i18n.t('Generation Info')} placement="bottom">
-										<button
-											class=" {isLastMessage
-												? 'visible'
-												: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
-											on:click={() => {
-												console.log(message);
-											}}
-											id="info-{message.id}"
-										>
-											<svg
-												xmlns="http://www.w3.org/2000/svg"
-												fill="none"
-												viewBox="0 0 24 24"
-												stroke-width="2.3"
-												stroke="currentColor"
-												class="w-4 h-4"
+									<Tooltip
+										content={message.info.openai
+											? `prompt_tokens: ${message.info.prompt_tokens ?? 'N/A'}<br/>
+													completion_tokens: ${message.info.completion_tokens ?? 'N/A'}<br/>
+													total_tokens: ${message.info.total_tokens ?? 'N/A'}`
+											: `response_token/s: ${
+													`${
+														Math.round(
+															((message.info.eval_count ?? 0) /
+																(message.info.eval_duration / 1000000000)) *
+																100
+														) / 100
+													} tokens` ?? 'N/A'
+											  }<br/>
+					prompt_token/s: ${
+						Math.round(
+							((message.info.prompt_eval_count ?? 0) /
+								(message.info.prompt_eval_duration / 1000000000)) *
+								100
+						) / 100 ?? 'N/A'
+					} tokens<br/>
+		            total_duration: ${
+									Math.round(((message.info.total_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
+								}ms<br/>
+		            load_duration: ${
+									Math.round(((message.info.load_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
+								}ms<br/>
+		            prompt_eval_count: ${message.info.prompt_eval_count ?? 'N/A'}<br/>
+		            prompt_eval_duration: ${
+									Math.round(((message.info.prompt_eval_duration ?? 0) / 1000000) * 100) / 100 ??
+									'N/A'
+								}ms<br/>
+		            eval_count: ${message.info.eval_count ?? 'N/A'}<br/>
+		            eval_duration: ${
+									Math.round(((message.info.eval_duration ?? 0) / 1000000) * 100) / 100 ?? 'N/A'
+								}ms<br/>
+		            approximate_total: ${approximateToHumanReadable(message.info.total_duration)}`}
+										placement="top"
+									>
+										<Tooltip content={$i18n.t('Generation Info')} placement="bottom">
+											<button
+												class=" {isLastMessage
+													? 'visible'
+													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
+												on:click={() => {
+													console.log(message);
+												}}
+												id="info-{message.id}"
 											>
-												<path
-													stroke-linecap="round"
-													stroke-linejoin="round"
-													d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
-												/>
-											</svg>
-										</button>
+												<svg
+													xmlns="http://www.w3.org/2000/svg"
+													fill="none"
+													viewBox="0 0 24 24"
+													stroke-width="2.3"
+													stroke="currentColor"
+													class="w-4 h-4"
+												>
+													<path
+														stroke-linecap="round"
+														stroke-linejoin="round"
+														d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
+													/>
+												</svg>
+											</button>
+										</Tooltip>
 									</Tooltip>
 								{/if}
 

+ 3 - 0
src/lib/i18n/locales/ar-BH/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "متابعة الرد",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "تم نسخ عنوان URL للدردشة المشتركة إلى الحافظة",
 	"Copy": "نسخ",
+	"Copy Code": "",
 	"Copy last code block": "انسخ كتلة التعليمات البرمجية الأخيرة",
 	"Copy last response": "انسخ الرد الأخير",
 	"Copy Link": "أنسخ الرابط",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "من اليمين إلى اليسار",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "حفظ",

+ 3 - 0
src/lib/i18n/locales/bg-BG/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Продължи отговора",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Копирана е връзката за чат!",
 	"Copy": "Копирай",
+	"Copy Code": "",
 	"Copy last code block": "Копиране на последен код блок",
 	"Copy last response": "Копиране на последен отговор",
 	"Copy Link": "Копиране на връзка",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Запис",

+ 3 - 0
src/lib/i18n/locales/bn-BD/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "যাচাই করুন",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "শেয়ারকৃত কথা-ব্যবহারের URL ক্লিপবোর্ডে কপি করা হয়েছে!",
 	"Copy": "অনুলিপি",
+	"Copy Code": "",
 	"Copy last code block": "সর্বশেষ কোড ব্লক কপি করুন",
 	"Copy last response": "সর্বশেষ রেসপন্স কপি করুন",
 	"Copy Link": "লিংক কপি করুন",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "রোজ পাইন",
 	"Rosé Pine Dawn": "ভোরের রোজ পাইন",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "সংরক্ষণ",

+ 5 - 2
src/lib/i18n/locales/ca-ES/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continuar la resposta",
 	"Continue with {{provider}}": "Continuar amb {{provider}}",
 	"Controls": "Controls",
+	"Copied": "Copiat",
 	"Copied shared chat URL to clipboard!": "S'ha copiat l'URL compartida al porta-retalls!",
 	"Copy": "Copiar",
+	"Copy Code": "Copiar el codi",
 	"Copy last code block": "Copiar l'últim bloc de codi",
 	"Copy last response": "Copiar l'última resposta",
 	"Copy Link": "Copiar l'enllaç",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Albada Rosé Pine",
 	"RTL": "RTL",
+	"Run": "Executar",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Executa Llama 2, Code Llama, i altres models. Personalitza i crea els teus propis models.",
 	"Running": "S'està executant",
 	"Save": "Desar",
@@ -509,7 +512,7 @@
 	"Scan": "Escanejar",
 	"Scan complete!": "Escaneigr completat!",
 	"Scan for documents from {{path}}": "Escanejar documents des de {{path}}",
-	"Scroll to bottom when switching between branches": "",
+	"Scroll to bottom when switching between branches": "Desplaçar a la part inferior quan es canviï de branca",
 	"Search": "Cercar",
 	"Search a model": "Cercar un model",
 	"Search Chats": "Cercar xats",
@@ -624,7 +627,7 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Per accedir a la WebUI, poseu-vos en contacte amb l'administrador. Els administradors poden gestionar els estats dels usuaris des del tauler d'administració.",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "Per afegir documents aquí, puja-ls primer a l'espai de treball \"Documents\".",
 	"to chat input.": "a l'entrada del xat.",
-	"To select actions here, add them to the \"Functions\" workspace first.": "Per seleccionar accions aquí, afegeix-los primer a l'espai de treball \"Funcions\".",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Per seleccionar accions aquí, afegeix-les primer a l'espai de treball \"Funcions\".",
 	"To select filters here, add them to the \"Functions\" workspace first.": "Per seleccionar filtres aquí, afegeix-los primer a l'espai de treball \"Funcions\".",
 	"To select toolkits here, add them to the \"Tools\" workspace first.": "Per seleccionar kits d'eines aquí, afegeix-los primer a l'espai de treball \"Eines\".",
 	"Today": "Avui",

+ 3 - 0
src/lib/i18n/locales/ceb-PH/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "",
 	"Copy": "",
+	"Copy Code": "",
 	"Copy last code block": "Kopyaha ang katapusang bloke sa code",
 	"Copy last response": "Kopyaha ang kataposang tubag",
 	"Copy Link": "",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Aube Pine Rosé",
 	"RTL": "",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Tipigi",

+ 3 - 0
src/lib/i18n/locales/de-DE/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Antwort fortsetzen",
 	"Continue with {{provider}}": "Mit {{provider}} fortfahren",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Freigabelink in die Zwischenablage kopiert!",
 	"Copy": "Kopieren",
+	"Copy Code": "",
 	"Copy last code block": "Letzten Codeblock kopieren",
 	"Copy last response": "Letzte Antwort kopieren",
 	"Copy Link": "Link kopieren",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Läuft",
 	"Save": "Speichern",

+ 3 - 0
src/lib/i18n/locales/dg-DG/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "",
 	"Copy": "",
+	"Copy Code": "",
 	"Copy last code block": "Copy last code block",
 	"Copy last response": "Copy last response",
 	"Copy Link": "",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Save much wow",

+ 3 - 0
src/lib/i18n/locales/en-GB/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "",
 	"Copy": "",
+	"Copy Code": "",
 	"Copy last code block": "",
 	"Copy last response": "",
 	"Copy Link": "",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "",
 	"Rosé Pine Dawn": "",
 	"RTL": "",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "",

+ 3 - 0
src/lib/i18n/locales/en-US/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "",
 	"Copy": "",
+	"Copy Code": "",
 	"Copy last code block": "",
 	"Copy last response": "",
 	"Copy Link": "",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "",
 	"Rosé Pine Dawn": "",
 	"RTL": "",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "",

+ 3 - 0
src/lib/i18n/locales/es-ES/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continuar Respuesta",
 	"Continue with {{provider}}": "Continuar con {{provider}}",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "¡URL de chat compartido copiado al portapapeles!",
 	"Copy": "Copiar",
+	"Copy Code": "",
 	"Copy last code block": "Copia el último bloque de código",
 	"Copy last response": "Copia la última respuesta",
 	"Copy Link": "Copiar enlace",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Ejecutando",
 	"Save": "Guardar",

+ 3 - 0
src/lib/i18n/locales/fa-IR/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "ادامه پاسخ",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL چت به کلیپ بورد کپی شد!",
 	"Copy": "کپی",
+	"Copy Code": "",
 	"Copy last code block": "کپی آخرین بلوک کد",
 	"Copy last response": "کپی آخرین پاسخ",
 	"Copy Link": "کپی لینک",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "ذخیره",

+ 3 - 0
src/lib/i18n/locales/fi-FI/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Jatka vastausta",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Jaettu keskustelulinkki kopioitu leikepöydälle!",
 	"Copy": "Kopioi",
+	"Copy Code": "",
 	"Copy last code block": "Kopioi viimeisin koodilohko",
 	"Copy last response": "Kopioi viimeisin vastaus",
 	"Copy Link": "Kopioi linkki",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosee-mänty",
 	"Rosé Pine Dawn": "Aamuinen Rosee-mänty",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Tallenna",

+ 3 - 0
src/lib/i18n/locales/fr-CA/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continuer la réponse",
 	"Continue with {{provider}}": "Continuer avec {{provider}}",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL du chat copiée dans le presse-papiers\u00a0!",
 	"Copy": "Copie",
+	"Copy Code": "",
 	"Copy last code block": "Copier le dernier bloc de code",
 	"Copy last response": "Copier la dernière réponse",
 	"Copy Link": "Copier le lien",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Pin rosé",
 	"Rosé Pine Dawn": "Aube de Pin Rosé",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Courir",
 	"Save": "Enregistrer",

+ 3 - 0
src/lib/i18n/locales/fr-FR/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continuer la réponse",
 	"Continue with {{provider}}": "Continuer avec {{provider}}",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL du chat copiée dans le presse-papiers\u00a0!",
 	"Copy": "Copie",
+	"Copy Code": "",
 	"Copy last code block": "Copier le dernier bloc de code",
 	"Copy last response": "Copier la dernière réponse",
 	"Copy Link": "Copier le lien",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Pin rosé",
 	"Rosé Pine Dawn": "Aube de Pin Rosé",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Courir",
 	"Save": "Enregistrer",

+ 3 - 0
src/lib/i18n/locales/he-IL/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "המשך תגובה",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "העתקת כתובת URL של צ'אט משותף ללוח!",
 	"Copy": "העתק",
+	"Copy Code": "",
 	"Copy last code block": "העתק את בלוק הקוד האחרון",
 	"Copy last response": "העתק את התגובה האחרונה",
 	"Copy Link": "העתק קישור",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "שמור",

+ 3 - 0
src/lib/i18n/locales/hi-IN/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "प्रतिक्रिया जारी रखें",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "साझा चैट URL को क्लिपबोर्ड पर कॉपी किया गया!",
 	"Copy": "कॉपी",
+	"Copy Code": "",
 	"Copy last code block": "अंतिम कोड ब्लॉक कॉपी करें",
 	"Copy last response": "अंतिम प्रतिक्रिया कॉपी करें",
 	"Copy Link": "लिंक को कॉपी करें",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "रोसे पिन",
 	"Rosé Pine Dawn": "रोसे पिन डेन",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "सहेजें",

+ 3 - 0
src/lib/i18n/locales/hr-HR/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Nastavi odgovor",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL dijeljenog razgovora kopiran u međuspremnik!",
 	"Copy": "Kopiraj",
+	"Copy Code": "",
 	"Copy last code block": "Kopiraj zadnji blok koda",
 	"Copy last response": "Kopiraj zadnji odgovor",
 	"Copy Link": "Kopiraj vezu",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Pokrenuto",
 	"Save": "Spremi",

+ 3 - 0
src/lib/i18n/locales/id-ID/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Lanjutkan Tanggapan",
 	"Continue with {{provider}}": "Lanjutkan dengan {{penyedia}}",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Menyalin URL obrolan bersama ke papan klip!",
 	"Copy": "Menyalin",
+	"Copy Code": "",
 	"Copy last code block": "Salin blok kode terakhir",
 	"Copy last response": "Salin tanggapan terakhir",
 	"Copy Link": "Salin Tautan",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Pinus Rosé",
 	"Rosé Pine Dawn": "Rosé Pine Fajar",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Berjalan",
 	"Save": "Simpan",

+ 3 - 0
src/lib/i18n/locales/it-IT/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continua risposta",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL della chat condivisa copiato negli appunti!",
 	"Copy": "Copia",
+	"Copy Code": "",
 	"Copy last code block": "Copia ultimo blocco di codice",
 	"Copy last response": "Copia ultima risposta",
 	"Copy Link": "Copia link",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Salva",

+ 3 - 0
src/lib/i18n/locales/ja-JP/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "続きの応答",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "共有チャットURLをクリップボードにコピーしました!",
 	"Copy": "コピー",
+	"Copy Code": "",
 	"Copy last code block": "最後のコードブロックをコピー",
 	"Copy last response": "最後の応答をコピー",
 	"Copy Link": "リンクをコピー",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "保存",

+ 3 - 0
src/lib/i18n/locales/ka-GE/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "პასუხის გაგრძელება",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "ყავს ჩათის URL-ი კლიპბორდში!",
 	"Copy": "კოპირება",
+	"Copy Code": "",
 	"Copy last code block": "ბოლო ბლოკის კოპირება",
 	"Copy last response": "ბოლო პასუხის კოპირება",
 	"Copy Link": "კოპირება",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "ვარდისფერი ფიჭვის ხე",
 	"Rosé Pine Dawn": "ვარდისფერი ფიჭვის გარიჟრაჟი",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "შენახვა",

+ 3 - 0
src/lib/i18n/locales/ko-KR/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "대화 계속",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "공유 채팅 URL이 클립보드에 복사되었습니다!",
 	"Copy": "복사",
+	"Copy Code": "",
 	"Copy last code block": "마지막 코드 블록 복사",
 	"Copy last response": "마지막 응답 복사",
 	"Copy Link": "링크 복사",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "실행 중",
 	"Save": "저장",

+ 3 - 0
src/lib/i18n/locales/lt-LT/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Tęsti atsakymą",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Nukopijavote pokalbio nuorodą",
 	"Copy": "Kopijuoti",
+	"Copy Code": "",
 	"Copy last code block": "Kopijuoti paskutinį kodo bloką",
 	"Copy last response": "Kopijuoti paskutinį atsakymą",
 	"Copy Link": "Kopijuoti nuorodą",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Išsaugoti",

+ 3 - 0
src/lib/i18n/locales/ms-MY/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Teruskan Respons",
 	"Continue with {{provider}}": "Teruskan dengan {{provider}}",
 	"Controls": "Kawalan",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Menyalin URL sembang kongsi ke papan klip",
 	"Copy": "Salin",
+	"Copy Code": "",
 	"Copy last code block": "Salin Blok Kod Terakhir",
 	"Copy last response": "Salin Respons Terakhir",
 	"Copy Link": "Salin Pautan",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Jalankan Llama 2, Code Llama dan model lain. Sesuaikan dan buat sendiri.",
 	"Running": "Sedang dijalankan",
 	"Save": "Simpan",

+ 3 - 0
src/lib/i18n/locales/nb-NO/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Fortsett svar",
 	"Continue with {{provider}}": "Fortsett med {{provider}}",
 	"Controls": "Kontroller",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Kopiert delt chat-URL til utklippstavlen!",
 	"Copy": "Kopier",
+	"Copy Code": "",
 	"Copy last code block": "Kopier siste kodeblokk",
 	"Copy last response": "Kopier siste svar",
 	"Copy Link": "Kopier lenke",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Kjør Llama 2, Code Llama og andre modeller. Tilpass og lag egne versjoner.",
 	"Running": "Kjører",
 	"Save": "Lagre",

+ 3 - 0
src/lib/i18n/locales/nl-NL/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Doorgaan met Antwoord",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL van gedeelde gesprekspagina gekopieerd naar klembord!",
 	"Copy": "Kopieer",
+	"Copy Code": "",
 	"Copy last code block": "Kopieer laatste code blok",
 	"Copy last response": "Kopieer laatste antwoord",
 	"Copy Link": "Kopieer Link",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Opslaan",

+ 3 - 0
src/lib/i18n/locales/pa-IN/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "ਜਵਾਬ ਜਾਰੀ ਰੱਖੋ",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "ਸਾਂਝੇ ਕੀਤੇ ਗੱਲਬਾਤ URL ਨੂੰ ਕਲਿੱਪਬੋਰਡ 'ਤੇ ਕਾਪੀ ਕਰ ਦਿੱਤਾ!",
 	"Copy": "ਕਾਪੀ ਕਰੋ",
+	"Copy Code": "",
 	"Copy last code block": "ਆਖਰੀ ਕੋਡ ਬਲਾਕ ਨੂੰ ਕਾਪੀ ਕਰੋ",
 	"Copy last response": "ਆਖਰੀ ਜਵਾਬ ਨੂੰ ਕਾਪੀ ਕਰੋ",
 	"Copy Link": "ਲਿੰਕ ਕਾਪੀ ਕਰੋ",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "ਰੋਜ਼ ਪਾਈਨ",
 	"Rosé Pine Dawn": "ਰੋਜ਼ ਪਾਈਨ ਡਾਨ",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "ਸੰਭਾਲੋ",

+ 3 - 0
src/lib/i18n/locales/pl-PL/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Kontynuuj odpowiedź",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Skopiowano URL czatu do schowka!",
 	"Copy": "Kopiuj",
+	"Copy Code": "",
 	"Copy last code block": "Skopiuj ostatni blok kodu",
 	"Copy last response": "Skopiuj ostatnią odpowiedź",
 	"Copy Link": "Kopiuj link",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RLT",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Zapisz",

+ 3 - 0
src/lib/i18n/locales/pt-BR/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continuar Resposta",
 	"Continue with {{provider}}": "Continuar com {{provider}}",
 	"Controls": "Controles",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL de chat compartilhado copiado para a área de transferência!",
 	"Copy": "Copiar",
+	"Copy Code": "",
 	"Copy last code block": "Copiar último bloco de código",
 	"Copy last response": "Copiar última resposta",
 	"Copy Link": "Copiar Link",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Execute Llama 2, Code Llama e outros modelos. Personalize e crie os seus próprios.",
 	"Running": "Executando",
 	"Save": "Salvar",

+ 3 - 0
src/lib/i18n/locales/pt-PT/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continuar resposta",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL de Conversa partilhado copiada com sucesso!",
 	"Copy": "Copiar",
+	"Copy Code": "",
 	"Copy last code block": "Copiar último bloco de código",
 	"Copy last response": "Copiar última resposta",
 	"Copy Link": "Copiar link",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "A correr",
 	"Save": "Guardar",

+ 3 - 0
src/lib/i18n/locales/ro-RO/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Continuă Răspunsul",
 	"Continue with {{provider}}": "Continuă cu {{provider}}",
 	"Controls": "Controale",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "URL-ul conversației partajate a fost copiat în clipboard!",
 	"Copy": "Copiază",
+	"Copy Code": "",
 	"Copy last code block": "Copiază ultimul bloc de cod",
 	"Copy last response": "Copiază ultimul răspuns",
 	"Copy Link": "Copiază Link",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Rulați Llama 2, Code Llama și alte modele. Personalizați și creați-vă propriile modele.",
 	"Running": "Rulare",
 	"Save": "Salvează",

+ 3 - 0
src/lib/i18n/locales/ru-RU/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Продолжить ответ",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Копирование общей ссылки чат в буфер обмена!",
 	"Copy": "Копировать",
+	"Copy Code": "",
 	"Copy last code block": "Копировать последний блок кода",
 	"Copy last response": "Копировать последний ответ",
 	"Copy Link": "Копировать ссылку",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Розовое сосновое дерево",
 	"Rosé Pine Dawn": "Розовое сосновое дерево рассвет",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Сохранить",

+ 3 - 0
src/lib/i18n/locales/sr-RS/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Настави одговор",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Адреса дељеног ћаскања ископирана у оставу!",
 	"Copy": "Копирај",
+	"Copy Code": "",
 	"Copy last code block": "Копирај последњи блок кода",
 	"Copy last response": "Копирај последњи одговор",
 	"Copy Link": "Копирај везу",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "ДНЛ",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "Сачувај",

+ 3 - 0
src/lib/i18n/locales/sv-SE/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Fortsätt svar",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Kopierad delad chatt-URL till urklipp!",
 	"Copy": "Kopiera",
+	"Copy Code": "",
 	"Copy last code block": "Kopiera sista kodblock",
 	"Copy last response": "Kopiera sista svar",
 	"Copy Link": "Kopiera länk",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Kör",
 	"Save": "Spara",

+ 3 - 0
src/lib/i18n/locales/th-TH/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "ตอบสนองต่อไป",
 	"Continue with {{provider}}": "ดำเนินการต่อด้วย {{provider}}",
 	"Controls": "การควบคุม",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "คัดลอก URL แชทที่แชร์ไปยังคลิปบอร์ดแล้ว!",
 	"Copy": "คัดลอก",
+	"Copy Code": "",
 	"Copy last code block": "คัดลอกบล็อกโค้ดสุดท้าย",
 	"Copy last response": "คัดลอกการตอบสนองล่าสุด",
 	"Copy Link": "คัดลอกลิงก์",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "รัน Llama 2, Code Llama และโมเดลอื่นๆ ปรับแต่งและสร้างของคุณเอง",
 	"Running": "กำลังทำงาน",
 	"Save": "บันทึก",

+ 3 - 0
src/lib/i18n/locales/tk-TW/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "",
 	"Continue with {{provider}}": "",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "",
 	"Copy": "",
+	"Copy Code": "",
 	"Copy last code block": "",
 	"Copy last response": "",
 	"Copy Link": "",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "",
 	"Rosé Pine Dawn": "",
 	"RTL": "",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "",
 	"Save": "",

+ 3 - 0
src/lib/i18n/locales/tr-TR/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Yanıta Devam Et",
 	"Continue with {{provider}}": "{{provider}} ile devam et",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!",
 	"Copy": "Kopyala",
+	"Copy Code": "",
 	"Copy last code block": "Son kod bloğunu kopyala",
 	"Copy last response": "Son yanıtı kopyala",
 	"Copy Link": "Bağlantıyı Kopyala",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
 	"Running": "Çalışıyor",
 	"Save": "Kaydet",

+ 5 - 2
src/lib/i18n/locales/uk-UA/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Продовжити відповідь",
 	"Continue with {{provider}}": "Продовжити з {{provider}}",
 	"Controls": "Керування",
+	"Copied": "Скопійовано",
 	"Copied shared chat URL to clipboard!": "Скопійовано URL-адресу спільного чату в буфер обміну!",
 	"Copy": "Копіювати",
+	"Copy Code": "Копіювати код",
 	"Copy last code block": "Копіювати останній блок коду",
 	"Copy last response": "Копіювати останню відповідь",
 	"Copy Link": "Копіювати посилання",
@@ -375,7 +377,7 @@
 	"Memory deleted successfully": "Пам'ять успішно видалено",
 	"Memory updated successfully": "Пам'ять успішно оновлено",
 	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Повідомлення, які ви надішлете після створення посилання, не будуть доступні для інших. Користувачі, які мають URL, зможуть переглядати спільний чат.",
-	"Min P": "",
+	"Min P": "Min P",
 	"Minimum Score": "Мінімальний бал",
 	"Mirostat": "Mirostat",
 	"Mirostat Eta": "Mirostat Eta",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "Запустити",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Запустіть Llama 2, Code Llama та інші моделі. Налаштуйте та створіть власну.",
 	"Running": "Виконується",
 	"Save": "Зберегти",
@@ -509,7 +512,7 @@
 	"Scan": "Сканування",
 	"Scan complete!": "Сканування завершено!",
 	"Scan for documents from {{path}}": "Сканування документів з {{path}}",
-	"Scroll to bottom when switching between branches": "",
+	"Scroll to bottom when switching between branches": "Перемотувати до кінця при перемиканні між гілками",
 	"Search": "Пошук",
 	"Search a model": "Шукати модель",
 	"Search Chats": "Пошук в чатах",

+ 3 - 0
src/lib/i18n/locales/vi-VN/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "Tiếp tục trả lời",
 	"Continue with {{provider}}": "Tiếp tục với {{provider}}",
 	"Controls": "",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "Đã sao chép link chia sẻ trò chuyện vào clipboard!",
 	"Copy": "Sao chép",
+	"Copy Code": "",
 	"Copy last code block": "Sao chép khối mã cuối cùng",
 	"Copy last response": "Sao chép phản hồi cuối cùng",
 	"Copy Link": "Sao chép link",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Chạy Llama 2, Code Llama và các mô hình khác. Tùy chỉnh hoặc mô hình riêng của bạn.",
 	"Running": "Đang chạy",
 	"Save": "Lưu",

+ 4 - 1
src/lib/i18n/locales/zh-CN/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "继续生成",
 	"Continue with {{provider}}": "使用 {{provider}} 继续",
 	"Controls": "对话高级设置",
+	"Copied": "已复制",
 	"Copied shared chat URL to clipboard!": "已复制此对话分享链接至剪贴板!",
 	"Copy": "复制",
+	"Copy Code": "复制代码",
 	"Copy last code block": "复制最后一个代码块中的代码",
 	"Copy last response": "复制最后一次回复内容",
 	"Copy Link": "复制链接",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "从右至左",
+	"Run": "运行",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "运行 Llama 2、Code Llama 和其他模型。自定义和创建您自己的模型。",
 	"Running": "运行中",
 	"Save": "保存",
@@ -509,7 +512,7 @@
 	"Scan": "立即扫描",
 	"Scan complete!": "扫描完成!",
 	"Scan for documents from {{path}}": "从 {{path}} 扫描文档",
-	"Scroll to bottom when switching between branches": "",
+	"Scroll to bottom when switching between branches": "在分支间切换时滚动到底部",
 	"Search": "搜索",
 	"Search a model": "搜索模型",
 	"Search Chats": "搜索对话",

+ 3 - 0
src/lib/i18n/locales/zh-TW/translation.json

@@ -134,8 +134,10 @@
 	"Continue Response": "繼續回應",
 	"Continue with {{provider}}": "使用 {{provider}} 繼續",
 	"Controls": "控制項",
+	"Copied": "",
 	"Copied shared chat URL to clipboard!": "已複製共用對話 URL 到剪貼簿!",
 	"Copy": "複製",
+	"Copy Code": "",
 	"Copy last code block": "複製最後一個程式碼區塊",
 	"Copy last response": "複製最後一個回應",
 	"Copy Link": "複製連結",
@@ -499,6 +501,7 @@
 	"Rosé Pine": "玫瑰松",
 	"Rosé Pine Dawn": "黎明玫瑰松",
 	"RTL": "從右到左",
+	"Run": "",
 	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "執行 Llama 2、Code Llama 和其他模型。自訂並建立您自己的模型。",
 	"Running": "運作中",
 	"Save": "儲存",

+ 4 - 3
src/lib/utils/index.ts

@@ -9,7 +9,7 @@ import { WEBUI_BASE_URL } from '$lib/constants';
 const convertLatexToSingleLine = (content) => {
 	// Patterns to match multiline LaTeX blocks
 	const patterns = [
-		/(\$\$[\s\S]*?\$\$)/g, // Match $$ ... $$
+		/(\$\$\s[\s\S]*?\s\$\$)/g, // Match $$ ... $$
 		/(\\\[[\s\S]*?\\\])/g, // Match \[ ... \]
 		/(\\begin\{[a-z]+\}[\s\S]*?\\end\{[a-z]+\})/g // Match \begin{...} ... \end{...}
 	];
@@ -25,7 +25,8 @@ const convertLatexToSingleLine = (content) => {
 
 export const sanitizeResponseContent = (content: string) => {
 	// replace single backslash with double backslash
-	content = content.replace(/\\/g, '\\\\');
+	content = content.replace(/\\\\/g, '\\\\\\\\');
+
 	content = convertLatexToSingleLine(content);
 
 	// First, temporarily replace valid <video> tags with a placeholder
@@ -87,7 +88,7 @@ export const replaceTokens = (content, char, user) => {
 };
 
 export const revertSanitizedResponseContent = (content: string) => {
-	return content.replaceAll('&lt;', '<').replaceAll('&gt;', '>');
+	return content.replaceAll('&lt;', '<').replaceAll('&gt;', '>').replaceAll('\\\\', '\\');
 };
 
 export function unescapeHtml(html: string) {

+ 80 - 0
src/lib/utils/katex-extension.ts

@@ -0,0 +1,80 @@
+import katex from 'katex';
+
+const inlineRule = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/;
+const inlineRuleNonStandard = /^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1/; // Non-standard, even if there are no spaces before and after $ or $$, try to parse
+
+const blockRule = /^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;
+
+export default function(options = {}) {
+  return {
+    extensions: [
+      inlineKatex(options, createRenderer(options, false)),
+      blockKatex(options, createRenderer(options, true)),
+    ],
+  };
+}
+
+function createRenderer(options, newlineAfter) {
+  return (token) => katex.renderToString(token.text, { ...options, displayMode: token.displayMode }) + (newlineAfter ? '\n' : '');
+}
+
+function inlineKatex(options, renderer) {
+  const nonStandard = options && options.nonStandard;
+  const ruleReg = nonStandard ? inlineRuleNonStandard : inlineRule;
+  return {
+    name: 'inlineKatex',
+    level: 'inline',
+    start(src) {
+      let index;
+      let indexSrc = src;
+
+      while (indexSrc) {
+        index = indexSrc.indexOf('$');
+        if (index === -1) {
+          return;
+        }
+        const f = nonStandard ? index > -1 : index === 0 || indexSrc.charAt(index - 1) === ' ';
+        if (f) {
+          const possibleKatex = indexSrc.substring(index);
+
+          if (possibleKatex.match(ruleReg)) {
+            return index;
+          }
+        }
+
+        indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, '');
+      }
+    },
+    tokenizer(src, tokens) {
+      const match = src.match(ruleReg);
+      if (match) {
+        return {
+          type: 'inlineKatex',
+          raw: match[0],
+          text: match[2].trim(),
+          displayMode: match[1].length === 2,
+        };
+      }
+    },
+    renderer,
+  };
+}
+
+function blockKatex(options, renderer) {
+  return {
+    name: 'blockKatex',
+    level: 'block',
+    tokenizer(src, tokens) {
+      const match = src.match(blockRule);
+      if (match) {
+        return {
+          type: 'blockKatex',
+          raw: match[0],
+          text: match[2].trim(),
+          displayMode: match[1].length === 2,
+        };
+      }
+    },
+    renderer,
+  };
+}