Add model change callback and improve chat UI robustness
- Persist selected model to settings when changed via dropdown - Reorder chat controls: model selector before mode selector, new chat before delete - Add null guards for modelSelectorEl during async populate - Fix streaming state and empty response handling in tool nudge loop - Expand vault operation keyword detection - Enable text selection in chat messages - Include original error message in chat error display
This commit is contained in:
+67
-32
@@ -57,7 +57,8 @@ export class ChatView extends ItemView {
|
||||
vectorStore?: VaultVectorStore,
|
||||
structuredMemoryManager?: StructuredMemoryManager,
|
||||
telemetryManager?: TelemetryManager,
|
||||
chatHistoryManager?: ChatHistoryManager
|
||||
chatHistoryManager?: ChatHistoryManager,
|
||||
onModelChange?: (model: string) => void
|
||||
) {
|
||||
super(leaf);
|
||||
this.messages = [];
|
||||
@@ -96,6 +97,7 @@ export class ChatView extends ItemView {
|
||||
this.structuredMemoryManager = structuredMemoryManager;
|
||||
this.telemetryManager = telemetryManager;
|
||||
this.chatHistoryManager = chatHistoryManager;
|
||||
this.onModelChange = onModelChange;
|
||||
this.workflowEngine = new WorkflowEngine(
|
||||
this.app.vault,
|
||||
this.app,
|
||||
@@ -129,6 +131,8 @@ export class ChatView extends ItemView {
|
||||
(newSettings.agentModel ?? newSettings.model) === (newSettings.chatModel ?? newSettings.model)
|
||||
? this.ollamaClient
|
||||
: this.createOllamaClient(newSettings.agentModel ?? newSettings.model, newSettings);
|
||||
// Refresh model dropdown so it reflects the newly saved model
|
||||
void this.populateModelDropdown();
|
||||
this.workflowEngine = new WorkflowEngine(
|
||||
this.app.vault,
|
||||
this.app,
|
||||
@@ -202,6 +206,7 @@ export class ChatView extends ItemView {
|
||||
this.chatContainer = null;
|
||||
this.showLogsButton = null;
|
||||
this.logsContainer = null;
|
||||
this.modelSelectorEl = null;
|
||||
this.historySelectEl = null;
|
||||
this.historyDeleteButton = null;
|
||||
return Promise.resolve();
|
||||
@@ -280,6 +285,23 @@ export class ChatView extends ItemView {
|
||||
}
|
||||
}
|
||||
|
||||
// Setup model selector
|
||||
if (!this.modelSelectorEl) {
|
||||
this.modelSelectorEl = newChatContainer.createEl('select', {
|
||||
cls: 'ollama-model-selector',
|
||||
});
|
||||
this.modelSelectorEl.addEventListener('change', () => {
|
||||
const selectedModel = this.modelSelectorEl!.value;
|
||||
this.ollamaClient.setModel(selectedModel);
|
||||
if (this.agentOllamaClient !== this.ollamaClient) {
|
||||
this.agentOllamaClient.setModel(selectedModel);
|
||||
}
|
||||
this.onModelChange?.(selectedModel);
|
||||
});
|
||||
}
|
||||
this.populateModelDropdown();
|
||||
newChatContainer.appendChild(this.modelSelectorEl);
|
||||
|
||||
// Setup mode selector
|
||||
if (!this.modeSelectorEl) {
|
||||
this.modeSelectorEl = newChatContainer.createEl('select', {
|
||||
@@ -301,22 +323,6 @@ export class ChatView extends ItemView {
|
||||
newChatContainer.appendChild(this.modeSelectorEl);
|
||||
}
|
||||
|
||||
// Setup model selector
|
||||
if (!this.modelSelectorEl) {
|
||||
this.modelSelectorEl = newChatContainer.createEl('select', {
|
||||
cls: 'ollama-model-selector',
|
||||
});
|
||||
this.modelSelectorEl.addEventListener('change', () => {
|
||||
const selectedModel = this.modelSelectorEl!.value;
|
||||
this.ollamaClient.setModel(selectedModel);
|
||||
if (this.agentOllamaClient !== this.ollamaClient) {
|
||||
this.agentOllamaClient.setModel(selectedModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.populateModelDropdown();
|
||||
newChatContainer.appendChild(this.modelSelectorEl);
|
||||
|
||||
// Setup chat history selector
|
||||
if (!this.historySelectEl) {
|
||||
this.historySelectEl = newChatContainer.createEl('select', {
|
||||
@@ -339,6 +345,16 @@ export class ChatView extends ItemView {
|
||||
this.populateHistoryDropdown();
|
||||
newChatContainer.appendChild(this.historySelectEl);
|
||||
|
||||
// Setup new chat button
|
||||
if (!this.newChatButton) {
|
||||
this.newChatButton = newChatContainer.createEl('button', {
|
||||
cls: 'ollama-new-chat-button',
|
||||
text: 'New Chat',
|
||||
});
|
||||
} else {
|
||||
newChatContainer.appendChild(this.newChatButton);
|
||||
}
|
||||
|
||||
// Setup delete history button
|
||||
if (!this.historyDeleteButton) {
|
||||
this.historyDeleteButton = newChatContainer.createEl('button', {
|
||||
@@ -358,16 +374,6 @@ export class ChatView extends ItemView {
|
||||
}
|
||||
newChatContainer.appendChild(this.historyDeleteButton);
|
||||
|
||||
// Setup new chat button
|
||||
if (!this.newChatButton) {
|
||||
this.newChatButton = newChatContainer.createEl('button', {
|
||||
cls: 'ollama-new-chat-button',
|
||||
text: 'New Chat',
|
||||
});
|
||||
} else {
|
||||
newChatContainer.appendChild(this.newChatButton);
|
||||
}
|
||||
|
||||
// Setup show-logs toggle button
|
||||
if (!this.showLogsButton) {
|
||||
this.showLogsButton = newChatContainer.createEl('button', {
|
||||
@@ -641,6 +647,7 @@ export class ChatView extends ItemView {
|
||||
this.modelSelectorEl.innerHTML = '';
|
||||
|
||||
const models = await this.ollamaClient.listModels();
|
||||
if (!this.modelSelectorEl) return; // Guard: view may have closed while fetching
|
||||
const currentModel = this.ollamaClient.getModel();
|
||||
|
||||
if (models.length === 0) {
|
||||
@@ -654,6 +661,7 @@ export class ChatView extends ItemView {
|
||||
}
|
||||
|
||||
for (const model of models) {
|
||||
if (!this.modelSelectorEl) return;
|
||||
const displayName = model.name;
|
||||
const option = this.modelSelectorEl.createEl('option', {
|
||||
text: displayName,
|
||||
@@ -664,6 +672,7 @@ export class ChatView extends ItemView {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.modelSelectorEl) return;
|
||||
// If the previously selected value is still valid, keep it
|
||||
if (previousValue && models.some((m) => m.name === previousValue)) {
|
||||
this.modelSelectorEl.value = previousValue;
|
||||
@@ -1468,11 +1477,11 @@ export class ChatView extends ItemView {
|
||||
if (modelMentionedActions || userWantsVaultOps) {
|
||||
shouldFallbackToReadTools = true;
|
||||
|
||||
// Suppress the model's "Let me..." text — replace with actual tool execution
|
||||
// Suppress the model's "Let me..." text — clear it from the DOM immediately
|
||||
fullResponse = '';
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
content: '',
|
||||
isStreaming: true,
|
||||
isStreaming: false,
|
||||
isThinking: false,
|
||||
});
|
||||
|
||||
@@ -1488,9 +1497,12 @@ export class ChatView extends ItemView {
|
||||
attempts === 1 ? 'Thinking…' : `Retrying (${attempts}/${maxAttempts})…`
|
||||
);
|
||||
|
||||
// Build nudge messages — skip empty assistant content to avoid API issues
|
||||
const nudgeMessages: OllamaMessage[] = [
|
||||
...currentMessages,
|
||||
{ role: 'assistant', content: currentResponse },
|
||||
...(currentResponse.trim()
|
||||
? [{ role: 'assistant' as const, content: currentResponse }]
|
||||
: []),
|
||||
{
|
||||
role: 'user',
|
||||
content:
|
||||
@@ -1553,7 +1565,7 @@ export class ChatView extends ItemView {
|
||||
// Update assistant message — only if no tool calls were processed
|
||||
if (toolCalls.length === 0) {
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
content: fullResponse,
|
||||
content: fullResponse || '(No response)',
|
||||
isStreaming: false,
|
||||
isThinking: false,
|
||||
});
|
||||
@@ -1607,8 +1619,9 @@ export class ChatView extends ItemView {
|
||||
this.syncMessagesToSession();
|
||||
} catch (error) {
|
||||
ErrorHandler.handleError(error, 'ChatView.handleUserInput');
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
content: 'An error occurred while processing your request.',
|
||||
content: `An error occurred: ${errorMsg}`,
|
||||
isStreaming: false,
|
||||
isThinking: false,
|
||||
});
|
||||
@@ -1676,6 +1689,27 @@ export class ChatView extends ItemView {
|
||||
'vault',
|
||||
'notes',
|
||||
'files',
|
||||
'continue',
|
||||
'go ahead',
|
||||
'start',
|
||||
'do it',
|
||||
'execute',
|
||||
'run',
|
||||
'proceed',
|
||||
'next',
|
||||
'now',
|
||||
'yes',
|
||||
'ok',
|
||||
'okay',
|
||||
'sure',
|
||||
'please',
|
||||
'step',
|
||||
'merge',
|
||||
'clean',
|
||||
'fix',
|
||||
'update',
|
||||
'implement',
|
||||
'apply',
|
||||
];
|
||||
return operationPhrases.some((phrase) => lower.includes(phrase));
|
||||
}
|
||||
@@ -1772,6 +1806,7 @@ export class ChatView extends ItemView {
|
||||
private telemetryManager?: TelemetryManager;
|
||||
private chatHistoryManager?: ChatHistoryManager;
|
||||
private vectorStore?: VaultVectorStore;
|
||||
private onModelChange?: (model: string) => void;
|
||||
|
||||
private modeSelectorEl: HTMLSelectElement | null = null;
|
||||
private modelSelectorEl: HTMLSelectElement | null = null;
|
||||
|
||||
Reference in New Issue
Block a user