/** * Расширяет ссылки на файлы и промпты, заменяя их содержимым перед отправкой на сервер. * Обрабатывает как одинарные (@/#), так и постоянные (@@/##) ссылки. */ import { FileSystemAdapter, TFile } from 'obsidian'; import LLMAgentPlugin from 'main'; import { FileReference, ReferenceParser } from './ReferenceParser'; import { TemplateEngine } from './TemplateEngine'; import { Attachment, ChatHistoryItem } from 'src/components/models/ChatHistoryItem'; import { CacheManager } from './CacheManager'; import { getMimeType } from 'src/utils/fileUtils'; // ----------------------------------------------- Reference Expander --------------------------------------------------------------- export class ReferenceExpander { private plugin: LLMAgentPlugin; private templateEngine: TemplateEngine; private cacheManager: CacheManager; constructor(plugin: LLMAgentPlugin, templateEngine: TemplateEngine) { this.plugin = plugin; this.templateEngine = templateEngine; this.cacheManager = new CacheManager(plugin); } // ----------------------------------------------- Public Methods --------------------------------------------------------------- /** * Извлекает вложения из текста и кеширует файлы. * Обрабатывает как физические файлы, так и виртуальные (current-log). */ async extractAttachments( text: string, references: FileReference[] ): Promise<{ originalText: string; attachments: Attachment[]; }> { const attachments: Attachment[] = []; for (const reference of references) { try { let cachedPath: string; let uuid: string; let mimeType: string; let sourcePath: string; // --- Обработка виртуальных ссылок на логи --- if (reference.path.startsWith("virtual:current-log")) { const logsFolder = this.plugin.settings.logsFolder || "audio-logs"; // Ищем md файлы в папке логов, сортируем по дате изменения (самый свежий — первый) const logFiles = this.plugin.app.vault.getMarkdownFiles() .filter(f => f.path.startsWith(logsFolder)) .sort((a, b) => b.stat.mtime - a.stat.mtime); if (logFiles.length > 0) { const file = logFiles[0]; let content = await this.plugin.app.vault.read(file); // Если выбрана часть, отрезаем последние N символов из настроек if (reference.path.endsWith(":part")) { const limit = this.plugin.settings.voiceContextLimit; // || 30000; content = content.slice(-limit); } uuid = "log-" + Date.now(); cachedPath = `log-snapshot-${Date.now()}.md`; mimeType = "text/markdown"; sourcePath = file.path; // Сохраняем контент лога во временный файл в папку кеша, чтобы бэкенд мог его прочитать if (this.plugin.app.vault.adapter instanceof FileSystemAdapter) { const cacheFolderPath = this.cacheManager.getCacheFolderPath(); const fs = window.require('fs').promises; // Убедимся, что папка кеша существует try { await fs.mkdir(cacheFolderPath, { recursive: true }); } catch(e) {} await fs.writeFile(`${cacheFolderPath}/${cachedPath}`, content, 'utf8'); } else { throw new Error("Кеширование логов доступно только в десктопной версии"); } } else { console.warn(`Файлы логов не найдены в папке: ${logsFolder}`); continue; } } // --- Обработка внешних файлов ($ / !$) --- else if (reference.type === 'external') { const cacheResult = await this.cacheManager.cacheExternalFile(reference.path); cachedPath = cacheResult.cachedPath; uuid = cacheResult.uuid; mimeType = cacheResult.mimeType; sourcePath = reference.path; // Абсолютный } // --- Обработка файлов из Vault (@ / @@) --- else if (reference.type === 'file') { const file = this.plugin.app.vault.getAbstractFileByPath(reference.path); if (file instanceof TFile) { const cacheResult = await this.cacheManager.cacheVaultFile(file); cachedPath = cacheResult.cachedPath; uuid = cacheResult.uuid; mimeType = getMimeType(file.name); sourcePath = file.path; // Относительный } else { console.warn(`Файл не найден в vault: ${reference.path}`); continue; } } // --- Обработка промптов (# / ##) --- else { const file = this.plugin.app.vault.getAbstractFileByPath(reference.path); if (file instanceof TFile) { const cacheResult = await this.cacheManager.cacheVaultFile(file); cachedPath = cacheResult.cachedPath; uuid = cacheResult.uuid; mimeType = 'text/markdown'; sourcePath = file.path; // Относительный от promptsFolder } else { console.warn(`Промпт не найден: ${reference.path}`); continue; } } attachments.push({ type: reference.type, name: reference.name, sourcePath: sourcePath, cachedPath: cachedPath, uuid: uuid, permanent: reference.permanent, mimeType: mimeType }); } catch (error) { console.error(`Не удалось обработать вложение ${reference.name}:`, error); } } return { originalText: text, attachments: attachments }; } }