llm-agent-plugin/src/references/ReferenceExpander.ts

147 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Расширяет ссылки на файлы и промпты, заменяя их содержимым перед отправкой на сервер.
* Обрабатывает как одинарные (@/#), так и постоянные (@@/##) ссылки.
*/
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
};
}
}