From 326198bb1da37d2d796f8f0254f86bedcefa8d10 Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Sun, 28 Sep 2025 23:45:52 +0300 Subject: [PATCH] Markdown rendering of messages in chat panel --- src/components/ChatPanel.ts | 61 ++++++++++++++++++++++--------------- src/views/ChatView.ts | 12 ++++---- src/views/GraphView.ts | 1 - 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/components/ChatPanel.ts b/src/components/ChatPanel.ts index 83aa5c4..3a0a6f9 100644 --- a/src/components/ChatPanel.ts +++ b/src/components/ChatPanel.ts @@ -12,6 +12,7 @@ import { ReferenceParser, FileReference, ReferenceMatch } from '../references/Re import { ReferenceBox } from '../references/ReferenceBox'; import { AutocompleteItem } from '../references/AutocompleteManager'; import LLMAgentPlugin from 'main'; +import { MarkdownRenderer } from 'obsidian'; // Добавляем импорт MarkdownRenderer export class ChatPanel { container: HTMLElement; @@ -193,29 +194,36 @@ export class ChatPanel { this.suggestionsContainer.style.display = 'none'; } - renderHistory(): void { + /** + * Рендерит историю сообщений в контейнер чата. + * @returns {Promise} + */ + async renderHistory(): Promise { const historyContainer = this.container.querySelector('#chat-history') as HTMLElement; - historyContainer.innerHTML = this.chatHistory - .map( - (msg, index) => ` -
-
-
-
- ${ - msg.role === 'user' - ? `` - : `` - } -
- ${this.getRoleLabel(msg.role)} -
-
-
${this.processMessageContent(msg.content)}
-
- ` - ) - .join(''); + historyContainer.empty(); // Очищаем контейнер перед добавлением новых сообщений + + for (const msg of this.chatHistory) { + const messageEl = historyContainer.createDiv({ cls: `message ${msg.role} message-type-${msg.type || 'default'}` }); + + // Информация об отправителе + const metaDiv = messageEl.createDiv({ cls: 'chat-meta' }); + const senderInfoDiv = metaDiv.createDiv({ cls: 'chat-sender-info' }); + const iconDiv = senderInfoDiv.createDiv({ cls: 'chat-message-icon' }); + iconDiv.innerHTML = + msg.role === 'user' + ? `` + : ``; + senderInfoDiv.createSpan({ cls: 'chat-role-label', text: this.getRoleLabel(msg.role) }); + + // Контент сообщения, рендерится Markdown + const contentDiv = messageEl.createDiv({ cls: 'chat-message-content' }); + await MarkdownRenderer.renderMarkdown( + msg.content, + contentDiv, + '', // Путь к источнику, может быть пустым для сообщений чата + this.props.plugin // Компонент, необходимый для управления жизненным циклом рендера + ); + } // Прокручиваем к самому низу родительского контейнера (view-content) if (this.chatHistory.length > 0) { @@ -249,9 +257,14 @@ export class ChatPanel { return content.replace(/]*?)>/g, '').replace(/\n/g, '
'); } - updateHistory(newHistory: ChatHistoryItem[]): void { + /** + * Обновляет историю чата и перерисовывает её. + * @param {ChatHistoryItem[]} newHistory - Новый массив элементов истории чата. + * @returns {Promise} + */ + async updateHistory(newHistory: ChatHistoryItem[]): Promise { this.chatHistory = newHistory; - this.renderHistory(); + await this.renderHistory(); } updateAvailableModels(models: string[]): void { diff --git a/src/views/ChatView.ts b/src/views/ChatView.ts index 338bf49..0ca36b0 100644 --- a/src/views/ChatView.ts +++ b/src/views/ChatView.ts @@ -114,7 +114,7 @@ export class ChatView extends ItemView { type: msg.type || msg.role // Используем type, если есть, иначе role как fallback })) || []; if (this.chatPanel) { - this.chatPanel.updateHistory(this.chatHistory); + await this.chatPanel.updateHistory(this.chatHistory); } } catch (error) { console.error('Ошибка загрузки сообщений:', error); @@ -181,9 +181,9 @@ export class ChatView extends ItemView { this.titleUpdateTimer = setTimeout(() => { console.log('Обновление графа для получения сгенерированных заголовков...'); if (this.selectedGraphId && this.currentNode) { - this.plugin.eventBus.emit('graph-selected', { - graphId: this.selectedGraphId, - currentNode: this.currentNode + this.plugin.eventBus.emit('graph-selected', { + graphId: this.selectedGraphId, + currentNode: this.currentNode }); } this.titleUpdateTimer = null; @@ -220,12 +220,12 @@ export class ChatView extends ItemView { * Обрабатывает событие начала нового чата. Сбрасывает текущее состояние чата * и очищает отображение истории. */ - handleNewChat(): void { + async handleNewChat(): Promise { this.chatHistory = []; this.currentNode = null; this.selectedGraphId = null; if (this.chatPanel) { - this.chatPanel.updateHistory(this.chatHistory); + await this.chatPanel.updateHistory(this.chatHistory); } } diff --git a/src/views/GraphView.ts b/src/views/GraphView.ts index 0efaed1..e6b6a1a 100644 --- a/src/views/GraphView.ts +++ b/src/views/GraphView.ts @@ -303,7 +303,6 @@ export class GraphView extends ItemView { */ async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise { this.selectedGraphId = data.graphId; - console.log("11111") // currentNode здесь может быть неактуальным, fetchGraphData загрузит актуальный current_node_id await this.fetchGraphData(data.graphId); // Note: Больше не нужно эмитить 'graph-selected' обратно в ChatView, т.к. ChatView уже инициировал это.