diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-06-24 21:48:56 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-06-24 21:48:56 +0900 |
| commit | a1d744387659f7fe29559017ae03dc76e8532bb1 (patch) | |
| tree | 1b1ad8c352afd6e16b8ed4ee176269f61f615488 /src | |
| parent | f7e2894856c10d8a1e2d429a7858d9f143c42404 (diff) | |
数式の出力に対応
Diffstat (limited to 'src')
| -rw-r--r-- | src/ollama_web/static/app.js | 59 | ||||
| -rw-r--r-- | src/ollama_web/static/style.css | 9 | ||||
| -rw-r--r-- | src/ollama_web/templates/index.html | 2 |
3 files changed, 68 insertions, 2 deletions
diff --git a/src/ollama_web/static/app.js b/src/ollama_web/static/app.js index f7a46a2..864f69e 100644 --- a/src/ollama_web/static/app.js +++ b/src/ollama_web/static/app.js @@ -28,11 +28,44 @@ function simpleMarkdownToHtml(text) { return el.outerHTML; } +function protectMath(text) { + const placeholders = []; + const store = (display, latex) => { + const id = `MATH_PLACEHOLDER_${placeholders.length}_`; + placeholders.push({ id, display, latex }); + return id; + }; + const processed = text + .replace(/(?<!\$)\$\$([\s\S]+?)\$\$(?!\$)/g, (_, latex) => store(true, latex)) + .replace(/(?<!\$)\$((?:\\.|[^\\\n$])+?)\$(?!\$)/g, (_, latex) => store(false, latex)) + .replace(/(?<!\S)\\\[([\s\S]+?)\\\](?!\S)/g, (_, latex) => store(true, latex)) + .replace(/(?<!\S)\\\(((?:\\.|[^\\\n])+?)\\\)(?!\S)/g, (_, latex) => store(false, latex)); + return { text: processed, placeholders }; +} + +function escapeHtmlMath(latex) { + return latex + .replace(/&/g, String.fromCharCode(38, 97, 109, 112, 59)) + .replace(/</g, String.fromCharCode(38, 108, 116, 59)) + .replace(/>/g, String.fromCharCode(38, 103, 116, 59)); +} + +function restoreMath(html, placeholders) { + let out = html; + for (const { id, display, latex } of placeholders) { + const tag = display ? "div" : "span"; + const cls = display ? "math-block" : "math-inline"; + out = out.split(id).join(`<${tag} class="${cls}">${escapeHtmlMath(latex)}</${tag}>`); + } + return out; +} + function renderMarkdown(text) { if (typeof marked !== "undefined" && marked.parse) { try { - const html = marked.parse(text); - if (typeof html === "string") return html; + const { text: protectedText, placeholders } = protectMath(text); + const html = marked.parse(protectedText); + if (typeof html === "string") return restoreMath(html, placeholders); } catch { // fall through } @@ -40,6 +73,24 @@ function renderMarkdown(text) { return simpleMarkdownToHtml(text); } +function renderMathIn(el) { + if (typeof katex === "undefined" || !katex.render) return; + el.querySelectorAll(".math-inline").forEach((node) => { + try { + katex.render(node.textContent, node, { throwOnError: false, displayMode: false }); + } catch { + /* ignore */ + } + }); + el.querySelectorAll(".math-block").forEach((node) => { + try { + katex.render(node.textContent, node, { throwOnError: false, displayMode: true }); + } catch { + /* ignore */ + } + }); +} + function highlightIn(el) { if (typeof hljs !== "undefined" && hljs.highlightElement) { el.querySelectorAll("pre code").forEach((block) => { @@ -242,6 +293,7 @@ function _renderAssistantContent(body, content) { } else { body.innerHTML = renderMarkdown(content); highlightIn(body); + renderMathIn(body); } } @@ -255,6 +307,7 @@ function renderSession() { const content = m.content || ""; body.innerHTML = renderMarkdown(content); highlightIn(body); + renderMathIn(body); if (m.attachments && m.attachments.length) { const info = document.createElement("div"); info.style.fontSize = "12px"; @@ -444,6 +497,7 @@ async function send() { assistantText += content; assistantBody.innerHTML = renderMarkdown(assistantText); highlightIn(assistantBody); + renderMathIn(assistantBody); chatEl.scrollTop = chatEl.scrollHeight; } @@ -539,6 +593,7 @@ async function send() { if (assistantText) { assistantBody.innerHTML = renderMarkdown(assistantText); highlightIn(assistantBody); + renderMathIn(assistantBody); } } diff --git a/src/ollama_web/static/style.css b/src/ollama_web/static/style.css index 9fb37ea..1c7b7c9 100644 --- a/src/ollama_web/static/style.css +++ b/src/ollama_web/static/style.css @@ -365,6 +365,15 @@ main#chat { } .message code { font-family: "SFMono-Regular", Consolas, monospace; font-size: 13px; } + +.message .math-block { + margin: 12px 0; + overflow-x: auto; +} + +.message .math-inline { + display: inline-block; +} .message p { margin: 8px 0; } .message ul, .message ol { padding-left: 22px; } diff --git a/src/ollama_web/templates/index.html b/src/ollama_web/templates/index.html index 1f4245a..c0eb30c 100644 --- a/src/ollama_web/templates/index.html +++ b/src/ollama_web/templates/index.html @@ -6,8 +6,10 @@ <title>ollama-web</title> <link rel="stylesheet" href="{{ url_for('static', path='style.css') }}" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" /> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.0/marked.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.js"></script> </head> <body> <aside id="sidebar"> |
