Refactor message handling in chat view and improve API error handling
Simplify message construction and update logic in chat-view.js Add abort controller support and improve error handling in ollama-client.js Remove unused sanitizeFilePath function from utils.js Export LogLevel enum in utils.js Improve vault indexing to process all batches
This commit is contained in:
+9
-9
@@ -239,14 +239,16 @@ class ChatView extends obsidian_1.ItemView {
|
||||
if (context.length > MAX_CONTEXT_LENGTH) {
|
||||
context = context.substring(0, MAX_CONTEXT_LENGTH) + '\n\n... (truncated)';
|
||||
}
|
||||
const systemContent = context
|
||||
? `You are a helpful assistant.\n\nRelevant vault context:\n${context}`
|
||||
: 'You are a helpful assistant.';
|
||||
const systemMessage = {
|
||||
role: 'system',
|
||||
content: 'You are a helpful assistant.',
|
||||
content: systemContent,
|
||||
};
|
||||
const userContent = context ? `${context}\n\n${userMessage}` : userMessage;
|
||||
const userMessageWithContext = {
|
||||
role: 'user',
|
||||
content: userContent,
|
||||
content: userMessage,
|
||||
};
|
||||
const messages = [
|
||||
systemMessage,
|
||||
@@ -368,13 +370,11 @@ class ChatView extends obsidian_1.ItemView {
|
||||
});
|
||||
}
|
||||
}
|
||||
// Update last message immutably — only if no tool calls were processed
|
||||
// Update assistant message immutably — only if no tool calls were processed
|
||||
if (toolCalls.length === 0) {
|
||||
const lastMessageIndex = this.messages.length - 1;
|
||||
if (lastMessageIndex >= 0) {
|
||||
const lastMessage = { ...this.messages[lastMessageIndex], isStreaming: false };
|
||||
this.messages = [...this.messages.slice(0, lastMessageIndex), lastMessage];
|
||||
}
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
isStreaming: false,
|
||||
});
|
||||
}
|
||||
// Limit conversation history to prevent memory issues
|
||||
if (this.messages.length > MAX_MESSAGE_HISTORY) {
|
||||
|
||||
@@ -9,4 +9,3 @@ exports.DEFAULT_SETTINGS = {
|
||||
maxMessageHistory: 50,
|
||||
lastIndexTime: 0,
|
||||
};
|
||||
// Model validation regex - lowercase letters, numbers, dashes, underscores only
|
||||
|
||||
@@ -132,6 +132,8 @@ class OllamaClient {
|
||||
return this.chatWithRetry(messages, tools, 0);
|
||||
}
|
||||
async chatWithRetry(messages, tools = [], attempt = 0) {
|
||||
this.abortController = new AbortController();
|
||||
try {
|
||||
const response = await this.fetchFn(`${this.baseURL}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -143,6 +145,7 @@ class OllamaClient {
|
||||
tools,
|
||||
stream: false,
|
||||
}),
|
||||
signal: this.abortController.signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
if (response.status >= 500 && attempt < this.maxRetries) {
|
||||
@@ -154,7 +157,11 @@ class OllamaClient {
|
||||
throw new types_1.ApiError(`Ollama API error: ${response.status}`, response.status);
|
||||
}
|
||||
const data = (await response.json());
|
||||
return this.toOllamaMessage(data.message) ?? { role: 'assistant', content: '', tool_calls: [] };
|
||||
return (this.toOllamaMessage(data.message) ?? { role: 'assistant', content: '', tool_calls: [] });
|
||||
}
|
||||
finally {
|
||||
this.abortController = null;
|
||||
}
|
||||
}
|
||||
throwIfOllamaError(parsed) {
|
||||
if (parsed.error) {
|
||||
|
||||
+34
-32
@@ -1,21 +1,17 @@
|
||||
'use strict';
|
||||
// src/utils.ts
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.Logger = void 0;
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Logger = exports.LogLevel = void 0;
|
||||
exports.validateOllamaUrl = validateOllamaUrl;
|
||||
exports.validateModelName = validateModelName;
|
||||
exports.validatePluginSettings = validatePluginSettings;
|
||||
exports.safeParseJson = safeParseJson;
|
||||
exports.sanitizeFilePath = sanitizeFilePath;
|
||||
|
||||
// ==================== Logger ====================
|
||||
var LogLevel;
|
||||
(function (LogLevel) {
|
||||
LogLevel[(LogLevel['DEBUG'] = 0)] = 'DEBUG';
|
||||
LogLevel[(LogLevel['INFO'] = 1)] = 'INFO';
|
||||
LogLevel[(LogLevel['WARN'] = 2)] = 'WARN';
|
||||
LogLevel[(LogLevel['ERROR'] = 3)] = 'ERROR';
|
||||
})(LogLevel || (LogLevel = {}));
|
||||
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
||||
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
||||
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
||||
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
||||
})(LogLevel || (exports.LogLevel = LogLevel = {}));
|
||||
const SEVERITY_ORDER = {
|
||||
debug: LogLevel.DEBUG,
|
||||
info: LogLevel.INFO,
|
||||
@@ -27,7 +23,8 @@ class Logger {
|
||||
if (typeof level === 'string') {
|
||||
const lowerLevel = level.toLowerCase();
|
||||
Logger.minLevel = SEVERITY_ORDER[lowerLevel] ?? LogLevel.DEBUG;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Logger.minLevel = level;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +66,8 @@ function validateOllamaUrl(url) {
|
||||
return { valid: false, error: 'Must be a valid HTTP or HTTPS URL' };
|
||||
}
|
||||
return { valid: true };
|
||||
} catch {
|
||||
}
|
||||
catch {
|
||||
return { valid: false, error: 'Must be a valid HTTP or HTTPS URL' };
|
||||
}
|
||||
}
|
||||
@@ -120,7 +118,8 @@ function countNestingDepth(value, depth = 0) {
|
||||
}
|
||||
if (value !== null && typeof value === 'object') {
|
||||
const entries = Object.values(value);
|
||||
if (entries.length === 0) return depth;
|
||||
if (entries.length === 0)
|
||||
return depth;
|
||||
return Math.max(...entries.map((item) => countNestingDepth(item, depth + 1)), depth);
|
||||
}
|
||||
return depth;
|
||||
@@ -135,17 +134,28 @@ function safeParseJson(jsonString) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(jsonString);
|
||||
} catch {
|
||||
}
|
||||
catch {
|
||||
throw new Error('Invalid JSON');
|
||||
}
|
||||
// Check for dangerous prototype pollution patterns
|
||||
const reStringified = JSON.stringify(parsed);
|
||||
if (
|
||||
reStringified.includes('constructor') ||
|
||||
reStringified.includes('prototype') ||
|
||||
reStringified.includes('__proto__') ||
|
||||
reStringified.includes('function')
|
||||
) {
|
||||
// Check for dangerous prototype pollution patterns in object keys only
|
||||
const checkDangerousPatterns = (obj) => {
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return false;
|
||||
}
|
||||
const dangerousKeys = ['constructor', 'prototype', '__proto__'];
|
||||
if (dangerousKeys.some((key) => Object.keys(obj).includes(key))) {
|
||||
return true;
|
||||
}
|
||||
// Recursively check nested objects
|
||||
for (const key in obj) {
|
||||
if (checkDangerousPatterns(obj[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (checkDangerousPatterns(parsed)) {
|
||||
throw new Error('dangerous code pattern detected');
|
||||
}
|
||||
// Check nesting depth
|
||||
@@ -154,12 +164,4 @@ function safeParseJson(jsonString) {
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
// ==================== Path & File Utilities ====================
|
||||
function sanitizeFilePath(path) {
|
||||
if (path.includes('..')) {
|
||||
throw new Error('Invalid path - cannot contain .. segments');
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// ==================== Markdown Utilities ====================
|
||||
|
||||
@@ -57,9 +57,6 @@ class VaultIndexer {
|
||||
}));
|
||||
const validResults = batchResults.filter((result) => result !== null);
|
||||
results.push(...validResults);
|
||||
if (results.length >= 50) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user