Add support of temperature and max tokens parameters

This commit is contained in:
dimitrievgs 2026-05-11 16:58:36 +03:00
parent 43d855a27d
commit 9c5706bd86
6 changed files with 326 additions and 53 deletions

156
main.ts
View File

@ -10,6 +10,8 @@ import { ReferenceParser } from './src/references/ReferenceParser';
import 'src/css/styles.css'; import 'src/css/styles.css';
import { LLM_DEFAULTS } from 'src/constants';
// ----------------------------------------------- Settings --------------------------------------------------------------- // ----------------------------------------------- Settings ---------------------------------------------------------------
interface LLMAgentSettings { interface LLMAgentSettings {
@ -22,29 +24,38 @@ interface LLMAgentSettings {
excludedFolders: string[]; excludedFolders: string[];
refCacheFolder: string; refCacheFolder: string;
temperatureChat: number;
maxTokensChatDefault: string;
summarizationModel: string; summarizationModel: string;
voiceCommandModel: string; voiceCommandModel: string;
logsFolder: string; logsFolder: string;
voiceInterval: number; voiceInterval: number;
metricsInterval: number; metricsInterval: number;
voiceDisplayLimit: number; // 3000 voiceDisplayLimit: number;
voiceContextLimit: number; // N2 символов для LLM voiceContextLimit: number;
temperatureVoice: number;
maxTokensVoiceRegular: string;
maxTokensVoiceCommands: string;
promptRegular: string; promptRegular: string;
promptCommands: string; promptCommands: string;
promptMetrics: string; promptMetrics: string;
markerStart: string; // "старт" markerStart: string;
markerStop: string; // "стоп" markerStop: string;
} }
const DEFAULT_SETTINGS: LLMAgentSettings = { const DEFAULT_SETTINGS: LLMAgentSettings = {
apiBaseUrl: 'http://localhost:5000/api', apiBaseUrl: 'http://localhost:5000/api',
systemPrompt: 'Ты полезный ИИ ассистент.', systemPrompt: 'Ты полезный ИИ ассистент.',
defaultModel: 'gemini-2.5-flash-r', defaultModel: LLM_DEFAULTS.MODEL,
promptsFolder: 'prompts', // Папка с промптами по умолчанию promptsFolder: 'prompts',
enableFileReferences: true, enableFileReferences: true,
enablePromptReferences: true, enablePromptReferences: true,
excludedFolders: ['.obsidian', 'llm-agent-backend', 'templates', 'ref-cache'], // Папка "templates" по умолчанию исключена excludedFolders: ['.obsidian', 'llm-agent-backend', 'templates', 'ref-cache'],
refCacheFolder: 'ref-cache', // По умолчанию относительно vault refCacheFolder: 'ref-cache',
temperatureChat: LLM_DEFAULTS.TEMPERATURE,
maxTokensChatDefault: '',
summarizationModel: '', summarizationModel: '',
voiceCommandModel: '', voiceCommandModel: '',
@ -53,6 +64,9 @@ const DEFAULT_SETTINGS: LLMAgentSettings = {
metricsInterval: 15, metricsInterval: 15,
voiceDisplayLimit: 30000, voiceDisplayLimit: 30000,
voiceContextLimit: 30000, voiceContextLimit: 30000,
temperatureVoice: LLM_DEFAULTS.TEMPERATURE,
maxTokensVoiceRegular: '',
maxTokensVoiceCommands: '',
promptRegular: '', promptRegular: '',
promptCommands: '', promptCommands: '',
promptMetrics: '', promptMetrics: '',
@ -116,7 +130,7 @@ export default class LLMAgentPlugin extends Plugin {
} catch (error) { } catch (error) {
console.error('Ошибка загрузки доступных моделей:', error); console.error('Ошибка загрузки доступных моделей:', error);
// Fallback к базовым моделям // Fallback к базовым моделям
this.availableModels = ['gemini-2.5-flash', 'gpt-4o-mini', 'claude-3-5-sonnet-20241022']; this.availableModels = [LLM_DEFAULTS.MODEL, 'gpt-4o-mini', 'claude-3-5-sonnet-20241022'];
} }
} }
@ -297,6 +311,38 @@ class SampleSettingTab extends PluginSettingTab {
}); });
}); });
// Этот промпт будет отправляться LLM перед каждым диалогом
this.addPromptAutocomplete(new Setting(containerEl).setName('Системный промпт'), 'systemPrompt');
new Setting(containerEl)
.setName('Температура чата по умолчанию')
.setDesc(`Значение от 0.0 до 2.0 (по умолчанию ${LLM_DEFAULTS.TEMPERATURE}). Влияет на креативность.`)
.addText((text) =>
text
.setPlaceholder(String(LLM_DEFAULTS.TEMPERATURE))
.setValue(String(this.plugin.settings.temperatureChat))
.onChange(async (value) => {
const num = parseFloat(value);
this.plugin.settings.temperatureChat = isNaN(num) ? LLM_DEFAULTS.TEMPERATURE : num;
await this.plugin.saveSettings();
await this.plugin.syncSettings();
})
);
new Setting(containerEl)
.setName('Макс. токенов чата по умолчанию')
.setDesc('Оставьте пустым для отсутствия лимита.')
.addText((text) =>
text
.setPlaceholder('Лимит')
.setValue(this.plugin.settings.maxTokensChatDefault)
.onChange(async (value) => {
this.plugin.settings.maxTokensChatDefault = value;
await this.plugin.saveSettings();
await this.plugin.syncSettings();
})
);
new Setting(containerEl) new Setting(containerEl)
.setName('API Base URL') .setName('API Base URL')
.setDesc('Базовый URL для API LLM Agent') .setDesc('Базовый URL для API LLM Agent')
@ -311,9 +357,6 @@ class SampleSettingTab extends PluginSettingTab {
}) })
); );
// Этот промпт будет отправляться LLM перед каждым диалогом
this.addPromptAutocomplete(new Setting(containerEl).setName('Системный промпт'), 'systemPrompt');
containerEl.createEl('h2', { text: 'Голосовое управление (Voice Service)' }); containerEl.createEl('h2', { text: 'Голосовое управление (Voice Service)' });
new Setting(containerEl) new Setting(containerEl)
@ -329,16 +372,61 @@ class SampleSettingTab extends PluginSettingTab {
}); });
}); });
// Инструкции для Промпта №1.
this.addPromptAutocomplete(
new Setting(containerEl).setName('Регулярный промпт'),
'promptRegular'
);
// Инструкции для Промпта №2.
this.addPromptAutocomplete(
new Setting(containerEl).setName('Промпт команд'),
'promptCommands'
);
this.addPromptAutocomplete(
new Setting(containerEl).setName('Промпт качественной аналитики'),
'promptMetrics'
);
new Setting(containerEl) new Setting(containerEl)
.setName('Папка аудио-логов') .setName('Температура Voice')
.setDesc('Папка внутри Obsidian, куда будут сохраняться текстовые расшифровки (в формате .md). Относительно корня хранилища.') .setDesc(`Температура для голосовых запросов (по умолчанию ${LLM_DEFAULTS.TEMPERATURE})`)
.addText((text) => .addText((text) =>
text text
.setPlaceholder('audio-logs') .setPlaceholder(String(LLM_DEFAULTS.TEMPERATURE))
.setValue(this.plugin.settings.logsFolder) .setValue(String(this.plugin.settings.temperatureVoice))
.onChange(async (value) => { .onChange(async (value) => {
// Убираем лишние слэши для корректности пути const num = parseFloat(value);
this.plugin.settings.logsFolder = value.replace(/^\/+|\/+$/g, '').trim(); this.plugin.settings.temperatureVoice = isNaN(num) ? LLM_DEFAULTS.TEMPERATURE : num;
await this.plugin.saveSettings();
await this.plugin.syncSettings();
})
);
new Setting(containerEl)
.setName('Макс. токенов Voice (Регулярный)')
.setDesc('Лимит токенов для регулярного анализа. Оставьте пустым для безлимита.')
.addText((text) =>
text
.setPlaceholder('Лимит')
.setValue(this.plugin.settings.maxTokensVoiceRegular)
.onChange(async (value) => {
this.plugin.settings.maxTokensVoiceRegular = value;
await this.plugin.saveSettings();
await this.plugin.syncSettings();
})
);
new Setting(containerEl)
.setName('Макс. токенов Voice (Команды)')
.setDesc('Лимит токенов для голосовых команд. Оставьте пустым для безлимита.')
.addText((text) =>
text
.setPlaceholder('Лимит')
.setValue(this.plugin.settings.maxTokensVoiceCommands)
.onChange(async (value) => {
this.plugin.settings.maxTokensVoiceCommands = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
await this.plugin.syncSettings(); await this.plugin.syncSettings();
}) })
@ -370,6 +458,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('Сколько последних символов транскрибации отправлять в модель.') .setDesc('Сколько последних символов транскрибации отправлять в модель.')
@ -411,23 +514,6 @@ class SampleSettingTab extends PluginSettingTab {
}) })
); );
// Инструкции для Промпта №1.
this.addPromptAutocomplete(
new Setting(containerEl).setName('Регулярный промпт'),
'promptRegular'
);
// Инструкции для Промпта №2.
this.addPromptAutocomplete(
new Setting(containerEl).setName('Промпт команд'),
'promptCommands'
);
this.addPromptAutocomplete(
new Setting(containerEl).setName('Промпт качественной аналитики'),
'promptMetrics'
);
containerEl.createEl('h2', { text: 'Управление ссылками и файлами' }); containerEl.createEl('h2', { text: 'Управление ссылками и файлами' });
new Setting(containerEl) new Setting(containerEl)

View File

@ -16,9 +16,11 @@ import { MarkdownRenderer, setIcon, Notice, TFile, FileSystemAdapter } from 'obs
import { Dialog } from 'src/utils/Dialog'; import { Dialog } from 'src/utils/Dialog';
import { CacheManager } from 'src/references/CacheManager'; import { CacheManager } from 'src/references/CacheManager';
import { LLM_DEFAULTS } from 'src/constants';
interface ChatPanelProps { interface ChatPanelProps {
chatHistory: ChatHistoryItem[]; chatHistory: ChatHistoryItem[];
onSendMessage: (message: string, selectedModel?: string, attachments?: Attachment[]) => void; onSendMessage: (message: string, selectedModel?: string, attachments?: Attachment[], customMaxTokens?: number, customTemperature?: number) => void;
selectedModel?: string; selectedModel?: string;
plugin: LLMAgentPlugin; plugin: LLMAgentPlugin;
currentGraphCustomSystemPrompt: string | null; currentGraphCustomSystemPrompt: string | null;
@ -38,6 +40,11 @@ export class ChatPanel {
currentStreamingNodeId: string | null; currentStreamingNodeId: string | null;
stopButton: HTMLButtonElement; stopButton: HTMLButtonElement;
maxTokensInput: HTMLInputElement;
maxTokensResetBtn: HTMLButtonElement;
tempInput: HTMLInputElement;
tempResetBtn: HTMLButtonElement;
// Элементы для файловых вложений // Элементы для файловых вложений
attachFileButton: HTMLButtonElement; attachFileButton: HTMLButtonElement;
fileInput: HTMLInputElement; fileInput: HTMLInputElement;
@ -58,7 +65,7 @@ export class ChatPanel {
this.container = container; this.container = container;
this.props = props; this.props = props;
this.chatHistory = props.chatHistory || []; this.chatHistory = props.chatHistory || [];
this.selectedModel = props.selectedModel || 'gemini-2.5-flash'; this.selectedModel = props.selectedModel || LLM_DEFAULTS.MODEL;
// Инициализация системы ссылок // Инициализация системы ссылок
this.initializeReferenceSystem(); this.initializeReferenceSystem();
@ -466,6 +473,16 @@ export class ChatPanel {
</span> </span>
</div> </div>
<div class="param-select-container" title="Температура">
<span class="param-icon">Temp: </span>
<input type="number" id="temp-input" class="param-input" placeholder="${LLM_DEFAULTS.TEMPERATURE}" min="0" max="2" step="0.1">
<button id="temp-reset" class="param-reset-btn" title="Сбросить до дефолтной"></button>
</div>
<div class="param-select-container" title="Максимальное количество токенов в ответе">
<span class="param-icon">Tokens: </span>
<input type="number" id="max-tokens-input" class="param-input" placeholder="Лимит" min="1" step="100">
<button id="max-tokens-reset" class="param-reset-btn" title="Сбросить до лимита из настроек"></button>
</div>
</div> </div>
<div class="control-buttons-right"> <div class="control-buttons-right">
<button class="stop-button" id="stop-button" style="display: none;"> <button class="stop-button" id="stop-button" style="display: none;">
@ -491,6 +508,20 @@ export class ChatPanel {
this.attachedFilesContainer = this.container.querySelector('#attached-files-container') as HTMLDivElement; this.attachedFilesContainer = this.container.querySelector('#attached-files-container') as HTMLDivElement;
this.tempInput = this.container.querySelector('#temp-input') as HTMLInputElement;
this.tempResetBtn = this.container.querySelector('#temp-reset') as HTMLButtonElement;
this.maxTokensInput = this.container.querySelector('#max-tokens-input') as HTMLInputElement;
this.maxTokensResetBtn = this.container.querySelector('#max-tokens-reset') as HTMLButtonElement;
const defaultTemp = (this.props.plugin.settings as any).temperatureChat ?? DEFAULT_TEMPERATURE;
this.tempInput.value = defaultTemp.toString();
// Устанавливаем дефолтное значение для токенов
const defaultTokens = (this.props.plugin.settings as any).maxTokensChatDefault;
if (defaultTokens) {
this.maxTokensInput.value = defaultTokens.toString();
}
this.setupEventListeners(); this.setupEventListeners();
this.setupReferenceSystem(); this.setupReferenceSystem();
this.renderHistory(); this.renderHistory();
@ -560,6 +591,16 @@ export class ChatPanel {
this.stopButton.addEventListener('click', () => this.handleStopGeneration()); this.stopButton.addEventListener('click', () => this.handleStopGeneration());
this.maxTokensResetBtn.addEventListener('click', () => {
const def = (this.props.plugin.settings as any).maxTokensChatDefault;
this.maxTokensInput.value = def ? def.toString() : '';
});
this.tempResetBtn.addEventListener('click', () => {
const def = (this.props.plugin.settings as any).temperatureChat ?? DEFAULT_TEMPERATURE;
this.tempInput.value = def.toString();
});
// Close dropdown when clicking outside // Close dropdown when clicking outside
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (!this.modelSelectButton.contains(e.target as Node) && !this.modelDropdown.contains(e.target as Node)) { if (!this.modelSelectButton.contains(e.target as Node) && !this.modelDropdown.contains(e.target as Node)) {
@ -904,9 +945,15 @@ export class ChatPanel {
// Объединяем вложения из ссылок и текущие (из attached-files-container) // Объединяем вложения из ссылок и текущие (из attached-files-container)
const allAttachments = [...attachments, ...this.currentAttachments]; const allAttachments = [...attachments, ...this.currentAttachments];
// Отправляем оригинальный текст и все вложения // Отправляем оригинальный текст и все вложения, включая лимит токенов
if (this.props.onSendMessage) { if (this.props.onSendMessage) {
this.props.onSendMessage(originalText || '', this.selectedModel, allAttachments); const valTokens = this.maxTokensInput?.value;
const parsedMaxTokens = valTokens ? parseInt(valTokens, 10) : undefined;
const valTemp = this.tempInput?.value;
const parsedTemp = valTemp ? parseFloat(valTemp) : undefined;
this.props.onSendMessage(originalText || '', this.selectedModel, allAttachments, parsedMaxTokens, parsedTemp);
} }
this.clearInput(); this.clearInput();
@ -938,7 +985,13 @@ export class ChatPanel {
handleSend(): void { handleSend(): void {
const message = this.messageInput.textContent?.trim() || ''; const message = this.messageInput.textContent?.trim() || '';
if (message && this.props.onSendMessage) { if (message && this.props.onSendMessage) {
this.props.onSendMessage(message, this.selectedModel, this.currentAttachments); const valTokens = this.maxTokensInput?.value;
const parsedMaxTokens = valTokens ? parseInt(valTokens, 10) : undefined;
const valTemp = this.tempInput?.value;
const parsedTemp = valTemp ? parseFloat(valTemp) : undefined;
this.props.onSendMessage(message, this.selectedModel, this.currentAttachments, parsedMaxTokens, parsedTemp);
this.clearInput(); this.clearInput();
} }
} }
@ -1019,8 +1072,19 @@ export class ChatPanel {
const regenerateIconContainer = regenerateButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' }); const regenerateIconContainer = regenerateButton.createSpan({ cls: 'flex items-center justify-center touch:w-10 h-8 w-8' });
setIcon(regenerateIconContainer, 'refresh-cw'); setIcon(regenerateIconContainer, 'refresh-cw');
regenerateButton.addEventListener('click', async () => { regenerateButton.addEventListener('click', async () => {
// Передаем выбранную модель при регенерации const valTokens = this.maxTokensInput?.value;
this.props.plugin.eventBus.emit('regenerate-message', { graphId, nodeId, model: this.selectedModel }); const parsedMaxTokens = valTokens ? parseInt(valTokens, 10) : undefined;
const valTemp = this.tempInput?.value;
const parsedTemp = valTemp ? parseFloat(valTemp) : undefined;
this.props.plugin.eventBus.emit('regenerate-message', {
graphId,
nodeId,
model: this.selectedModel,
maxTokens: parsedMaxTokens,
temperature: parsedTemp
});
}); });
// Кнопка "Начать новую ветку отсюда" // Кнопка "Начать новую ветку отсюда"

View File

@ -22,6 +22,8 @@ import ReactFlow, {
import { App, setIcon } from 'obsidian'; import { App, setIcon } from 'obsidian';
import { Dialog } from 'src/utils/Dialog'; import { Dialog } from 'src/utils/Dialog';
import { LLM_DEFAULTS } from 'src/constants';
// <-- ИСПРАВЛЕНО: Это ключевой момент. Убедитесь, что esbuild настроен на обработку CSS импортов. // <-- ИСПРАВЛЕНО: Это ключевой момент. Убедитесь, что esbuild настроен на обработку CSS импортов.
// Обычно, при использовании esbuild для React-проектов, вы импортируете CSS прямо в JS/TS файл. // Обычно, при использовании esbuild для React-проектов, вы импортируете CSS прямо в JS/TS файл.
// esbuild должен быть настроен для копирования или встраивания этого CSS. // esbuild должен быть настроен для копирования или встраивания этого CSS.
@ -98,10 +100,18 @@ const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable,
// Используем EventBus вместо workspace.trigger // Используем EventBus вместо workspace.trigger
const eventBus = (data.app as any).plugins?.plugins?.['llm-agent-plugin']?.eventBus; const eventBus = (data.app as any).plugins?.plugins?.['llm-agent-plugin']?.eventBus;
if (eventBus) { if (eventBus) {
let model = (data.app as any).plugins?.plugins?.['llm-agent-plugin']?.settings?.defaultModel || LLM_DEFAULTS.MODEL;
// TODO: Возможно, брать в данном случае данные из настроек плагина не вполне корректно, но вариант брать его из чат панели
// или перелопачивать код и хранить в узлах и бд настройки, использованные при генерации, как будто, ещё хуже
let maxTokens = (data.app as any).plugins?.plugins?.['llm-agent-plugin']?.settings?.maxTokensChatDefault || '';
let temperature = (data.app as any).plugins?.plugins?.['llm-agent-plugin']?.settings?.temperatureChat || LLM_DEFAULTS.TEMPERATURE;
eventBus.emit('regenerate-message', { eventBus.emit('regenerate-message', {
graphId: data.graphId, graphId: data.graphId,
nodeId: data.nodeId, nodeId: data.nodeId,
model: (data.app as any).plugins?.plugins?.['llm-agent-plugin']?.settings?.defaultModel || 'gemini-2.5-flash' // НОВОЕ: Добавляем модель model: model, // НОВОЕ: Добавляем модель
maxTokens: maxTokens,
temperature: temperature
}); });
} }
}; };

42
src/constants.ts Normal file
View File

@ -0,0 +1,42 @@
export const LLM_DEFAULTS = {
TEMPERATURE: 1.0,
MODEL: 'gemini-2.5-flash-r',
// Сюда можно добавить и другие константы
};
/*
Температурные режимы LLM моделей
Google (Gemini 2.0, 2.5, 3.x)
Минимум: 0.0
По умолчанию: 1.0
Максимум: 2.0
Примечание: Для новых моделей 3.0+ Google рекомендует строго 1.0 для корректной логики.
OpenAI (GPT-4o, GPT-4 Turbo)
Минимум: 0.0
По умолчанию: 1.0
Максимум: 2.0
Примечание: В API стандарт 1.0, хотя в веб-версии ChatGPT часто используется 0.7.
Mistral AI (Mistral Small, Large)
Минимум: 0.0
По умолчанию: 0.7
Максимум: 1.5
Примечание: Для задач на логику и код в моделях Small часто выставляют 0.150.3.
Anthropic (Claude 3.5, 4.5)
Минимум: 0.0
По умолчанию: 1.0
Максимум: 1.0
Примечание: У Claude диапазон ограничен единицей, значения выше 1.0 API не принимает.
Краткая шпаргалка по значениям:
0.0 - 0.2: Код, JSON, математика, строгая логика.
0.7: Баланс для обычного чата и суммаризации.
1.0: Творчество, идеи, копирайтинг (стандарт для Gemini и Claude).
*/

View File

@ -1449,4 +1449,59 @@
.attach-file-button svg { .attach-file-button svg {
width: 20px; width: 20px;
height: 20px; height: 20px;
}
/* ----------------------------------------------- Params UI Styles (Temp & Tokens) --------------------------------------------------------------- */
.param-select-container {
display: flex;
align-items: center;
/*background-color: var(--button-secondary-background);
border: 1px solid var(--border-color-dark);*/
border-radius: 8px;
padding: 2px 6px;
gap: 2px;
}
.param-icon {
font-size: 11px;
color: var(--text-muted);
/*font-weight: bold;*/
}
.param-input {
width: 48px;
background: transparent;
border: none;
color: var(--text-normal);
font-size: 11px;
padding: 0;
outline: none;
text-align: center;
box-shadow: none !important;
}
.param-input::-webkit-inner-spin-button,
.param-input::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.param-reset-btn {
background: transparent;
border: none;
color: var(--text-muted) !important;
cursor: pointer;
padding: 1px 4px !important;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
height: auto !important;
min-width: 0 !important;
}
.param-reset-btn:hover {
color: var(--text-normal) !important;
background-color: var(--background-modifier-hover);
} }

View File

@ -11,6 +11,8 @@ import { TemplateEngine } from '../references/TemplateEngine';
export const CHAT_VIEW_TYPE = 'llm-agent-chat-view'; export const CHAT_VIEW_TYPE = 'llm-agent-chat-view';
import { LLM_DEFAULTS } from 'src/constants';
// ----------------------------------------------- Chat View --------------------------------------------------------------- // ----------------------------------------------- Chat View ---------------------------------------------------------------
export class ChatView extends ItemView { export class ChatView extends ItemView {
@ -60,7 +62,7 @@ export class ChatView extends ItemView {
this.chatPanel = new ChatPanel(this.chatContainer, { this.chatPanel = new ChatPanel(this.chatContainer, {
chatHistory: this.chatHistory, chatHistory: this.chatHistory,
onSendMessage: this.handleSendMessage.bind(this), onSendMessage: this.handleSendMessage.bind(this),
selectedModel: this.plugin.settings.defaultModel || 'gemini-2.5-flash', selectedModel: this.plugin.settings.defaultModel || LLM_DEFAULTS.MODEL,
plugin: this.plugin, plugin: this.plugin,
currentGraphCustomSystemPrompt: this.currentGraphCustomSystemPrompt currentGraphCustomSystemPrompt: this.currentGraphCustomSystemPrompt
}); });
@ -156,7 +158,7 @@ export class ChatView extends ItemView {
// ----------------------------------------------- Event Handlers --------------------------------------------------------------- // ----------------------------------------------- Event Handlers ---------------------------------------------------------------
async handleSendMessage(message: string, selectedModel?: string, attachments?: Attachment[]): Promise<void> { async handleSendMessage(message: string, selectedModel?: string, attachments?: Attachment[], customMaxTokens?: number, customTemperature?: number): Promise<void> {
try { try {
if (!this.selectedGraphId) { if (!this.selectedGraphId) {
new Notice('Граф не выбран или не инициализирован. Пожалуйста, начните новый чат или выберите существующий.', 3000); new Notice('Граф не выбран или не инициализирован. Пожалуйста, начните новый чат или выберите существующий.', 3000);
@ -199,7 +201,7 @@ export class ChatView extends ItemView {
const systemPromptToUse = const systemPromptToUse =
this.currentGraphCustomSystemPrompt !== null ? this.currentGraphCustomSystemPrompt : this.plugin.settings.systemPrompt; this.currentGraphCustomSystemPrompt !== null ? this.currentGraphCustomSystemPrompt : this.plugin.settings.systemPrompt;
await this.streamLLMResponse(this.selectedGraphId, userNodeId, selectedModel, null, systemPromptToUse, cacheFolder); await this.streamLLMResponse(this.selectedGraphId, userNodeId, selectedModel, null, systemPromptToUse, cacheFolder, customMaxTokens, customTemperature);
} else { } else {
throw new Error('Graph ID не определён после отправки сообщения'); throw new Error('Graph ID не определён после отправки сообщения');
} }
@ -274,21 +276,29 @@ export class ChatView extends ItemView {
selectedModel?: string, selectedModel?: string,
existingAssistantNodeId: string | null = null, existingAssistantNodeId: string | null = null,
systemPrompt: string | null = null, systemPrompt: string | null = null,
cacheFolder?: string cacheFolder?: string,
customMaxTokens?: number,
customTemperature?: number
): Promise<void> { ): Promise<void> {
// Разворачиваем системный промпт (например, если там передано "#my_system_instructions") // Разворачиваем системный промпт
let finalSystemPrompt = systemPrompt; let finalSystemPrompt = systemPrompt;
if (systemPrompt) { if (systemPrompt) {
const templateEngine = new TemplateEngine(this.plugin); const templateEngine = new TemplateEngine(this.plugin);
finalSystemPrompt = await templateEngine.processRawSettingValue(systemPrompt); finalSystemPrompt = await templateEngine.processRawSettingValue(systemPrompt);
} }
const temperature = customTemperature !== undefined
? customTemperature
: ((this.plugin.settings as any).temperatureChat !== undefined ? (this.plugin.settings as any).temperatureChat : LLM_DEFAULTS.TEMPERATURE);
const body = { const body = {
graph_id: graphId, graph_id: graphId,
user_node_id: userNodeId, user_node_id: userNodeId,
system_prompt: finalSystemPrompt, system_prompt: finalSystemPrompt,
model: selectedModel || this.plugin.settings.defaultModel, model: selectedModel || this.plugin.settings.defaultModel,
cache_folder: cacheFolder cache_folder: cacheFolder,
temperature: temperature,
max_tokens: customMaxTokens
}; };
if (existingAssistantNodeId) { if (existingAssistantNodeId) {
@ -461,13 +471,15 @@ 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, maxTokens?: number, temperature?: number }): Promise<void> {
try { try {
// Определяем системный промпт для отправки: кастомный для графа, если есть, иначе глобальный
const systemPromptToUse = const systemPromptToUse =
this.currentGraphCustomSystemPrompt !== null ? this.currentGraphCustomSystemPrompt : this.plugin.settings.systemPrompt; this.currentGraphCustomSystemPrompt !== null ? this.currentGraphCustomSystemPrompt : this.plugin.settings.systemPrompt;
const cacheFolder = this.getCacheFolderPath(); const cacheFolder = this.getCacheFolderPath();
const temperature = data.temperature !== undefined
? data.temperature
: ((this.plugin.settings as any).temperatureChat !== undefined ? (this.plugin.settings as any).temperatureChat : LLM_DEFAULTS.TEMPERATURE);
const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/regenerate`, { const response = await fetch(`${this.plugin.settings.apiBaseUrl}/chat/regenerate`, {
method: 'POST', method: 'POST',
@ -477,7 +489,9 @@ export class ChatView extends ItemView {
node_id: data.nodeId, node_id: data.nodeId,
model: data.model, model: data.model,
system_prompt: systemPromptToUse, system_prompt: systemPromptToUse,
cache_folder: cacheFolder cache_folder: cacheFolder,
temperature: temperature,
max_tokens: data.maxTokens
}) })
}); });
@ -490,9 +504,11 @@ export class ChatView extends ItemView {
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; const systemPromptFromBackend = result.system_prompt;
const maxTokensFromBackend = result.max_tokens;
const temperatureFromBackend = result.temperature;
if (data.graphId) { if (data.graphId) {
await this.streamLLMResponse(data.graphId, userNodeId, modelToUse, existingAssistantNodeId, systemPromptFromBackend); await this.streamLLMResponse(data.graphId, userNodeId, modelToUse, existingAssistantNodeId, systemPromptFromBackend, cacheFolder, maxTokensFromBackend, temperatureFromBackend);
} }
} catch (error: any) { } catch (error: any) {
console.error('Ошибка регенерации:', error); console.error('Ошибка регенерации:', error);