Support linking audio-logs with @ in chat
This commit is contained in:
parent
9ae3256c35
commit
f66dd0920b
33
main.ts
33
main.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, ItemView, WorkspaceLeaf, TFile } from 'obsidian';
|
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, ItemView, WorkspaceLeaf, TFile, FileSystemAdapter } from 'obsidian';
|
||||||
import { EventBus } from './src/utils/EventBus';
|
import { EventBus } from './src/utils/EventBus';
|
||||||
import { GraphView, GRAPH_VIEW_TYPE } from './src/views/GraphView';
|
import { GraphView, GRAPH_VIEW_TYPE } from './src/views/GraphView';
|
||||||
import { ChatView, CHAT_VIEW_TYPE } from './src/views/ChatView';
|
import { ChatView, CHAT_VIEW_TYPE } from './src/views/ChatView';
|
||||||
|
|
@ -24,6 +24,7 @@ interface LLMAgentSettings {
|
||||||
|
|
||||||
summarizationModel: string;
|
summarizationModel: string;
|
||||||
voiceCommandModel: string;
|
voiceCommandModel: string;
|
||||||
|
logsFolder: string;
|
||||||
voiceInterval: number;
|
voiceInterval: number;
|
||||||
voiceDisplayLimit: number; // 3000
|
voiceDisplayLimit: number; // 3000
|
||||||
voiceContextLimit: number; // N2 символов для LLM
|
voiceContextLimit: number; // N2 символов для LLM
|
||||||
|
|
@ -43,10 +44,12 @@ const DEFAULT_SETTINGS: LLMAgentSettings = {
|
||||||
excludedFolders: ['.obsidian', 'llm-agent-backend', 'templates', 'ref-cache'], // Папка "templates" по умолчанию исключена
|
excludedFolders: ['.obsidian', 'llm-agent-backend', 'templates', 'ref-cache'], // Папка "templates" по умолчанию исключена
|
||||||
refCacheFolder: 'ref-cache', // По умолчанию относительно vault
|
refCacheFolder: 'ref-cache', // По умолчанию относительно vault
|
||||||
|
|
||||||
voiceModel: '',
|
summarizationModel: '',
|
||||||
|
voiceCommandModel: '',
|
||||||
|
logsFolder: 'audio-logs',
|
||||||
voiceInterval: 1,
|
voiceInterval: 1,
|
||||||
voiceDisplayLimit: 3000,
|
voiceDisplayLimit: 30000,
|
||||||
voiceContextLimit: 3000,
|
voiceContextLimit: 30000,
|
||||||
promptRegular: '',
|
promptRegular: '',
|
||||||
promptCommands: '',
|
promptCommands: '',
|
||||||
markerStart: 'старт',
|
markerStart: 'старт',
|
||||||
|
|
@ -74,6 +77,13 @@ export default class LLMAgentPlugin extends Plugin {
|
||||||
settingsToSync.promptRegular = await engine.processRawSettingValue(this.settings.promptRegular);
|
settingsToSync.promptRegular = await engine.processRawSettingValue(this.settings.promptRegular);
|
||||||
settingsToSync.promptCommands = await engine.processRawSettingValue(this.settings.promptCommands);
|
settingsToSync.promptCommands = await engine.processRawSettingValue(this.settings.promptCommands);
|
||||||
|
|
||||||
|
const adapter = this.app.vault.adapter;
|
||||||
|
if (adapter instanceof FileSystemAdapter) {
|
||||||
|
// Соединяем базовый путь хранилища и относительный путь к логам
|
||||||
|
settingsToSync.absoluteLogsPath = adapter.getBasePath() +
|
||||||
|
(this.settings.logsFolder ? '/' + this.settings.logsFolder : '');
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`${this.settings.apiBaseUrl}/settings/sync`, {
|
const response = await fetch(`${this.settings.apiBaseUrl}/settings/sync`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -310,6 +320,21 @@ class SampleSettingTab extends PluginSettingTab {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Папка аудио-логов')
|
||||||
|
.setDesc('Папка внутри Obsidian, куда будут сохраняться текстовые расшифровки (в формате .md). Относительно корня хранилища.')
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder('audio-logs')
|
||||||
|
.setValue(this.plugin.settings.logsFolder)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
// Убираем лишние слэши для корректности пути
|
||||||
|
this.plugin.settings.logsFolder = value.replace(/^\/+|\/+$/g, '').trim();
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.syncSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Интервал регулярного анализа (мин)')
|
.setName('Интервал регулярного анализа (мин)')
|
||||||
.setDesc('Как часто LLM анализирует последние фразы (Prompt 1).')
|
.setDesc('Как часто LLM анализирует последние фразы (Prompt 1).')
|
||||||
|
|
|
||||||
|
|
@ -45,22 +45,36 @@ export class AutocompleteManager {
|
||||||
* @returns массив файлов для автокомплита
|
* @returns массив файлов для автокомплита
|
||||||
*/
|
*/
|
||||||
async getFileOptions(query: string): Promise<AutocompleteItem[]> {
|
async getFileOptions(query: string): Promise<AutocompleteItem[]> {
|
||||||
if (!this.plugin.settings.enableFileReferences) {
|
if (!this.plugin.settings.enableFileReferences) return [];
|
||||||
return [];
|
|
||||||
|
const lowercaseQuery = query.toLowerCase();
|
||||||
|
const virtualItems: AutocompleteItem[] = [];
|
||||||
|
|
||||||
|
// Пункт 1: Если в запросе "cur", добавляем логи в начало
|
||||||
|
if ("current-log".includes(lowercaseQuery)) {
|
||||||
|
virtualItems.push({
|
||||||
|
name: "current-log",
|
||||||
|
path: "virtual:current-log",
|
||||||
|
type: "file", // Для применения иконки @ по умолчанию
|
||||||
|
icon: "file-text"
|
||||||
|
});
|
||||||
|
virtualItems.push({
|
||||||
|
name: "current-log:part",
|
||||||
|
path: "virtual:current-log:part",
|
||||||
|
type: "file",
|
||||||
|
icon: "file-text"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateCacheIfNeeded();
|
await this.updateCacheIfNeeded();
|
||||||
|
|
||||||
const allFiles = this.fileCache.get('all') || [];
|
const allFiles = this.fileCache.get('all') || [];
|
||||||
|
|
||||||
if (!query.trim()) {
|
// Объединяем: виртуальные вначале, потом поиск по файлам
|
||||||
return allFiles.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS); // Показываем первые 10 файлов
|
const filteredFiles = !query.trim()
|
||||||
}
|
? allFiles
|
||||||
|
: allFiles.filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery));
|
||||||
|
|
||||||
const lowercaseQuery = query.toLowerCase();
|
return [...virtualItems, ...filteredFiles].slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
|
||||||
return allFiles
|
|
||||||
.filter((item) => item.name.toLowerCase().includes(lowercaseQuery) || item.path.toLowerCase().includes(lowercaseQuery))
|
|
||||||
.slice(0, AutocompleteManager.MAX_TOTAL_ITEMS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Обрабатывает как одинарные (@/#), так и постоянные (@@/##) ссылки.
|
* Обрабатывает как одинарные (@/#), так и постоянные (@@/##) ссылки.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TFile } from 'obsidian';
|
import { FileSystemAdapter, TFile } from 'obsidian';
|
||||||
import LLMAgentPlugin from 'main';
|
import LLMAgentPlugin from 'main';
|
||||||
import { FileReference, ReferenceParser } from './ReferenceParser';
|
import { FileReference, ReferenceParser } from './ReferenceParser';
|
||||||
import { TemplateEngine } from './TemplateEngine';
|
import { TemplateEngine } from './TemplateEngine';
|
||||||
|
|
@ -79,7 +79,8 @@ export class ReferenceExpander {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Извлекает вложения из текста и кеширует файлы
|
* Извлекает вложения из текста и кеширует файлы.
|
||||||
|
* Обрабатывает как физические файлы, так и виртуальные (current-log).
|
||||||
*/
|
*/
|
||||||
async extractAttachments(
|
async extractAttachments(
|
||||||
text: string,
|
text: string,
|
||||||
|
|
@ -97,15 +98,56 @@ export class ReferenceExpander {
|
||||||
let mimeType: string;
|
let mimeType: string;
|
||||||
let sourcePath: string;
|
let sourcePath: string;
|
||||||
|
|
||||||
if (reference.type === 'external') {
|
// --- Обработка виртуальных ссылок на логи ---
|
||||||
// Кешируем внешний файл
|
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);
|
const cacheResult = await this.cacheManager.cacheExternalFile(reference.path);
|
||||||
cachedPath = cacheResult.cachedPath;
|
cachedPath = cacheResult.cachedPath;
|
||||||
uuid = cacheResult.uuid;
|
uuid = cacheResult.uuid;
|
||||||
mimeType = cacheResult.mimeType;
|
mimeType = cacheResult.mimeType;
|
||||||
sourcePath = reference.path; // Абсолютный
|
sourcePath = reference.path; // Абсолютный
|
||||||
} else if (reference.type === 'file') {
|
}
|
||||||
// ИЗМЕНЕНО: Кешируем файл из vault
|
// --- Обработка файлов из Vault (@ / @@) ---
|
||||||
|
else if (reference.type === 'file') {
|
||||||
const file = this.plugin.app.vault.getAbstractFileByPath(reference.path);
|
const file = this.plugin.app.vault.getAbstractFileByPath(reference.path);
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
const cacheResult = await this.cacheManager.cacheVaultFile(file);
|
const cacheResult = await this.cacheManager.cacheVaultFile(file);
|
||||||
|
|
@ -117,9 +159,9 @@ export class ReferenceExpander {
|
||||||
console.warn(`Файл не найден в vault: ${reference.path}`);
|
console.warn(`Файл не найден в vault: ${reference.path}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// prompt
|
// --- Обработка промптов (# / ##) ---
|
||||||
// Кешируем промпт
|
else {
|
||||||
const file = this.plugin.app.vault.getAbstractFileByPath(reference.path);
|
const file = this.plugin.app.vault.getAbstractFileByPath(reference.path);
|
||||||
|
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
|
|
@ -144,7 +186,7 @@ export class ReferenceExpander {
|
||||||
mimeType: mimeType
|
mimeType: mimeType
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Не удалось закешировать ${reference.name}:`, error);
|
console.error(`Не удалось обработать вложение ${reference.name}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user