Markdown rendering of messages in chat panel

This commit is contained in:
dimitrievgs 2025-09-28 23:45:52 +03:00
parent 23b4ca4571
commit 326198bb1d
3 changed files with 43 additions and 31 deletions

View File

@ -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<void>}
*/
async renderHistory(): Promise<void> {
const historyContainer = this.container.querySelector('#chat-history') as HTMLElement;
historyContainer.innerHTML = this.chatHistory
.map(
(msg, index) => `
<div class="message ${msg.role} message-type-${msg.type || 'default'} ">
<div class="chat-meta">
<div class="chat-sender-info">
<div class="chat-message-icon">
${
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'
? `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-user"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`
: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>`
: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>`;
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 // Компонент, необходимый для управления жизненным циклом рендера
);
}
</div>
<span class="chat-role-label">${this.getRoleLabel(msg.role)}</span>
</div>
</div>
<div class="chat-message-content">${this.processMessageContent(msg.content)}</div>
</div>
`
)
.join('');
// Прокручиваем к самому низу родительского контейнера (view-content)
if (this.chatHistory.length > 0) {
@ -249,9 +257,14 @@ export class ChatPanel {
return content.replace(/<img([^>]*?)>/g, '<img$1 style="max-width: 100%; height: auto;">').replace(/\n/g, '<br>');
}
updateHistory(newHistory: ChatHistoryItem[]): void {
/**
* Обновляет историю чата и перерисовывает её.
* @param {ChatHistoryItem[]} newHistory - Новый массив элементов истории чата.
* @returns {Promise<void>}
*/
async updateHistory(newHistory: ChatHistoryItem[]): Promise<void> {
this.chatHistory = newHistory;
this.renderHistory();
await this.renderHistory();
}
updateAvailableModels(models: string[]): void {

View File

@ -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);
@ -220,12 +220,12 @@ export class ChatView extends ItemView {
* Обрабатывает событие начала нового чата. Сбрасывает текущее состояние чата
* и очищает отображение истории.
*/
handleNewChat(): void {
async handleNewChat(): Promise<void> {
this.chatHistory = [];
this.currentNode = null;
this.selectedGraphId = null;
if (this.chatPanel) {
this.chatPanel.updateHistory(this.chatHistory);
await this.chatPanel.updateHistory(this.chatHistory);
}
}

View File

@ -303,7 +303,6 @@ export class GraphView extends ItemView {
*/
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
this.selectedGraphId = data.graphId;
console.log("11111")
// currentNode здесь может быть неактуальным, fetchGraphData загрузит актуальный current_node_id
await this.fetchGraphData(data.graphId);
// Note: Больше не нужно эмитить 'graph-selected' обратно в ChatView, т.к. ChatView уже инициировал это.