Add graph options with custom system prompt, make more correct handling for New Chat button
This commit is contained in:
parent
fd6e7d12c5
commit
1a7c1de507
2
main.ts
2
main.ts
|
|
@ -4,7 +4,7 @@ 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';
|
||||||
import { WebSocketService } from './src/utils/WebSocketService';
|
import { WebSocketService } from './src/utils/WebSocketService';
|
||||||
|
|
||||||
import 'src/css/styles.css'; // Путь к твоему новому CSS файлу
|
import 'src/css/styles.css';
|
||||||
|
|
||||||
// ----------------------------------------------- Settings ---------------------------------------------------------------
|
// ----------------------------------------------- Settings ---------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
25
package-lock.json
generated
25
package-lock.json
generated
|
|
@ -9,7 +9,9 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"socket.io-client": "^4.8.1"
|
"@types/uuid": "^11.0.0",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/dagre": "^0.7.52",
|
"@types/dagre": "^0.7.52",
|
||||||
|
|
@ -980,6 +982,15 @@
|
||||||
"@types/estree": "*"
|
"@types/estree": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==",
|
||||||
|
"deprecated": "This is a stub types definition. uuid provides its own type definitions, so you do not need this installed.",
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "5.29.0",
|
"version": "5.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz",
|
||||||
|
|
@ -2899,6 +2910,18 @@
|
||||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist-node/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-keyname": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"socket.io-client": "^4.8.1"
|
"@types/uuid": "^11.0.0",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
|
"uuid": "^13.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export class ChatPanel {
|
||||||
availableModels?: string[];
|
availableModels?: string[];
|
||||||
selectedModel?: string;
|
selectedModel?: string;
|
||||||
plugin: LLMAgentPlugin;
|
plugin: LLMAgentPlugin;
|
||||||
|
currentGraphCustomSystemPrompt: string | null;
|
||||||
};
|
};
|
||||||
chatHistory: ChatHistoryItem[];
|
chatHistory: ChatHistoryItem[];
|
||||||
messageInput: HTMLDivElement;
|
messageInput: HTMLDivElement;
|
||||||
|
|
@ -31,6 +32,7 @@ export class ChatPanel {
|
||||||
modelSelectButton: HTMLButtonElement;
|
modelSelectButton: HTMLButtonElement;
|
||||||
modelDropdown: HTMLDivElement;
|
modelDropdown: HTMLDivElement;
|
||||||
selectedModel: string;
|
selectedModel: string;
|
||||||
|
customPromptIndicator: HTMLSpanElement | null = null;
|
||||||
|
|
||||||
// ----------------------------------------------- Reference System Fields ---------------------------------------------------------------
|
// ----------------------------------------------- Reference System Fields ---------------------------------------------------------------
|
||||||
private autocompleteManager: AutocompleteManager;
|
private autocompleteManager: AutocompleteManager;
|
||||||
|
|
@ -47,7 +49,8 @@ export class ChatPanel {
|
||||||
onSendMessage: (message: string, selectedModel?: string) => void;
|
onSendMessage: (message: string, selectedModel?: string) => void;
|
||||||
availableModels?: string[];
|
availableModels?: string[];
|
||||||
selectedModel?: string;
|
selectedModel?: string;
|
||||||
plugin: LLMAgentPlugin; // Обновленный тип
|
plugin: LLMAgentPlugin;
|
||||||
|
currentGraphCustomSystemPrompt: string | null;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
|
|
@ -321,6 +324,10 @@ export class ChatPanel {
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="model-dropdown" id="model-dropdown" style="display: none;"></div>
|
<div class="model-dropdown" id="model-dropdown" style="display: none;"></div>
|
||||||
|
<span class="custom-prompt-indicator" id="custom-prompt-indicator" style="display: none;"
|
||||||
|
data-tooltip-position="top" aria-label="Используется пользовательский системный промпт для этого графа">
|
||||||
|
⚙️
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-buttons-right">
|
<div class="control-buttons-right">
|
||||||
|
|
@ -339,12 +346,14 @@ export class ChatPanel {
|
||||||
this.suggestionsContainer = this.container.querySelector('#suggestions') as HTMLDivElement;
|
this.suggestionsContainer = this.container.querySelector('#suggestions') as HTMLDivElement;
|
||||||
this.modelSelectButton = this.container.querySelector('#model-select-button') as HTMLButtonElement;
|
this.modelSelectButton = this.container.querySelector('#model-select-button') as HTMLButtonElement;
|
||||||
this.modelDropdown = this.container.querySelector('#model-dropdown') as HTMLDivElement;
|
this.modelDropdown = this.container.querySelector('#model-dropdown') as HTMLDivElement;
|
||||||
|
this.customPromptIndicator = this.container.querySelector('#custom-prompt-indicator') as HTMLSpanElement;
|
||||||
|
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
this.setupReferenceSystem();
|
this.setupReferenceSystem();
|
||||||
this.renderHistory();
|
this.renderHistory();
|
||||||
this.updateModelDropdown();
|
this.updateModelDropdown();
|
||||||
this.updatePlaceholder();
|
this.updatePlaceholder();
|
||||||
|
this.updateCustomSystemPromptStatus(this.props.currentGraphCustomSystemPrompt); // Инициализируем статус
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------- Reference System Setup ---------------------------------------------------------------
|
// ----------------------------------------------- Reference System Setup ---------------------------------------------------------------
|
||||||
|
|
@ -868,26 +877,41 @@ export class ChatPanel {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Кнопка "Наверх сообщения"
|
// Кнопка "Наверх сообщения"
|
||||||
const scrollToTopButton = toolsetContainer.createEl('button', {
|
const scrollToTopButton = toolsetContainer.createEl('button', {
|
||||||
cls: 'text-token-text-secondary hover:bg-token-bg-secondary rounded-lg ml-1',
|
cls: 'text-token-text-secondary hover:bg-token-bg-secondary rounded-lg ml-1',
|
||||||
attr: {
|
attr: {
|
||||||
'aria-label': 'Перейти к началу сообщения',
|
'aria-label': 'Перейти к началу сообщения',
|
||||||
'data-testid': 'scroll-to-message-top-button',
|
'data-testid': 'scroll-to-message-top-button',
|
||||||
'data-state': 'closed'
|
'data-state': 'closed'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const scrollToTopIconContainer = scrollToTopButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' });
|
const scrollToTopIconContainer = scrollToTopButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' });
|
||||||
setIcon(scrollToTopIconContainer, 'arrow-up'); // Иконка стрелки вверх
|
setIcon(scrollToTopIconContainer, 'arrow-up'); // Иконка стрелки вверх
|
||||||
scrollToTopButton.addEventListener('click', () => {
|
scrollToTopButton.addEventListener('click', () => {
|
||||||
// Прокручиваем родительский контейнер (view-content) к началу этого сообщения
|
// Прокручиваем родительский контейнер (view-content) к началу этого сообщения
|
||||||
const scrollContainer = this.container.closest('.view-content') as HTMLElement;
|
const scrollContainer = this.container.closest('.view-content') as HTMLElement;
|
||||||
if (scrollContainer) {
|
if (scrollContainer) {
|
||||||
scrollContainer.scrollTop = messageElement.offsetTop;
|
scrollContainer.scrollTop = messageElement.offsetTop;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private copyMessageContent(content: string): Promise<void> {
|
private copyMessageContent(content: string): Promise<void> {
|
||||||
return navigator.clipboard.writeText(content);
|
return navigator.clipboard.writeText(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateCustomSystemPromptStatus(customSystemPrompt: string | null): void {
|
||||||
|
if (this.customPromptIndicator) {
|
||||||
|
if (customSystemPrompt) {
|
||||||
|
this.customPromptIndicator.style.display = 'inline-flex';
|
||||||
|
this.customPromptIndicator.setAttribute(
|
||||||
|
'aria-label',
|
||||||
|
`Используется пользовательский системный промпт: ${customSystemPrompt}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.customPromptIndicator.style.display = 'none';
|
||||||
|
this.customPromptIndicator.removeAttribute('aria-label');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
92
src/modals/GraphSettingsModal.ts
Normal file
92
src/modals/GraphSettingsModal.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
// ----------------------------------------------- Obsidian Imports ----------------------------------------------------------
|
||||||
|
import { App, Modal, Setting } from 'obsidian';
|
||||||
|
|
||||||
|
// ----------------------------------------------- Interfaces ----------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Интерфейс для свойств, передаваемых в модальное окно GraphSettingsModal.
|
||||||
|
*/
|
||||||
|
interface GraphSettingsModalProps {
|
||||||
|
app: App;
|
||||||
|
graphId: string;
|
||||||
|
graphTitle: string;
|
||||||
|
initialCustomSystemPrompt: string | null;
|
||||||
|
onSave: (graphId: string, customSystemPrompt: string | null) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------- GraphSettingsModal --------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Модальное окно для настройки специфического системного промпта для конкретного графа.
|
||||||
|
*/
|
||||||
|
export class GraphSettingsModal extends Modal {
|
||||||
|
private graphId: string;
|
||||||
|
private graphTitle: string;
|
||||||
|
private customSystemPrompt: string | null;
|
||||||
|
private initialCustomSystemPrompt: string | null;
|
||||||
|
private onSave: (graphId: string, customSystemPrompt: string | null) => Promise<void>;
|
||||||
|
|
||||||
|
constructor(app: App, props: GraphSettingsModalProps) {
|
||||||
|
super(app);
|
||||||
|
this.graphId = props.graphId;
|
||||||
|
this.graphTitle = props.graphTitle;
|
||||||
|
this.customSystemPrompt = props.initialCustomSystemPrompt;
|
||||||
|
this.initialCustomSystemPrompt = props.initialCustomSystemPrompt;
|
||||||
|
this.onSave = props.onSave;
|
||||||
|
|
||||||
|
this.titleEl.setText(`Настройки графа: ${this.graphTitle}`);
|
||||||
|
this.modalEl.addClass('llm-agent-graph-settings-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen(): void {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
|
||||||
|
// ----------------------------------------------- System Prompt Setting ------------------------------------------------
|
||||||
|
new Setting(contentEl)
|
||||||
|
.setName('Пользовательский системный промпт')
|
||||||
|
.setDesc('Этот промпт будет использоваться для данного графа вместо глобального системного промпта. Оставьте пустым, чтобы использовать глобальный промпт.')
|
||||||
|
.addTextArea(textArea => {
|
||||||
|
textArea
|
||||||
|
.setPlaceholder('Введите пользовательский системный промпт для этого графа...')
|
||||||
|
.setValue(this.customSystemPrompt || '')
|
||||||
|
.onChange(value => {
|
||||||
|
this.customSystemPrompt = value.trim() === '' ? null : value.trim();
|
||||||
|
});
|
||||||
|
textArea.inputEl.rows = 6;
|
||||||
|
textArea.inputEl.style.minWidth = '400px';
|
||||||
|
textArea.inputEl.style.width = '100%';
|
||||||
|
});
|
||||||
|
|
||||||
|
// ----------------------------------------------- Action Buttons -------------------------------------------------------
|
||||||
|
new Setting(contentEl)
|
||||||
|
.addButton(button => {
|
||||||
|
button
|
||||||
|
.setButtonText('Сохранить')
|
||||||
|
.setCta()
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.onSave(this.graphId, this.customSystemPrompt);
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addButton(button => {
|
||||||
|
button
|
||||||
|
.setButtonText('Сбросить на глобальный')
|
||||||
|
.setClass('mod-warning')
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.onSave(this.graphId, null); // Отправляем null для сброса
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addButton(button => {
|
||||||
|
button
|
||||||
|
.setButtonText('Отмена')
|
||||||
|
.onClick(() => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(): void {
|
||||||
|
const { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ export class ChatView extends ItemView {
|
||||||
chatContainer: HTMLDivElement;
|
chatContainer: HTMLDivElement;
|
||||||
chatPanel: ChatPanel | null;
|
chatPanel: ChatPanel | null;
|
||||||
availableModels: string[];
|
availableModels: string[];
|
||||||
|
currentGraphCustomSystemPrompt: string | null;
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
|
|
@ -28,6 +29,7 @@ export class ChatView extends ItemView {
|
||||||
this.currentNode = null;
|
this.currentNode = null;
|
||||||
this.chatPanel = null;
|
this.chatPanel = null;
|
||||||
this.availableModels = [];
|
this.availableModels = [];
|
||||||
|
this.currentGraphCustomSystemPrompt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewType(): string {
|
getViewType(): string {
|
||||||
|
|
@ -59,16 +61,17 @@ export class ChatView extends ItemView {
|
||||||
onSendMessage: this.handleSendMessage.bind(this),
|
onSendMessage: this.handleSendMessage.bind(this),
|
||||||
availableModels: this.availableModels,
|
availableModels: this.availableModels,
|
||||||
selectedModel: this.plugin.settings.defaultModel || 'gemini-2.5-flash',
|
selectedModel: this.plugin.settings.defaultModel || 'gemini-2.5-flash',
|
||||||
plugin: this.plugin // Добавляем передачу plugin
|
plugin: this.plugin,
|
||||||
|
currentGraphCustomSystemPrompt: this.currentGraphCustomSystemPrompt
|
||||||
});
|
});
|
||||||
|
|
||||||
// Остальной код без изменений...
|
|
||||||
this.plugin.eventBus.on('node-selected', this.handleNodeSelected.bind(this));
|
this.plugin.eventBus.on('node-selected', this.handleNodeSelected.bind(this));
|
||||||
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
this.plugin.eventBus.on('new-chat', this.handleNewChat.bind(this));
|
||||||
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
|
this.plugin.eventBus.on('graph-selected', this.handleGraphSelected.bind(this));
|
||||||
this.plugin.eventBus.on('regenerate-message', this.handleRegenerateMessage.bind(this)); // Добавлено
|
this.plugin.eventBus.on('regenerate-message', this.handleRegenerateMessage.bind(this));
|
||||||
this.plugin.eventBus.on('ws-node-type-changed', this.handleNodeTypeChanged.bind(this));
|
this.plugin.eventBus.on('ws-node-type-changed', this.handleNodeTypeChanged.bind(this));
|
||||||
this.plugin.eventBus.on('start-new-independent-chat', this.handleStartNewIndependentChat.bind(this));
|
this.plugin.eventBus.on('start-new-independent-chat', this.handleStartNewIndependentChat.bind(this));
|
||||||
|
this.plugin.eventBus.on('graph-custom-prompt-updated', this.handleGraphCustomPromptUpdated.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClose(): Promise<void> {
|
async onClose(): Promise<void> {
|
||||||
|
|
@ -82,6 +85,7 @@ export class ChatView extends ItemView {
|
||||||
this.plugin.eventBus.off('regenerate-message', this.handleRegenerateMessage);
|
this.plugin.eventBus.off('regenerate-message', this.handleRegenerateMessage);
|
||||||
this.plugin.eventBus.off('ws-node-type-changed', this.handleNodeTypeChanged);
|
this.plugin.eventBus.off('ws-node-type-changed', this.handleNodeTypeChanged);
|
||||||
this.plugin.eventBus.off('start-new-independent-chat', this.handleStartNewIndependentChat);
|
this.plugin.eventBus.off('start-new-independent-chat', this.handleStartNewIndependentChat);
|
||||||
|
this.plugin.eventBus.off('graph-custom-prompt-updated', this.handleGraphCustomPromptUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
// ----------------------------------------------- API Methods ---------------------------------------------------------------
|
||||||
|
|
@ -164,6 +168,15 @@ export class ChatView extends ItemView {
|
||||||
|
|
||||||
async handleSendMessage(message: string, selectedModel?: string): Promise<void> {
|
async handleSendMessage(message: string, selectedModel?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
if (!this.selectedGraphId) {
|
||||||
|
new Notice('Граф не выбран или не инициализирован. Пожалуйста, начните новый чат или выберите существующий.', 3000);
|
||||||
|
console.warn('selectedGraphId is null in handleSendMessage. Current state:', {
|
||||||
|
selectedGraphId: this.selectedGraphId,
|
||||||
|
currentNode: this.currentNode
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Шаг 1: Создаём узел пользователя
|
// Шаг 1: Создаём узел пользователя
|
||||||
const sendResponse = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/send`, {
|
const sendResponse = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/send`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
@ -180,7 +193,7 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendResult = await sendResponse.json();
|
const sendResult = await sendResponse.json();
|
||||||
this.selectedGraphId = sendResult.graph_id;
|
// this.selectedGraphId = sendResult.graph_id;
|
||||||
const userNodeId = sendResult.node_id;
|
const userNodeId = sendResult.node_id;
|
||||||
|
|
||||||
// Обновляем граф с новым узлом пользователя
|
// Обновляем граф с новым узлом пользователя
|
||||||
|
|
@ -191,14 +204,15 @@ export class ChatView extends ItemView {
|
||||||
|
|
||||||
// Шаг 2: Запускаем стриминг ответа
|
// Шаг 2: Запускаем стриминг ответа
|
||||||
if (this.selectedGraphId) {
|
if (this.selectedGraphId) {
|
||||||
// Проверка на null
|
const systemPromptToUse = this.currentGraphCustomSystemPrompt !== null ?
|
||||||
// Передаем null, так как это не регенерация, а новое сообщение
|
this.currentGraphCustomSystemPrompt :
|
||||||
await this.streamLLMResponse(this.selectedGraphId, userNodeId, selectedModel, null);
|
this.plugin.settings.systemPrompt;
|
||||||
|
|
||||||
|
await this.streamLLMResponse(this.selectedGraphId, userNodeId, selectedModel, null, systemPromptToUse);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Graph ID не определён после отправки сообщения');
|
throw new Error('Graph ID не определён после отправки сообщения');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Указываем тип any для error
|
|
||||||
console.error('Ошибка отправки сообщения:', error);
|
console.error('Ошибка отправки сообщения:', error);
|
||||||
new Notice(`Ошибка: ${error?.message || error}`, 5000);
|
new Notice(`Ошибка: ${error?.message || error}`, 5000);
|
||||||
}
|
}
|
||||||
|
|
@ -223,45 +237,45 @@ export class ChatView extends ItemView {
|
||||||
async handleNewChat(): Promise<void> {
|
async handleNewChat(): Promise<void> {
|
||||||
this.chatHistory = [];
|
this.chatHistory = [];
|
||||||
this.currentNode = null;
|
this.currentNode = null;
|
||||||
this.selectedGraphId = null;
|
//this.selectedGraphId = null;
|
||||||
|
//this.currentGraphCustomSystemPrompt = null;
|
||||||
|
this.currentGraphCustomSystemPrompt = null;
|
||||||
if (this.chatPanel) {
|
if (this.chatPanel) {
|
||||||
await this.chatPanel.updateHistory(this.chatHistory);
|
await this.chatPanel.updateHistory(this.chatHistory);
|
||||||
|
this.chatPanel.updateCustomSystemPromptStatus(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обрабатывает выбор графа из `GraphView`. Устанавливает выбранный граф и обновляет
|
* Обрабатывает выбор графа из `GraphView`. Устанавливает выбранный граф и обновляет
|
||||||
* историю чата, загружая сообщения, относящиеся к этому графу.
|
|
||||||
* @param {{ graphId: string; currentNode: string | null }} data - Объект, содержащий ID выбранного графа и текущий узел.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
*/
|
||||||
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
|
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
|
||||||
this.selectedGraphId = data.graphId;
|
this.selectedGraphId = data.graphId;
|
||||||
this.currentNode = data.currentNode; // Может быть пустым, но это не проблема
|
this.currentNode = data.currentNode;
|
||||||
|
// Важно: custom_system_prompt будет обновлен через 'graph-custom-prompt-updated' событие,
|
||||||
|
// которое генерируется в GraphView после fetchGraphData.
|
||||||
|
// Здесь мы пока не трогаем this.currentGraphCustomSystemPrompt напрямую,
|
||||||
|
// но ожидаем, что GraphView его обновит.
|
||||||
await this.fetchMessagesFromRootToNode(data.currentNode);
|
await this.fetchMessagesFromRootToNode(data.currentNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Стримит ответ LLM.
|
* Стримит ответ LLM.
|
||||||
* @param {string} graphId - ID графа.
|
|
||||||
* @param {string} userNodeId - ID узла пользователя.
|
|
||||||
* @param {string} selectedModel - Выбранная модель.
|
|
||||||
* @param {string | null} existingAssistantNodeId - Существующий ID узла ассистента для повторного использования (для регенерации).
|
|
||||||
*/
|
*/
|
||||||
async streamLLMResponse(
|
async streamLLMResponse(
|
||||||
graphId: string,
|
graphId: string,
|
||||||
userNodeId: string,
|
userNodeId: string,
|
||||||
selectedModel?: string,
|
selectedModel?: string,
|
||||||
existingAssistantNodeId: string | null = null
|
existingAssistantNodeId: string | null = null,
|
||||||
|
systemPrompt: string | null = null
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const body = {
|
const body = {
|
||||||
graph_id: graphId,
|
graph_id: graphId,
|
||||||
user_node_id: userNodeId,
|
user_node_id: userNodeId,
|
||||||
system_prompt: this.plugin.settings.systemPrompt,
|
system_prompt: systemPrompt,
|
||||||
model: selectedModel || this.plugin.settings.defaultModel
|
model: selectedModel || this.plugin.settings.defaultModel
|
||||||
};
|
};
|
||||||
|
|
||||||
// Если есть существующий ID узла ассистента, добавляем его в тело запроса
|
|
||||||
if (existingAssistantNodeId) {
|
if (existingAssistantNodeId) {
|
||||||
Object.assign(body, { existing_assistant_node_id: existingAssistantNodeId });
|
Object.assign(body, { existing_assistant_node_id: existingAssistantNodeId });
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +283,7 @@ export class ChatView extends ItemView {
|
||||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/stream`, {
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/stream`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(body) // Используем модифицированное тело запроса
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -299,7 +313,6 @@ export class ChatView extends ItemView {
|
||||||
|
|
||||||
if (data.type === 'node_created') {
|
if (data.type === 'node_created') {
|
||||||
assistantNodeId = data.node_id;
|
assistantNodeId = data.node_id;
|
||||||
// Если узел переиспользуется, нужно очистить его содержимое в UI
|
|
||||||
if (data.reused) {
|
if (data.reused) {
|
||||||
this.clearStreamingMessage(assistantNodeId);
|
this.clearStreamingMessage(assistantNodeId);
|
||||||
}
|
}
|
||||||
|
|
@ -314,13 +327,12 @@ export class ChatView extends ItemView {
|
||||||
console.error('Ошибка:', data.error);
|
console.error('Ошибка:', data.error);
|
||||||
if (assistantNodeId) {
|
if (assistantNodeId) {
|
||||||
this.markNodeAsError(assistantNodeId, data.error);
|
this.markNodeAsError(assistantNodeId, data.error);
|
||||||
// Эмиттируем событие, чтобы GraphView обновил отображение узла с ошибкой
|
|
||||||
this.plugin.eventBus.emit('graph-selected', {
|
this.plugin.eventBus.emit('graph-selected', {
|
||||||
graphId: graphId,
|
graphId: graphId,
|
||||||
currentNode: assistantNodeId
|
currentNode: assistantNodeId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break; // Прекращаем обработку при ошибке
|
break;
|
||||||
} else if (data.type === 'done') {
|
} else if (data.type === 'done') {
|
||||||
this.currentNode = assistantNodeId;
|
this.currentNode = assistantNodeId;
|
||||||
this.plugin.eventBus.emit('graph-selected', {
|
this.plugin.eventBus.emit('graph-selected', {
|
||||||
|
|
@ -402,15 +414,20 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleRegenerateMessage(data: { graphId: string; nodeId: string; model: string }): Promise<void> {
|
async handleRegenerateMessage(data: { graphId: string; nodeId: string; model: string }): Promise<void> {
|
||||||
// НОВОЕ: Добавляем 'model' в тип данных
|
|
||||||
try {
|
try {
|
||||||
|
// Определяем системный промпт для отправки: кастомный для графа, если есть, иначе глобальный
|
||||||
|
const systemPromptToUse = this.currentGraphCustomSystemPrompt !== null ?
|
||||||
|
this.currentGraphCustomSystemPrompt :
|
||||||
|
this.plugin.settings.systemPrompt;
|
||||||
|
|
||||||
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/regenerate`, {
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/regenerate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
graph_id: data.graphId,
|
graph_id: data.graphId,
|
||||||
node_id: data.nodeId,
|
node_id: data.nodeId,
|
||||||
model: data.model // НОВОЕ: Передаем выбранную модель для регенерации
|
model: data.model,
|
||||||
|
system_prompt: systemPromptToUse
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -419,14 +436,13 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
// НОВОЕ: Получаем ID существующего узла ассистента и ID пользовательского узла
|
|
||||||
const existingAssistantNodeId = result.existing_assistant_node_id;
|
const existingAssistantNodeId = result.existing_assistant_node_id;
|
||||||
const userNodeId = result.user_node_id;
|
const userNodeId = result.user_node_id;
|
||||||
const modelToUse = result.model;
|
const modelToUse = result.model;
|
||||||
|
const systemPromptFromBackend = result.system_prompt;
|
||||||
|
|
||||||
// Запускаем стриминг, передавая ID существующего узла для повторного использования
|
|
||||||
if (data.graphId) {
|
if (data.graphId) {
|
||||||
await this.streamLLMResponse(data.graphId, userNodeId, modelToUse, existingAssistantNodeId);
|
await this.streamLLMResponse(data.graphId, userNodeId, modelToUse, existingAssistantNodeId, systemPromptFromBackend);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Ошибка регенерации:', error);
|
console.error('Ошибка регенерации:', error);
|
||||||
|
|
@ -434,16 +450,17 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обрабатывает событие начала нового независимого чата.
|
* Обрабатывает событие начала нового независимого чата.
|
||||||
* Сбрасывает текущее состояние ChatView, но не влияет на GraphView.
|
* Сбрасывает текущее состояние ChatView, но не влияет на GraphView.
|
||||||
*/
|
*/
|
||||||
async handleStartNewIndependentChat(): Promise<void> {
|
async handleStartNewIndependentChat(): Promise<void> {
|
||||||
this.chatHistory = [];
|
this.chatHistory = [];
|
||||||
this.currentNode = null;
|
this.currentNode = null;
|
||||||
// Оставляем this.selectedGraphId без изменений
|
// Оставляем this.selectedGraphId без изменений
|
||||||
if (this.chatPanel) {
|
if (this.chatPanel) {
|
||||||
await this.chatPanel.updateHistory(this.chatHistory);
|
await this.chatPanel.updateHistory(this.chatHistory);
|
||||||
|
this.chatPanel.updateCustomSystemPromptStatus(null);
|
||||||
}
|
}
|
||||||
// Опционально: можно уведомить пользователя о начале нового чата
|
// Опционально: можно уведомить пользователя о начале нового чата
|
||||||
new Notice('Начат новый чат. Текущий граф не изменен.', 2000);
|
new Notice('Начат новый чат. Текущий граф не изменен.', 2000);
|
||||||
|
|
@ -461,4 +478,19 @@ export class ChatView extends ItemView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleGraphCustomPromptUpdated(data: { graph_id: string; custom_system_prompt: string | null }): void {
|
||||||
|
if (this.selectedGraphId === data.graph_id) {
|
||||||
|
this.currentGraphCustomSystemPrompt = data.custom_system_prompt;
|
||||||
|
if (this.chatPanel) {
|
||||||
|
this.chatPanel.updateCustomSystemPromptStatus(this.currentGraphCustomSystemPrompt);
|
||||||
|
}
|
||||||
|
} else if (this.selectedGraphId === null && data.graph_id === null) {
|
||||||
|
// Сброс промпта, если граф не выбран
|
||||||
|
this.currentGraphCustomSystemPrompt = null;
|
||||||
|
if (this.chatPanel) {
|
||||||
|
this.chatPanel.updateCustomSystemPromptStatus(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,11 +7,13 @@
|
||||||
import { ItemView, WorkspaceLeaf, Notice } from 'obsidian';
|
import { ItemView, WorkspaceLeaf, Notice } from 'obsidian';
|
||||||
// ----------------------------------------------- Plugin Imports ------------------------------------------------------------
|
// ----------------------------------------------- Plugin Imports ------------------------------------------------------------
|
||||||
import LLMAgentPlugin from 'main'; // Предполагаем, что main.ts экспортирует LLMAgentPlugin по умолчанию
|
import LLMAgentPlugin from 'main'; // Предполагаем, что main.ts экспортирует LLMAgentPlugin по умолчанию
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
import { HistoryPanel } from '../components/HistoryPanel'; // Предполагаем, что HistoryPanel это JS-класс
|
import { HistoryPanel } from '../components/HistoryPanel'; // Предполагаем, что HistoryPanel это JS-класс
|
||||||
import { GraphPanelComponent } from '../components/GraphPanel'; // Импортируем React-компонент и ReactDOM
|
import { GraphPanelComponent } from '../components/GraphPanel'; // Импортируем React-компонент и ReactDOM
|
||||||
import * as React from 'react';
|
|
||||||
import * as ReactDOM from 'react-dom/client'; // <-- ИСПРАВЛЕНО: Импортируем 'react-dom/client' для createRoot
|
|
||||||
import { Dialog } from 'src/utils/Dialog';
|
import { Dialog } from 'src/utils/Dialog';
|
||||||
|
import { GraphSettingsModal } from '../modals/GraphSettingsModal';
|
||||||
|
|
||||||
export const GRAPH_VIEW_TYPE = 'llm-agent-graph-view';
|
export const GRAPH_VIEW_TYPE = 'llm-agent-graph-view';
|
||||||
|
|
||||||
|
|
@ -25,6 +27,7 @@ export class GraphView extends ItemView {
|
||||||
selectedGraphId: string | null;
|
selectedGraphId: string | null;
|
||||||
selectedNodeIds: Set<string>; // Добавляем Set для хранения ID выделенных узлов
|
selectedNodeIds: Set<string>; // Добавляем Set для хранения ID выделенных узлов
|
||||||
selectedGraphTitle: string | null;
|
selectedGraphTitle: string | null;
|
||||||
|
currentGraphCustomSystemPrompt: string | null;
|
||||||
|
|
||||||
// UI Elements
|
// UI Elements
|
||||||
historyPanel: HistoryPanel | null;
|
historyPanel: HistoryPanel | null;
|
||||||
|
|
@ -38,6 +41,7 @@ export class GraphView extends ItemView {
|
||||||
copySelectedButton: HTMLButtonElement | null = null;
|
copySelectedButton: HTMLButtonElement | null = null;
|
||||||
deleteSelectedButton: HTMLButtonElement | null = null;
|
deleteSelectedButton: HTMLButtonElement | null = null;
|
||||||
newIndependentChatActionButton: HTMLButtonElement | null = null;
|
newIndependentChatActionButton: HTMLButtonElement | null = null;
|
||||||
|
graphSettingsButton: HTMLButtonElement | null = null;
|
||||||
|
|
||||||
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
constructor(leaf: WorkspaceLeaf, plugin: LLMAgentPlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
|
|
@ -49,8 +53,9 @@ export class GraphView extends ItemView {
|
||||||
this.historyPanel = null;
|
this.historyPanel = null;
|
||||||
this.reactRoot = null;
|
this.reactRoot = null;
|
||||||
this.isHistoryCollapsed = true;
|
this.isHistoryCollapsed = true;
|
||||||
this.selectedNodeIds = new Set<string>(); // Инициализируем Set
|
this.selectedNodeIds = new Set<string>();
|
||||||
this.selectedGraphTitle = null;
|
this.selectedGraphTitle = null;
|
||||||
|
this.currentGraphCustomSystemPrompt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -210,7 +215,6 @@ export class GraphView extends ItemView {
|
||||||
const toolbar = this.graphContainer.querySelector('.graph-view-toolbar');
|
const toolbar = this.graphContainer.querySelector('.graph-view-toolbar');
|
||||||
if (!toolbar) return;
|
if (!toolbar) return;
|
||||||
|
|
||||||
// Кнопка "Копировать выделенные"
|
|
||||||
this.copySelectedButton = toolbar.createEl('button', {
|
this.copySelectedButton = toolbar.createEl('button', {
|
||||||
cls: 'global-action-button',
|
cls: 'global-action-button',
|
||||||
attr: { 'aria-label': 'Копировать выделенные узлы', 'data-tooltip': 'Копировать выделенные узлы' },
|
attr: { 'aria-label': 'Копировать выделенные узлы', 'data-tooltip': 'Копировать выделенные узлы' },
|
||||||
|
|
@ -218,7 +222,6 @@ export class GraphView extends ItemView {
|
||||||
});
|
});
|
||||||
this.copySelectedButton.addEventListener('click', this.handleCopySelectedNodes.bind(this));
|
this.copySelectedButton.addEventListener('click', this.handleCopySelectedNodes.bind(this));
|
||||||
|
|
||||||
// Кнопка "Удалить выделенные"
|
|
||||||
this.deleteSelectedButton = toolbar.createEl('button', {
|
this.deleteSelectedButton = toolbar.createEl('button', {
|
||||||
cls: 'global-action-button',
|
cls: 'global-action-button',
|
||||||
attr: { 'aria-label': 'Удалить выделенные узлы', 'data-tooltip': 'Удалить выделенные узлы' },
|
attr: { 'aria-label': 'Удалить выделенные узлы', 'data-tooltip': 'Удалить выделенные узлы' },
|
||||||
|
|
@ -226,20 +229,79 @@ export class GraphView extends ItemView {
|
||||||
});
|
});
|
||||||
this.deleteSelectedButton.addEventListener('click', this.handleDeleteSelectedNodes.bind(this));
|
this.deleteSelectedButton.addEventListener('click', this.handleDeleteSelectedNodes.bind(this));
|
||||||
|
|
||||||
// Кнопка "Новый чат" (сохраняя текущий граф)
|
this.newIndependentChatActionButton = toolbar.createEl('button', {
|
||||||
this.newIndependentChatActionButton = toolbar.createEl('button', {
|
cls: 'global-action-button',
|
||||||
cls: 'global-action-button',
|
attr: { 'aria-label': 'Новый независимый чат', 'data-tooltip': 'Начать новую беседу, формируя отдельный, независимый подграф' },
|
||||||
attr: { 'aria-label': 'Новый независимый чат', 'data-tooltip': 'Начать новую беседу, формируя отдельный, независимый подграф' },
|
text: '✨' // Или 'Новый чат'
|
||||||
text: '✨' // Или 'Новый чат'
|
});
|
||||||
});
|
this.newIndependentChatActionButton.addEventListener('click', () => {
|
||||||
this.newIndependentChatActionButton.addEventListener('click', () => {
|
// Эмитируем новое событие, которое будет слушать ТОЛЬКО ChatView
|
||||||
// Эмитируем новое событие, которое будет слушать ТОЛЬКО ChatView
|
this.plugin.eventBus.emit('start-new-independent-chat');
|
||||||
this.plugin.eventBus.emit('start-new-independent-chat');
|
});
|
||||||
});
|
|
||||||
|
this.graphSettingsButton = toolbar.createEl('button', {
|
||||||
|
cls: 'global-action-button',
|
||||||
|
attr: { 'aria-label': 'Настройки графа', 'data-tooltip': 'Настроить системный промпт для текущего графа' },
|
||||||
|
text: '⚙️' // Иконка шестеренки
|
||||||
|
});
|
||||||
|
this.graphSettingsButton.addEventListener('click', this.handleGraphSettingsClick.bind(this));
|
||||||
|
// Деактивируем кнопку по умолчанию, пока граф не выбран
|
||||||
|
this.graphSettingsButton.setAttr('disabled', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------- Graph Panel Integration --------------------------------------------------
|
// ----------------------------------------------- Graph Panel Integration --------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработчик клика по кнопке "Настройки графа".
|
||||||
|
*/
|
||||||
|
handleGraphSettingsClick(): void {
|
||||||
|
if (!this.selectedGraphId || !this.selectedGraphTitle) {
|
||||||
|
new Notice('Сначала выберите граф для настройки.', 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new GraphSettingsModal(this.app, {
|
||||||
|
app: this.app,
|
||||||
|
graphId: this.selectedGraphId,
|
||||||
|
graphTitle: this.selectedGraphTitle,
|
||||||
|
initialCustomSystemPrompt: this.currentGraphCustomSystemPrompt,
|
||||||
|
onSave: this.saveGraphCustomSystemPrompt.bind(this)
|
||||||
|
}).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сохраняет пользовательский системный промпт для текущего графа.
|
||||||
|
* @param {string} graphId - ID графа.
|
||||||
|
* @param {string | null} customSystemPrompt - Новый системный промпт или null для сброса.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async saveGraphCustomSystemPrompt(graphId: string, customSystemPrompt: string | null): Promise<void> {
|
||||||
|
if (!graphId) {
|
||||||
|
new Notice('Граф не выбран для сохранения настроек.', 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/${graphId}/settings`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ custom_system_prompt: customSystemPrompt })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Ошибка сохранения системного промпта: ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Notice('Системный промпт графа успешно обновлен.', 2000);
|
||||||
|
// Обновляем данные графа, чтобы получить актуальный промпт и перерисовать UI
|
||||||
|
await this.fetchGraphData(graphId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Ошибка при сохранении системного промпта для графа ${graphId}:`, error);
|
||||||
|
new Notice(`Ошибка при сохранении системного промпта: ${error.message}`, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инициализирует GraphPanelComponent (React компонент) в graphContainer.
|
* Инициализирует GraphPanelComponent (React компонент) в graphContainer.
|
||||||
* Использует ReactDOM.createRoot для React 18.
|
* Использует ReactDOM.createRoot для React 18.
|
||||||
|
|
@ -278,9 +340,9 @@ export class GraphView extends ItemView {
|
||||||
edges: this.graphData.edges
|
edges: this.graphData.edges
|
||||||
},
|
},
|
||||||
onNodeClick: this.handleNodeClick.bind(this),
|
onNodeClick: this.handleNodeClick.bind(this),
|
||||||
onNodeSelectionChange: this.handleNodeSelectionChange.bind(this), // Передаем обработчик изменения выделения
|
onNodeSelectionChange: this.handleNodeSelectionChange.bind(this),
|
||||||
onNodeDelete: this.handleDeleteNode.bind(this), // Передаем колбэк для удаления одиночного узла
|
onNodeDelete: this.handleDeleteNode.bind(this),
|
||||||
selectedNodeIds: this.selectedNodeIds, // Передаем Set выделенных узлов
|
selectedNodeIds: this.selectedNodeIds,
|
||||||
activeNodeId: this.currentNode,
|
activeNodeId: this.currentNode,
|
||||||
app: this.plugin.app
|
app: this.plugin.app
|
||||||
})
|
})
|
||||||
|
|
@ -329,7 +391,11 @@ export class GraphView extends ItemView {
|
||||||
this.selectedGraphTitle = null; // Сброс заголовка
|
this.selectedGraphTitle = null; // Сброс заголовка
|
||||||
this.updateTitle(); // Обновляем заголовок
|
this.updateTitle(); // Обновляем заголовок
|
||||||
this.renderGraphPanel();
|
this.renderGraphPanel();
|
||||||
this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null });
|
// Нет подписчиков: this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null });
|
||||||
|
this.plugin.eventBus.emit('graph-custom-prompt-updated', { graph_id: null, custom_system_prompt: null }); // Уведомляем ChatView о сбросе
|
||||||
|
if (this.graphSettingsButton) {
|
||||||
|
this.graphSettingsButton.setAttr('disabled', 'true');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,22 +417,36 @@ export class GraphView extends ItemView {
|
||||||
|
|
||||||
// Устанавливаем заголовок графа
|
// Устанавливаем заголовок графа
|
||||||
this.selectedGraphTitle = data.title || data.first_message || `Граф ${graphId.substring(0, 8)}...`;
|
this.selectedGraphTitle = data.title || data.first_message || `Граф ${graphId.substring(0, 8)}...`;
|
||||||
this.updateTitle(); // Обновляем заголовок
|
this.updateTitle();
|
||||||
|
|
||||||
|
this.currentGraphCustomSystemPrompt = data.custom_system_prompt || null;
|
||||||
|
|
||||||
|
if (this.graphSettingsButton) {
|
||||||
|
this.graphSettingsButton.setAttr('disabled', null); // Активируем
|
||||||
|
}
|
||||||
|
|
||||||
// Перерендериваем граф
|
// Перерендериваем граф
|
||||||
this.renderGraphPanel();
|
this.renderGraphPanel();
|
||||||
|
|
||||||
// Также обновляем ChatView с полученными сообщениями и текущим узлом
|
// Также обновляем ChatView с полученными сообщениями и текущим узлом
|
||||||
this.plugin.eventBus.emit('chat-history-updated', {
|
/* Нет подписчиков: this.plugin.eventBus.emit('chat-history-updated', {
|
||||||
graphId: graphId,
|
graphId: graphId,
|
||||||
messages: data.messages || [],
|
messages: data.messages || [],
|
||||||
current_node_id: data.current_node_id // Передаем актуальный current_node_id
|
current_node_id: data.current_node_id // Передаем актуальный current_node_id
|
||||||
|
});*/
|
||||||
|
this.plugin.eventBus.emit('graph-custom-prompt-updated', {
|
||||||
|
graph_id: graphId,
|
||||||
|
custom_system_prompt: this.currentGraphCustomSystemPrompt
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки данных графа:', error);
|
console.error('Ошибка загрузки данных графа:', error);
|
||||||
this.selectedGraphTitle = null;
|
this.selectedGraphTitle = null;
|
||||||
|
this.currentGraphCustomSystemPrompt = null; // Сброс кастомного промпта при ошибке
|
||||||
this.updateTitle(); // Обновляем заголовок
|
this.updateTitle(); // Обновляем заголовок
|
||||||
// Optionally: show a notice to the user
|
if (this.graphSettingsButton) {
|
||||||
// new Notice(`Ошибка загрузки данных графа: ${error.message}`);
|
this.graphSettingsButton.setAttr('disabled', 'true');
|
||||||
|
}
|
||||||
|
this.plugin.eventBus.emit('graph-custom-prompt-updated', { graph_id: graphId, custom_system_prompt: null });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -440,33 +520,63 @@ export class GraphView extends ItemView {
|
||||||
* Обработчик начала нового чата.
|
* Обработчик начала нового чата.
|
||||||
* Очищает текущие данные графа и перерендеривает его.
|
* Очищает текущие данные графа и перерендеривает его.
|
||||||
*/
|
*/
|
||||||
handleNewChat(): void {
|
async handleNewChat(): Promise<void> {
|
||||||
this.graphData = { nodes: [], edges: [] };
|
this.graphData = { nodes: [], edges: [] };
|
||||||
this.currentNode = null;
|
this.currentNode = null;
|
||||||
this.selectedGraphId = null;
|
this.selectedNodeIds.clear();
|
||||||
this.selectedNodeIds.clear(); // Очищаем выделения
|
this.selectedGraphTitle = null;
|
||||||
this.selectedGraphTitle = null; // Сброс заголовка при новом чате
|
this.currentGraphCustomSystemPrompt = null;
|
||||||
this.updateTitle(); // Обновляем заголовок
|
|
||||||
this.renderGraphPanel();
|
try {
|
||||||
this.fetchGraphsList(); // Обновить список графов, чтобы показать новый пустой чат
|
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/graphs/new`, {
|
||||||
this.plugin.eventBus.emit('chat-history-updated', { graphId: null, messages: [], current_node_id: null });
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка при создании нового графа на бэкенде: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
const newGraphId = result.graph_id;
|
||||||
|
|
||||||
|
this.selectedGraphId = newGraphId;
|
||||||
|
this.selectedGraphTitle = 'Новый граф'; // Будет обновлен fetchGraphData, если бэкенд вернет title
|
||||||
|
|
||||||
|
this.plugin.eventBus.emit('graph-selected', { graphId: this.selectedGraphId, currentNode: null });
|
||||||
|
|
||||||
|
this.fetchGraphsList(); // Обновляем список графов в HistoryPanel
|
||||||
|
|
||||||
|
// Нет подписчиков: this.plugin.eventBus.emit('chat-history-updated', { graphId: this.selectedGraphId, messages: [], current_node_id: null });
|
||||||
|
this.plugin.eventBus.emit('graph-custom-prompt-updated', { graph_id: this.selectedGraphId, custom_system_prompt: null });
|
||||||
|
|
||||||
|
if (this.graphSettingsButton) {
|
||||||
|
this.graphSettingsButton.setAttr('disabled', null);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Ошибка при создании нового чата:', error);
|
||||||
|
new Notice(`Ошибка при создании нового чата: ${error?.message || error}`, 5000);
|
||||||
|
// Сброс состояния в случае ошибки, чтобы не было "фантомного" графа
|
||||||
|
this.selectedGraphId = null;
|
||||||
|
this.selectedGraphTitle = null;
|
||||||
|
this.updateTitle();
|
||||||
|
if (this.graphSettingsButton) {
|
||||||
|
this.graphSettingsButton.setAttr('disabled', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик выбора графа из панели истории.
|
* Обработчик выбора графа из панели истории.
|
||||||
* Устанавливает выбранный граф и загружает его данные.
|
|
||||||
* @param {{ graphId: string; currentNode: string | null }} data - Объект, содержащий ID выбранного графа и текущий узел.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
*/
|
||||||
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
|
async handleGraphSelected(data: { graphId: string; currentNode: string | null }): Promise<void> {
|
||||||
this.selectedGraphId = data.graphId;
|
this.selectedGraphId = data.graphId;
|
||||||
this.selectedNodeIds.clear(); // Очищаем выделения при смене графа
|
this.selectedNodeIds.clear();
|
||||||
await this.fetchGraphData(data.graphId);
|
await this.fetchGraphData(data.graphId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик события удаления узла из ChatPanel.
|
* Обработчик события удаления узла из ChatPanel.
|
||||||
* @param {{ graphId: string, nodeId: string }} data - Данные об удаляемом узле.
|
|
||||||
*/
|
*/
|
||||||
async handleDeleteNodeFromChat(data: { graphId: string; nodeId: string }): Promise<void> {
|
async handleDeleteNodeFromChat(data: { graphId: string; nodeId: string }): Promise<void> {
|
||||||
if (data.graphId === this.selectedGraphId) {
|
if (data.graphId === this.selectedGraphId) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user