summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-06-24 21:48:56 +0900
committertanaka takashi <isazaka@gmail.com>2026-06-24 21:48:56 +0900
commita1d744387659f7fe29559017ae03dc76e8532bb1 (patch)
tree1b1ad8c352afd6e16b8ed4ee176269f61f615488
parentf7e2894856c10d8a1e2d429a7858d9f143c42404 (diff)
数式の出力に対応
-rw-r--r--src/ollama_web/static/app.js59
-rw-r--r--src/ollama_web/static/style.css9
-rw-r--r--src/ollama_web/templates/index.html2
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">