mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
refactor: improve path sanitization and remove unused chat area code
- Remove unused conversationKey and incomplete message sorting logic - Add stack limit protection to conversation naming loop - Enhance UTF-8 truncation to preserve character boundaries - Add empty segment fallback and improve documentation - Fix VaultService.exists to respect access restrictions
This commit is contained in:
parent
4a42b1f84c
commit
23721246bd
4 changed files with 43 additions and 14 deletions
|
|
@ -18,13 +18,10 @@
|
|||
export let currentStreamingMessageId: string | null = null;
|
||||
export let editModeActive: boolean = false;
|
||||
|
||||
let conversationKey = 0;
|
||||
|
||||
export function resetChatArea() {
|
||||
messageElements = [];
|
||||
lastProcessedContent.clear();
|
||||
currentStreamFinalized = false;
|
||||
conversationKey++; // Force complete re-render
|
||||
if (chatAreaPaddingElement) {
|
||||
chatAreaPaddingElement.style.padding = "0px";
|
||||
}
|
||||
|
|
@ -35,13 +32,13 @@
|
|||
tick().then(() => {
|
||||
settled = false;
|
||||
|
||||
const lastMessage = messageElements.sort((a, b) => a.index - b.index).last();
|
||||
if (!lastMessage || !chatAreaPaddingElement) {
|
||||
if (messageElements.length === 0 || !chatAreaPaddingElement) {
|
||||
if (chatAreaPaddingElement) {
|
||||
chatAreaPaddingElement.style.padding = "0px";
|
||||
}
|
||||
return;
|
||||
}
|
||||
messageElements.sort((a, b) => a.index - b.index)[messageElements.length - 1];
|
||||
|
||||
const gap = parseFloat(getComputedStyle(chatContainer).gap) || 0;
|
||||
const paddingTop = parseFloat(getComputedStyle(chatContainer).paddingTop) || 0;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import type { VaultService } from "./VaultService";
|
|||
import { Path } from "Enums/Path";
|
||||
|
||||
export class ConversationNamingService {
|
||||
private readonly stackLimit: number = 1000;
|
||||
|
||||
private namingProvider: IConversationNamingService | undefined;
|
||||
private conversationService: ConversationFileSystemService;
|
||||
private vaultService: VaultService;
|
||||
|
|
@ -60,6 +62,10 @@ export class ConversationNamingService {
|
|||
while (this.vaultService.exists(`${Path.Conversations}/${availableTitle}.json`, true)) {
|
||||
availableTitle = `${cleanedTitle}(${index})`;
|
||||
index++;
|
||||
|
||||
if (index > this.stackLimit) {
|
||||
throw new Error(`Stack limit reached when trying to generate conversation name for "${cleanedTitle}"`);
|
||||
}
|
||||
}
|
||||
return availableTitle;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class SanitiserService {
|
|||
private readonly windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
private readonly windowsTrailingRe = /[\. ]+$/;
|
||||
|
||||
constructor() {}
|
||||
constructor() { }
|
||||
|
||||
/**
|
||||
* Sanitizes a file path with directories, removing illegal characters and ensuring cross-platform compatibility
|
||||
|
|
@ -37,7 +37,8 @@ export class SanitiserService {
|
|||
const driveLetter = driveMatch ? driveMatch[1] : '';
|
||||
|
||||
// Split by both forward and back slashes
|
||||
// This handles mixed separators like: /home/user\docs/file.txt
|
||||
// Note: Forward slashes and backslashes are treated as path separators,
|
||||
// not as illegal characters within segments. This is intentional for cross-platform compatibility.
|
||||
let segments = input.split(/[\\\/]+/);
|
||||
|
||||
// Remove empty segments (from leading/trailing slashes or multiple consecutive slashes)
|
||||
|
|
@ -92,6 +93,9 @@ export class SanitiserService {
|
|||
|
||||
/**
|
||||
* Sanitizes a single path segment (filename or directory name)
|
||||
* @param segment - The path segment to sanitize
|
||||
* @param replacement - Character to replace illegal characters with
|
||||
* @returns Sanitized segment, or a fallback value if the result would be empty
|
||||
*/
|
||||
private sanitizeSegment(segment: string, replacement: string): string {
|
||||
if (!segment || segment === '') {
|
||||
|
|
@ -105,26 +109,48 @@ export class SanitiserService {
|
|||
.replace(this.windowsReservedRe, replacement)
|
||||
.replace(this.windowsTrailingRe, replacement);
|
||||
|
||||
// Handle case where sanitization results in an empty string
|
||||
// This can happen with names like "...", "CON", or strings containing only illegal characters
|
||||
if (sanitized === '') {
|
||||
sanitized = 'unnamed';
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates a string to a maximum byte length while preserving UTF-8 character integrity
|
||||
* This method ensures that multi-byte UTF-8 characters are not cut in the middle,
|
||||
* which would result in invalid UTF-8 sequences.
|
||||
*
|
||||
* @param str - String to truncate
|
||||
* @param maxBytes - Maximum byte length
|
||||
* @returns Truncated string
|
||||
* @returns Truncated string with valid UTF-8 encoding
|
||||
*/
|
||||
private truncateToByteLength(str: string, maxBytes: number): string {
|
||||
const encoder = new TextEncoder();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
|
||||
const encoded = encoder.encode(str);
|
||||
|
||||
if (encoded.length <= maxBytes) {
|
||||
return str;
|
||||
}
|
||||
|
||||
const truncated = encoded.slice(0, maxBytes);
|
||||
return decoder.decode(truncated);
|
||||
// Truncate at maxBytes, then work backwards to find a valid UTF-8 boundary
|
||||
// UTF-8 continuation bytes start with 10xxxxxx (0x80-0xBF)
|
||||
let truncateAt = maxBytes;
|
||||
while (truncateAt > 0 && (encoded[truncateAt] & 0xC0) === 0x80) {
|
||||
truncateAt--;
|
||||
}
|
||||
|
||||
// Decode with fatal mode to ensure we get a valid UTF-8 string
|
||||
// If decoding fails (which shouldn't happen with our boundary logic), fall back to safe decode
|
||||
try {
|
||||
const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
||||
return decoder.decode(encoded.slice(0, truncateAt));
|
||||
} catch {
|
||||
// Fallback: use non-fatal mode if something unexpected happens
|
||||
const decoder = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true });
|
||||
return decoder.decode(encoded.slice(0, truncateAt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ export class VaultService {
|
|||
console.error(`Plugin attempted to access a file that is in the exclusions list: ${filePath}`);
|
||||
return false;
|
||||
}
|
||||
return this.vault.getAbstractFileByPath(filePath) instanceof TFile;
|
||||
return this.getAbstractFileByPath(filePath, allowAccessToPluginRoot) instanceof TFile;
|
||||
}
|
||||
|
||||
public async read(file: TFile, allowAccessToPluginRoot: boolean = false): Promise<string> {
|
||||
|
|
|
|||
Loading…
Reference in a new issue