diff --git a/main.ts b/main.ts index 370af0c..d7784c9 100644 --- a/main.ts +++ b/main.ts @@ -26,10 +26,12 @@ interface LLMAgentSettings { voiceCommandModel: string; logsFolder: string; voiceInterval: number; + metricsInterval: number; voiceDisplayLimit: number; // 3000 voiceContextLimit: number; // N2 символов для LLM promptRegular: string; promptCommands: string; + promptMetrics: string; markerStart: string; // "старт" markerStop: string; // "стоп" } @@ -48,10 +50,12 @@ const DEFAULT_SETTINGS: LLMAgentSettings = { voiceCommandModel: '', logsFolder: 'audio-logs', voiceInterval: 1, + metricsInterval: 15, voiceDisplayLimit: 30000, voiceContextLimit: 30000, promptRegular: '', promptCommands: '', + promptMetrics: '', markerStart: 'старт', markerStop: 'стоп' }; @@ -76,6 +80,7 @@ export default class LLMAgentPlugin extends Plugin { settingsToSync.systemPrompt = await engine.processRawSettingValue(this.settings.systemPrompt); settingsToSync.promptRegular = await engine.processRawSettingValue(this.settings.promptRegular); settingsToSync.promptCommands = await engine.processRawSettingValue(this.settings.promptCommands); + settingsToSync.promptMetrics = await engine.processRawSettingValue(this.settings.promptMetrics); const adapter = this.app.vault.adapter; if (adapter instanceof FileSystemAdapter) { @@ -163,8 +168,12 @@ export default class LLMAgentPlugin extends Plugin { this.addSettingTab(new SampleSettingTab(this.app, this)); await this.fetchAvailableModels(); - // Вызываем один раз при запуске - await this.syncSettings(); + + // Ждем полной инициализации хранилища перед вызовом зависимых функций + // await this.syncSettings(); + this.app.workspace.onLayoutReady(async () => { + await this.syncSettings(); + }); this.registerEvent( this.app.vault.on('modify', async (file) => { @@ -348,6 +357,19 @@ class SampleSettingTab extends PluginSettingTab { }) ); + new Setting(containerEl) + .setName('Интервал аналитики (мин)') + .setDesc('Как часто Python и LLM генерируют графики и качественный анализ (Таб 4).') + .addText((text) => + text + .setValue(String(this.plugin.settings.metricsInterval)) + .onChange(async (value) => { + this.plugin.settings.metricsInterval = Number(value) || 15; + await this.plugin.saveSettings(); + await this.plugin.syncSettings(); + }) + ); + new Setting(containerEl) .setName('Объем контекста (символы)') .setDesc('Сколько последних символов транскрибации отправлять в модель.') @@ -401,6 +423,11 @@ class SampleSettingTab extends PluginSettingTab { 'promptCommands' ); + this.addPromptAutocomplete( + new Setting(containerEl).setName('Промпт качественной аналитики'), + 'promptMetrics' + ); + containerEl.createEl('h2', { text: 'Управление ссылками и файлами' }); new Setting(containerEl) diff --git a/src/components/VoicePanel.ts b/src/components/VoicePanel.ts index 3052424..e06754c 100644 --- a/src/components/VoicePanel.ts +++ b/src/components/VoicePanel.ts @@ -2,7 +2,7 @@ import LLMAgentPlugin from 'main'; import { MarkdownRenderer, setIcon, Notice } from 'obsidian'; export class VoicePanel { - private activeTab: 'res1' | 'res2' | 'trans' = 'res1'; + private activeTab: 'res1' | 'res2' | 'trans' | 'metrics' = 'res1'; constructor( private container: HTMLElement, @@ -25,6 +25,7 @@ export class VoicePanel { +
@@ -84,6 +85,42 @@ export class VoicePanel { return; } + if (this.activeTab === 'metrics') { + if (!data.metrics) return; + + // 1. Строгая проверка через JSON.stringify, чтобы отсечь лишние апдейты + // и не перестраивать DOM (что и вызывает дергание) + const newDataString = JSON.stringify(data.metrics); + if ((body as any).lastMetricsData === newDataString) return; + (body as any).lastMetricsData = newDataString; + + // 2. Запоминаем позицию скролла перед обновлением + const scrollTop = body.scrollTop; + + body.empty(); + + // Рендер графиков + if (data.metrics.graphs && data.metrics.graphs.length > 0) { + const graphsContainer = document.createElement('div'); + graphsContainer.style.display = "flex"; + graphsContainer.style.flexDirection = "column"; + graphsContainer.innerHTML = data.metrics.graphs.join(''); + body.appendChild(graphsContainer); + } + + // Рендер текста + if (data.metrics.text) { + const textContainer = document.createElement('div'); + textContainer.addClass('markdown-rendered'); + await MarkdownRenderer.renderMarkdown(data.metrics.text, textContainer, '', this.plugin); + body.appendChild(textContainer); + } + + // 3. Возвращаем скролл на место сразу после рендера в следующем микротаске + setTimeout(() => { body.scrollTop = scrollTop; }, 0); + return; + } + // Получаем записи (они уже приходят отсортированные: новые в начале) const entries: { id: string; content: string }[] = this.activeTab === 'res1' ? data.response1 : data.response2;