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:
@@ -9059,8 +9059,12 @@ var ChatView = class extends import_obsidian3.ItemView {
|
||||
if (existingEl) {
|
||||
const contentEl = existingEl.querySelector(".ollama-message-content");
|
||||
if (contentEl) {
|
||||
if (msg.isThinking) {
|
||||
contentEl.innerHTML = '<span class="ollama-thinking-indicator">Thinking\u2026</span>';
|
||||
} else {
|
||||
contentEl.textContent = msg.content;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const messageEl = container.createEl("div", {
|
||||
cls: `ollama-message ollama-message-${msg.role}`
|
||||
@@ -9069,9 +9073,13 @@ 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" });
|
||||
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);
|
||||
if (streamingMessage && this.lastMessageEl) {
|
||||
const existingStreamingEl = container.querySelector(
|
||||
@@ -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();
|
||||
|
||||
@@ -146,8 +146,12 @@ export class ChatView extends ItemView {
|
||||
if (existingEl) {
|
||||
const contentEl = existingEl.querySelector('.ollama-message-content');
|
||||
if (contentEl) {
|
||||
if (msg.isThinking) {
|
||||
contentEl.innerHTML = '<span class="ollama-thinking-indicator">Thinking…</span>';
|
||||
} else {
|
||||
contentEl.textContent = msg.content;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const messageEl = container.createEl('div', {
|
||||
cls: `ollama-message ollama-message-${msg.role}`,
|
||||
@@ -158,9 +162,13 @@ export class ChatView extends ItemView {
|
||||
headerEl.createEl('span', { cls: 'ollama-message-role', text: msg.role });
|
||||
|
||||
const contentEl = messageEl.createEl('div', { cls: 'ollama-message-content' });
|
||||
if (msg.isThinking) {
|
||||
contentEl.innerHTML = '<span class="ollama-thinking-indicator">Thinking…</span>';
|
||||
} else {
|
||||
contentEl.textContent = msg.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-attach streaming message if it exists
|
||||
const streamingMessage = messagesSnapshot.find((msg) => msg.isStreaming);
|
||||
@@ -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
|
||||
|
||||
@@ -141,6 +141,7 @@ export interface ChatMessage {
|
||||
content: string;
|
||||
timestamp: number;
|
||||
isStreaming?: boolean;
|
||||
isThinking?: boolean;
|
||||
tool_calls?: OllamaToolCall[];
|
||||
// Refinement tracking
|
||||
originalQuery?: string;
|
||||
|
||||
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user