Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add KaTeX rendering #447

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
#VITE_API_BASE=http://localhost:5174
#VITE_ENDPOINT_COMPLETIONS=/v1/chat/completions
#VITE_ENDPOINT_MODELS=/v1/models
#VITE_RENDER_LATEX=false
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

yarn.lock
node_modules
dist
dist-ssr
Expand Down
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"eslint-plugin-svelte3": "^4.0.0",
"flourite": "^1.2.4",
"gpt-tokenizer": "^2.1.2",
"katex": "^0.16.10",
"llama-tokenizer-js": "^1.1.3",
"postcss": "^8.4.32",
"sass": "^1.69.7",
Expand Down
26 changes: 22 additions & 4 deletions src/lib/Code.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,26 @@
type LanguageType
} from 'svelte-highlight/languages/index'

import katex from 'katex'
import 'katex/contrib/mhchem'

export const type: 'code' = 'code'
export const raw: string = ''
export const codeBlockStyle: 'indented' | undefined = undefined
export let lang: string | undefined
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
let language: LanguageType<string>

Expand Down Expand Up @@ -117,7 +131,11 @@
{@html style}
</svelte:head>

<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 lang === 'rendermath'}
{@html renderedMath}
{:else}
<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 changes: 20 additions & 0 deletions src/lib/Codespan.svelte
Original file line number Diff line number Diff line change
@@ -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}
76 changes: 73 additions & 3 deletions src/lib/EditMessage.svelte
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
<script lang="ts">
import Code from './Code.svelte'
import Codespan from './Codespan.svelte'
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
import { deleteMessage, deleteSummaryMessage, truncateFromMessage, submitExitingPromptsNow, continueMessage, updateMessages } from './Storage.svelte'
import { getPrice } from './Stats.svelte'
import SvelteMarkdown from 'svelte-markdown'
import type { Message, Model, Chat } from './Types.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 { openModal } from 'svelte-modals'
import PromptConfirm from './PromptConfirm.svelte'
import { getImage } from './ImageStore.svelte'
import { getModelDetail } from './Models.svelte'

import 'katex/dist/katex.min.css'

export let message:Message
export let chatId:number
export let chat:Chat
Expand All @@ -31,6 +34,12 @@
breaks: true, // Enable line breaks in markdown
mangle: false // Do not mangle email addresses
}

const renderers = {
code: Code,
html: Code,
codespan: Codespan
}

const getDisplayMessage = ():string => {
const content = message.content
Expand Down Expand Up @@ -214,6 +223,57 @@
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) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like something that is brittle and can break lots of other cases. Is there not a better way to detect latex?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought so, but looking at KaTeX's own AutoRender, they do does delimeter detecting simmilarly.. so I thought using simple regex is fine.

I agree with that it might break easily and there should be more solid way, but as far as I see, almost every LaTeX detector uses simmilar regex.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disabled rendering at default and added button to render afterwards if needed.

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>

<article
Expand Down Expand Up @@ -253,9 +313,9 @@
{/if}
{#key refreshCounter}
<SvelteMarkdown
source={displayMessage}
bind:source={displayMessage}
options={markdownOptions}
renderers={{ code: Code, html: Code }}
renderers={renderers}
/>
{/key}
{#if imageUrl}
Expand Down Expand Up @@ -371,6 +431,16 @@
<span class="icon"><Fa icon={faClipboard} /></span>
</a>
{/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}
<a
href={'#'}
Expand Down