summaryrefslogtreecommitdiff
path: root/src/ollama_web/static
diff options
context:
space:
mode:
Diffstat (limited to 'src/ollama_web/static')
-rw-r--r--src/ollama_web/static/app.js67
-rw-r--r--src/ollama_web/static/style.css50
2 files changed, 72 insertions, 45 deletions
diff --git a/src/ollama_web/static/app.js b/src/ollama_web/static/app.js
index b2cd08b..845a71f 100644
--- a/src/ollama_web/static/app.js
+++ b/src/ollama_web/static/app.js
@@ -53,41 +53,46 @@ function addMessage(role) {
const body = document.createElement("div");
body.className = "body";
div.append(roleEl, body);
+ let extras = null;
+ if (role === "assistant") {
+ extras = document.createElement("div");
+ extras.className = "extras";
+ div.appendChild(extras);
+ }
chatEl.appendChild(div);
chatEl.scrollTop = chatEl.scrollHeight;
- return body;
+ return { body, extras };
}
-function addToolBubble(name, args) {
- const div = document.createElement("div");
- div.className = "tool";
+function addToolBubble(parentExtras, name, args) {
+ const det = document.createElement("details");
+ det.className = "tool";
const label = (TOOLS.find((t) => t.name === name) || { label: name }).label;
- const status = document.createElement("span");
- status.className = "status";
- status.textContent = `${label} を実行中…`;
- div.appendChild(status);
+ const sum = document.createElement("summary");
+ sum.textContent = `${label} を実行中…`;
+ det.appendChild(sum);
if (args && Object.keys(args).length) {
const a = document.createElement("div");
a.className = "args";
a.textContent = JSON.stringify(args, null, 2);
- div.appendChild(a);
+ det.appendChild(a);
}
const result = document.createElement("div");
result.className = "result";
- div.appendChild(result);
- chatEl.appendChild(div);
+ det.appendChild(result);
+ parentExtras.appendChild(det);
chatEl.scrollTop = chatEl.scrollHeight;
- return { div, status, result };
+ return { det, sum, result };
}
-function addThinkingBubble() {
+function addThinkingBubble(parentExtras) {
const det = document.createElement("details");
det.className = "thinking";
const sum = document.createElement("summary");
sum.textContent = "思考プロセス";
const body = document.createElement("div");
det.append(sum, body);
- chatEl.appendChild(det);
+ parentExtras.appendChild(det);
chatEl.scrollTop = chatEl.scrollHeight;
return body;
}
@@ -119,11 +124,13 @@ async function send() {
abortController = new AbortController();
messages.push({ role: "user", content: text });
- const userBody = addMessage("user");
+ const userBody = addMessage("user").body;
userBody.innerHTML = renderMarkdown(text);
highlightIn(userBody);
- const assistantBody = addMessage("assistant");
+ const assistantMsg = addMessage("assistant");
+ const assistantBody = assistantMsg.body;
+ const assistantExtras = assistantMsg.extras;
assistantBody.textContent = "…";
let assistantText = "";
let thinkingBody = null;
@@ -162,6 +169,20 @@ async function send() {
const decoder = new TextDecoder();
let buffer = "";
+ // Remove the placeholder "…" as soon as real content arrives.
+ let placeholderCleared = false;
+
+ function appendDelta(content) {
+ if (!placeholderCleared) {
+ assistantBody.innerHTML = "";
+ placeholderCleared = true;
+ }
+ assistantText += content;
+ assistantBody.innerHTML = renderMarkdown(assistantText);
+ highlightIn(assistantBody);
+ chatEl.scrollTop = chatEl.scrollHeight;
+ }
+
while (true) {
const { done, value } = await reader.read();
if (done) break;
@@ -183,23 +204,21 @@ async function send() {
}
if (ev.type === "tool_start") {
- const bubble = addToolBubble(ev.name, ev.arguments);
+ const bubble = addToolBubble(assistantExtras, ev.name, ev.arguments);
pendingTools.push(bubble);
} else if (ev.type === "tool_end") {
const b = pendingTools.shift();
if (b) {
- b.status.textContent = `${b.status.textContent.replace("実行中…", "完了")}`;
+ b.sum.textContent = b.sum.textContent.replace("実行中…", "完了");
b.result.textContent = (ev.result || "").slice(0, 2000);
+ chatEl.scrollTop = chatEl.scrollHeight;
}
} else if (ev.type === "thinking") {
- if (!thinkingBody) thinkingBody = addThinkingBubble();
+ if (!thinkingBody) thinkingBody = addThinkingBubble(assistantExtras);
thinkingBody.textContent += ev.content;
chatEl.scrollTop = chatEl.scrollHeight;
} else if (ev.type === "delta") {
- assistantText += ev.content;
- assistantBody.innerHTML = renderMarkdown(assistantText);
- highlightIn(assistantBody);
- chatEl.scrollTop = chatEl.scrollHeight;
+ appendDelta(ev.content);
} else if (ev.type === "error") {
assistantBody.innerHTML += `<span style="color:#f85149">エラー: ${ev.message}</span>`;
} else if (ev.type === "done") {
@@ -261,4 +280,4 @@ cancelBtn.addEventListener("click", () => {
if (abortController) {
abortController.abort();
}
-});
+}); \ No newline at end of file
diff --git a/src/ollama_web/static/style.css b/src/ollama_web/static/style.css
index ece20c1..0be42c2 100644
--- a/src/ollama_web/static/style.css
+++ b/src/ollama_web/static/style.css
@@ -97,34 +97,42 @@ main#chat {
.message p { margin: 8px 0; }
.message ul, .message ol { padding-left: 22px; }
-.tool {
- max-width: 860px;
- width: 100%;
- margin: 0 auto;
- padding: 8px 12px;
+.message .extras {
+ margin-top: 10px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.message .extras details {
border-radius: 8px;
- border: 1px dashed var(--border);
- background: var(--tool-bg);
- color: var(--muted);
+ border: 1px solid var(--border);
+ padding: 6px 10px;
font-size: 13px;
+ color: var(--muted);
+}
+
+.message .extras details summary {
+ cursor: pointer;
+ color: var(--accent);
+ font-weight: 600;
}
-.tool .name { color: var(--accent); font-weight: 600; }
-.tool .args, .tool .result { color: var(--muted); white-space: pre-wrap; word-break: break-word; }
-.tool details summary { cursor: pointer; color: var(--accent); }
+.message .extras details .args,
+.message .extras details .result {
+ white-space: pre-wrap;
+ word-break: break-word;
+ margin-top: 6px;
+}
-.thinking {
- max-width: 860px;
- width: 100%;
- margin: 0 auto;
- padding: 8px 12px;
- border-radius: 8px;
- border: 1px solid var(--border);
+.message .extras details.tool {
+ background: var(--tool-bg);
+ border-style: dashed;
+}
+
+.message .extras details.thinking {
background: var(--think-bg);
- color: var(--muted);
- font-size: 13px;
}
-.thinking summary { cursor: pointer; color: var(--accent); }
form#input-form {
display: flex;