/** * Парсер для обработки ссылок на файлы (@/@@) и промпты (#/##) в тексте сообщений. * Поддерживает поиск активных ссылок при вводе и валидацию промптов. */ // ----------------------------------------------- Interfaces --------------------------------------------------------------- export interface ReferenceMatch { fullMatch: string; prefix: string; // Теперь может быть @, !@, #, !#, $, !$ name: string; startIndex: number; endIndex: number; type: 'file' | 'prompt' | 'external' | 'url'; permanent: boolean; } export interface FileReference { type: 'file' | 'prompt' | 'external' | 'url'; name: string; path: string; permanent: boolean; uuid?: string; } // ----------------------------------------------- Reference Parser --------------------------------------------------------------- export class ReferenceParser { /** * Парсит текст и находит все @/@@ и #/## ссылки * @param text - текст для парсинга * @returns массив найденных ссылок */ static parseReferences(text: string): ReferenceMatch[] { const matches: ReferenceMatch[] = []; const regex = /(![@#$%]|[@#$%])(?:"([^"]+)"|([^\s@#$%"!]+))/g; let match; while ((match = regex.exec(text)) !== null) { const prefix = match[1]; const quotedName = match[2]; const unquotedName = match[3]; const name = quotedName || unquotedName; // Определяем тип по первому символу префикса (после !) const typeChar = prefix.replace('!', ''); let type: 'file' | 'prompt' | 'external'| 'url'; if (typeChar === '@') type = 'file'; else if (typeChar === '#') type = 'prompt'; else if (typeChar === '$') type = 'external'; else type = 'url'; matches.push({ fullMatch: match[0], prefix: prefix, name: name, startIndex: match.index, endIndex: match.index + match[0].length, type: type, permanent: prefix.startsWith('!') }); } return matches; } /** * Проверяет, является ли позиция курсора внутри незавершённой ссылки * @param text - текст сообщения * @param cursorPosition - позиция курсора * @returns информация о текущей ссылке или null */ static getCurrentReference(text: string, cursorPosition: number): ReferenceMatch | null { const beforeCursor = text.substring(0, cursorPosition); // Измененная регулярка: теперь она понимает, если после символа # идет кавычка // Группа 1: Префикс, Группа 2: Имя в кавычках, Группа 3: Имя без кавычек const incompleteRegex = /(![@#$%]|[@#$%])(?:"([^"]*)|([^\s@#$%"!]*?))$/; const match = beforeCursor.match(incompleteRegex); if (match) { const prefix = match[1]; const nameInQuotes = match[2]; // текст внутри кавычек const nameWithoutQuotes = match[3]; // текст до первого пробела const partialName = nameInQuotes !== undefined ? nameInQuotes : nameWithoutQuotes; const startIndex = beforeCursor.length - match[0].length; const typeChar = prefix.replace('!', ''); let type: 'file' | 'prompt' | 'external' | 'url'; if (typeChar === '@') type = 'file'; else if (typeChar === '#') type = 'prompt'; else if (typeChar === '$') type = 'external'; else type = 'url'; return { fullMatch: match[0], prefix: prefix, name: partialName, startIndex: startIndex, endIndex: cursorPosition, type: type, permanent: prefix.startsWith('!') }; } return null; } /** * Валидирует промпт на корректность @{...} ссылок * @param promptContent - содержимое промпта * @returns результат валидации */ static validatePromptReferences(promptContent: string): { valid: boolean; errors: string[] } { const errors: string[] = []; // Ищем шаблонные ссылки @{...} const templateRegex = /@\{([^}]+)\}/g; const matches = promptContent.matchAll(templateRegex); for (const match of matches) { const referenceName = match[1].trim(); // Проверяем, что имя ссылки не пустое if (!referenceName) { errors.push(`Пустая ссылка в позиции ${match.index}`); continue; } // Проверяем, что имя содержит только допустимые символы if (!/^[a-zA-Z0-9_-]+$/.test(referenceName)) { errors.push(`Недопустимые символы в ссылке "${referenceName}"`); } } return { valid: errors.length === 0, errors: errors }; } /** * Извлекает все шаблонные ссылки из промпта, основываясь на заданных префиксах. * По умолчанию ищет ссылки с префиксом @{...}. * @param promptContent - содержимое промпта * @param prefixes - массив префиксов, по которым нужно искать ссылки (например, ['@'], ['#'], ['@', '#']). По умолчанию ['@']. * @returns массив имен ссылок */ static extractTemplateReferences(promptContent: string, prefixes: Array<'@' | '#'> = ['@']): string[] { const references: string[] = []; let prefixRegexPart: string; if (prefixes.includes('@') && prefixes.includes('#')) { prefixRegexPart = '[#@]'; // Ищет как @, так и # } else if (prefixes.includes('@')) { prefixRegexPart = '@'; // Ищет только @ } else if (prefixes.includes('#')) { prefixRegexPart = '#'; // Ищет только # } else { // Если пустой массив или неподдерживаемые префиксы, не ищем ничего return []; } // Регулярное выражение для поиска ссылок с динамическим префиксом const templateRegex = new RegExp(`${prefixRegexPart}\\{([^}]+)\\}`, 'g'); const matches = promptContent.matchAll(templateRegex); for (const match of matches) { const referenceName = match[1].trim(); if (referenceName && !references.includes(referenceName)) { references.push(referenceName); } } return references; } }