feat: add thinking indicator while model is generating

- src/types.ts: Add isThinking flag to ChatMessage to track transient
  'model is working' state.

- src/chat-view.ts: Set isThinking: true on the assistant placeholder
  message when user sends input. Clear it when the first stream chunk
  arrives or on error. Update render() to show a spinner + 'Thinking…'
  text while isThinking is active.

- styles.css: Add ollama-thinking-indicator class with a CSS spinner
  animation and muted italic text styling.
This commit is contained in:
2026-05-19 21:21:47 +02:00
parent 810676ff21
commit 70bf963f28
4 changed files with 56 additions and 7 deletions
+16 -5
View File
@@ -9059,7 +9059,11 @@ var ChatView = class extends import_obsidian3.ItemView {
if (existingEl) {
const contentEl = existingEl.querySelector(".ollama-message-content");
if (contentEl) {
contentEl.textContent = msg.content;
if (msg.isThinking) {
contentEl.innerHTML = '<span class="ollama-thinking-indicator">Thinking\u2026</span>';
} else {
contentEl.textContent = msg.content;
}
}
} else {
const messageEl = container.createEl("div", {
@@ -9069,7 +9073,11 @@ var ChatView = class extends import_obsidian3.ItemView {
const headerEl = messageEl.createEl("div", { cls: "ollama-message-header" });
headerEl.createEl("span", { cls: "ollama-message-role", text: msg.role });
const contentEl = messageEl.createEl("div", { cls: "ollama-message-content" });
contentEl.textContent = msg.content;
if (msg.isThinking) {
contentEl.innerHTML = '<span class="ollama-thinking-indicator">Thinking\u2026</span>';
} else {
contentEl.textContent = msg.content;
}
}
}
const streamingMessage = messagesSnapshot.find((msg) => msg.isStreaming);
@@ -9297,7 +9305,8 @@ var ChatView = class extends import_obsidian3.ItemView {
role: "assistant",
content: "",
timestamp: Date.now(),
isStreaming: true
isStreaming: true,
isThinking: true
};
const previousStreamingEl = this.lastMessageEl;
this.messages = [...this.messages, userChatMessage, assistantMessage];
@@ -9334,7 +9343,8 @@ ${userMessage}` : userMessage;
fullResponse += chunk.content;
this.updateLastMessage({
content: fullResponse,
isStreaming: true
isStreaming: true,
isThinking: false
});
}
if (chunk.tool_calls) {
@@ -9373,7 +9383,8 @@ ${userMessage}` : userMessage;
ErrorHandler.handleError(error, "ChatView.handleUserInput");
this.updateMessageById(assistantMessageId, {
content: "An error occurred while processing your request.",
isStreaming: false
isStreaming: false,
isThinking: false
});
} finally {
this.cleanupStreamingResources();
+13 -2
View File
@@ -146,7 +146,11 @@ export class ChatView extends ItemView {
if (existingEl) {
const contentEl = existingEl.querySelector('.ollama-message-content');
if (contentEl) {
contentEl.textContent = msg.content;
if (msg.isThinking) {
contentEl.innerHTML = '<span class="ollama-thinking-indicator">Thinking…</span>';
} else {
contentEl.textContent = msg.content;
}
}
} else {
const messageEl = container.createEl('div', {
@@ -158,7 +162,11 @@ export class ChatView extends ItemView {
headerEl.createEl('span', { cls: 'ollama-message-role', text: msg.role });
const contentEl = messageEl.createEl('div', { cls: 'ollama-message-content' });
contentEl.textContent = msg.content;
if (msg.isThinking) {
contentEl.innerHTML = '<span class="ollama-thinking-indicator">Thinking…</span>';
} else {
contentEl.textContent = msg.content;
}
}
}
@@ -437,6 +445,7 @@ export class ChatView extends ItemView {
content: '',
timestamp: Date.now(),
isStreaming: true,
isThinking: true,
};
const previousStreamingEl = this.lastMessageEl;
@@ -486,6 +495,7 @@ export class ChatView extends ItemView {
this.updateLastMessage({
content: fullResponse,
isStreaming: true,
isThinking: false,
});
}
@@ -536,6 +546,7 @@ export class ChatView extends ItemView {
this.updateMessageById(assistantMessageId, {
content: 'An error occurred while processing your request.',
isStreaming: false,
isThinking: false,
});
} finally {
// Clean up streaming resources regardless of outcome
+1
View File
@@ -141,6 +141,7 @@ export interface ChatMessage {
content: string;
timestamp: number;
isStreaming?: boolean;
isThinking?: boolean;
tool_calls?: OllamaToolCall[];
// Refinement tracking
originalQuery?: string;
+26
View File
@@ -149,3 +149,29 @@
.ollama-send-button:active {
filter: brightness(0.95);
}
/* Thinking indicator */
.ollama-thinking-indicator {
display: inline-flex;
align-items: center;
gap: var(--size-4-1);
font-style: italic;
color: var(--text-muted);
}
.ollama-thinking-indicator::before {
content: '';
display: inline-block;
width: 0.9em;
height: 0.9em;
border: 2px solid var(--text-muted);
border-top-color: transparent;
border-radius: 50%;
animation: ollama-spin 1s linear infinite;
}
@keyframes ollama-spin {
to {
transform: rotate(360deg);
}
}