Added KaTeX Rendering

This commit is contained in:
2024-04-23 09:10:57 +09:00
parent 2fdf7ac126
commit 0066760f86
7 changed files with 144 additions and 8 deletions

1
.env
View File

@@ -2,3 +2,4 @@
#VITE_API_BASE=http://localhost:5174 #VITE_API_BASE=http://localhost:5174
#VITE_ENDPOINT_COMPLETIONS=/v1/chat/completions #VITE_ENDPOINT_COMPLETIONS=/v1/chat/completions
#VITE_ENDPOINT_MODELS=/v1/models #VITE_ENDPOINT_MODELS=/v1/models
#VITE_RENDER_LATEX=false

2
.gitignore vendored
View File

@@ -6,7 +6,7 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
yarn.lock
node_modules node_modules
dist dist
dist-ssr dist-ssr

26
package-lock.json generated
View File

@@ -29,6 +29,7 @@
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"flourite": "^1.2.4", "flourite": "^1.2.4",
"gpt-tokenizer": "^2.1.2", "gpt-tokenizer": "^2.1.2",
"katex": "^0.16.10",
"llama-tokenizer-js": "^1.1.3", "llama-tokenizer-js": "^1.1.3",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"sass": "^1.69.7", "sass": "^1.69.7",
@@ -3273,6 +3274,31 @@
"json5": "lib/cli.js" "json5": "lib/cli.js"
} }
}, },
"node_modules/katex": {
"version": "0.16.10",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
"dev": true,
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"dependencies": {
"commander": "^8.3.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/katex/node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true,
"engines": {
"node": ">= 12"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View File

@@ -35,6 +35,7 @@
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"flourite": "^1.2.4", "flourite": "^1.2.4",
"gpt-tokenizer": "^2.1.2", "gpt-tokenizer": "^2.1.2",
"katex": "^0.16.10",
"llama-tokenizer-js": "^1.1.3", "llama-tokenizer-js": "^1.1.3",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"sass": "^1.69.7", "sass": "^1.69.7",

View File

@@ -27,12 +27,26 @@
type LanguageType type LanguageType
} from 'svelte-highlight/languages/index' } from 'svelte-highlight/languages/index'
import katex from 'katex'
import 'katex/contrib/mhchem'
export const type: 'code' = 'code' export const type: 'code' = 'code'
export const raw: string = '' export const raw: string = ''
export const codeBlockStyle: 'indented' | undefined = undefined export const codeBlockStyle: 'indented' | undefined = undefined
export let lang: string | undefined export let lang: string | undefined
export let text: string export let text: string
let renderedMath: string | undefined
$: if (lang === 'rendermath') {
renderedMath = katex.renderToString(text, {
throwOnError: false,
displayMode: true
})
} else {
renderedMath = undefined
}
// Map lang string to LanguageType // Map lang string to LanguageType
let language: LanguageType<string> let language: LanguageType<string>
@@ -117,7 +131,11 @@
{@html style} {@html style}
</svelte:head> </svelte:head>
<div class="code-block is-relative"> {#if lang === 'rendermath'}
<button class="button is-light is-outlined is-small p-2" on:click={copyFunction}>Copy</button> {@html renderedMath}
<Highlight code={text} {language} /> {:else}
</div> <div class="code-block is-relative">
<button class="button is-light is-outlined is-small p-2" on:click={copyFunction}>Copy</button>
<Highlight code={text} {language} />
</div>
{/if}

20
src/lib/Codespan.svelte Normal file
View File

@@ -0,0 +1,20 @@
<script lang="ts">
export let raw
import katex from 'katex'
import 'katex/contrib/mhchem'
let renderedMath: string | undefined
if (raw.startsWith('`rendermath')) {
renderedMath = katex.renderToString(raw.replace(/`rendermath|`/g, ''), {
throwOnError: false,
displayMode: false
})
}
</script>
{#if renderedMath}
{@html renderedMath}
{:else}
<code>{raw.replace(/`/g, '')}</code>
{/if}

View File

@@ -1,18 +1,21 @@
<script lang="ts"> <script lang="ts">
import Code from './Code.svelte' import Code from './Code.svelte'
import Codespan from './Codespan.svelte'
import { afterUpdate, createEventDispatcher, onMount } from 'svelte' import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
import { deleteMessage, deleteSummaryMessage, truncateFromMessage, submitExitingPromptsNow, continueMessage, updateMessages } from './Storage.svelte' import { deleteMessage, deleteSummaryMessage, truncateFromMessage, submitExitingPromptsNow, continueMessage, updateMessages } from './Storage.svelte'
import { getPrice } from './Stats.svelte' import { getPrice } from './Stats.svelte'
import SvelteMarkdown from 'svelte-markdown' import SvelteMarkdown from 'svelte-markdown'
import type { Message, Model, Chat } from './Types.svelte' import type { Message, Model, Chat } from './Types.svelte'
import Fa from 'svelte-fa/src/fa.svelte' import Fa from 'svelte-fa/src/fa.svelte'
import { faTrash, faDiagramPredecessor, faDiagramNext, faCircleCheck, faPaperPlane, faEye, faEyeSlash, faEllipsis, faDownload, faClipboard } from '@fortawesome/free-solid-svg-icons/index' import { faTrash, faDiagramPredecessor, faDiagramNext, faCircleCheck, faPaperPlane, faEye, faEyeSlash, faEllipsis, faDownload, faClipboard, faSquareRootVariable } from '@fortawesome/free-solid-svg-icons/index'
import { errorNotice, scrollToMessage } from './Util.svelte' import { errorNotice, scrollToMessage } from './Util.svelte'
import { openModal } from 'svelte-modals' import { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte' import PromptConfirm from './PromptConfirm.svelte'
import { getImage } from './ImageStore.svelte' import { getImage } from './ImageStore.svelte'
import { getModelDetail } from './Models.svelte' import { getModelDetail } from './Models.svelte'
import 'katex/dist/katex.min.css'
export let message:Message export let message:Message
export let chatId:number export let chatId:number
export let chat:Chat export let chat:Chat
@@ -32,6 +35,12 @@
mangle: false // Do not mangle email addresses mangle: false // Do not mangle email addresses
} }
const renderers = {
code: Code,
html: Code,
codespan: Codespan
}
const getDisplayMessage = ():string => { const getDisplayMessage = ():string => {
const content = message.content const content = message.content
if (isSystem && chatSettings.hideSystemPrompt) { if (isSystem && chatSettings.hideSystemPrompt) {
@@ -214,6 +223,57 @@
document.body.removeChild(a) document.body.removeChild(a)
} }
const preprocessMath = (text: string): string => {
let codeBlockPlaceholderPrefix = '__prefix__c0d3b10ck__'
while (text.indexOf(codeBlockPlaceholderPrefix) > 0) {
codeBlockPlaceholderPrefix = codeBlockPlaceholderPrefix + '_'
}
let index = 0
const codeBlocks = []
const codeBlockRegex = /(```[\s\S]*?```|`[^`]*`)/g
text = text.replace(codeBlockRegex, (match) => {
const placeholder = `${codeBlockPlaceholderPrefix}idx${index}__`
codeBlocks.push(match)
index++
return placeholder
})
text = text
.replace(/(\\\[((?:\s|\S)*?)\\\])|(\$\$((?:\s|\S)*?)\$\$)/g, (match, p1, p2, p3, p4) => {
const math = p2 || p4
return '\n```rendermath\n' + math.trim() + '\n```\n'
})
.replace(/(\\\((?!\$)(.*?)\\\))|(?<!\\|\$)\$(?!\$)(.*?[^\\])\$(?!\$)/g, (match, p1, p2, p3) => {
const math = p2 || p3
return '`rendermath' + math.trim() + '`'
})
// .replace(/\\\[((?:\s|\S)*?)\\\]/g, (match, math) => {
// return '\n```rendermath\n' + math.trim() + '\n```\n'
// })
// .replace(/\$\$((?:\s|\S)*?)\$\$/g, (match, math) => {
// return '\n```rendermath\n' + math.trim() + '\n```\n'
// })
// .replace(/\\\((?!\$)(.*?[^\\])\\\)/g, (match, math) => {
// return '`rendermath' + math.trim() + '`'
// })
// .replace(/(?<!\\|\$)\$(?!\$)(.*?[^\\])\$(?!\$)/g, (match, math) => {
// return '`rendermath' + math.trim() + '`'
// })
text = text.replace(new RegExp(`${codeBlockPlaceholderPrefix}idx(\\d+)__`, 'g'), (match, p1) => {
return codeBlocks[p1]
})
return text
}
const renderMathMsg = () => {
displayMessage = preprocessMath(message.content);
};
</script> </script>
<article <article
@@ -253,9 +313,9 @@
{/if} {/if}
{#key refreshCounter} {#key refreshCounter}
<SvelteMarkdown <SvelteMarkdown
source={displayMessage} bind:source={displayMessage}
options={markdownOptions} options={markdownOptions}
renderers={{ code: Code, html: Code }} renderers={renderers}
/> />
{/key} {/key}
{#if imageUrl} {#if imageUrl}
@@ -371,6 +431,16 @@
<span class="icon"><Fa icon={faClipboard} /></span> <span class="icon"><Fa icon={faClipboard} /></span>
</a> </a>
{/if} {/if}
<a
href={'#'}
title="Render LaTeX in message"
class="button is-small"
on:click|preventDefault={() => {
renderMathMsg()
}}
>
<span class="icon"><Fa icon={faSquareRootVariable} /></span>
</a>
{#if imageUrl} {#if imageUrl}
<a <a
href={'#'} href={'#'}