mirror of
https://github.com/zadam/trilium.git
synced 2025-11-18 03:00:41 +01:00
Merge branch 'develop' into tab-row
This commit is contained in:
@@ -350,6 +350,115 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current chat data to a specific note ID
|
||||
*/
|
||||
async saveCurrentDataToSpecificNote(targetNoteId: string | null) {
|
||||
if (!this.onSaveData || !targetNoteId) {
|
||||
console.warn('Cannot save chat data: no saveData callback or no targetNoteId available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Extract current tool execution steps if any exist
|
||||
const toolSteps = extractInChatToolSteps(this.noteContextChatMessages);
|
||||
|
||||
// Get tool executions from both UI and any cached executions in metadata
|
||||
let toolExecutions: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
arguments: any;
|
||||
result: any;
|
||||
error?: string;
|
||||
timestamp: string;
|
||||
}> = [];
|
||||
|
||||
// First include any tool executions already in metadata (from streaming events)
|
||||
if (this.metadata?.toolExecutions && Array.isArray(this.metadata.toolExecutions)) {
|
||||
toolExecutions = [...this.metadata.toolExecutions];
|
||||
console.log(`Including ${toolExecutions.length} tool executions from metadata`);
|
||||
}
|
||||
|
||||
// Also extract any visible tool steps from the UI
|
||||
const extractedExecutions = toolSteps.map(step => {
|
||||
// Parse tool execution information
|
||||
if (step.type === 'tool-execution') {
|
||||
try {
|
||||
const content = JSON.parse(step.content);
|
||||
return {
|
||||
id: content.toolCallId || `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
|
||||
name: content.tool || 'unknown',
|
||||
arguments: content.args || {},
|
||||
result: content.result || {},
|
||||
error: content.error,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
} catch (e) {
|
||||
// If we can't parse it, create a basic record
|
||||
return {
|
||||
id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
|
||||
name: 'unknown',
|
||||
arguments: {},
|
||||
result: step.content,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
} else if (step.type === 'result' && step.name) {
|
||||
// Handle result steps with a name
|
||||
return {
|
||||
id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
|
||||
name: step.name,
|
||||
arguments: {},
|
||||
result: step.content,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
return {
|
||||
id: `tool-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
|
||||
name: 'unknown',
|
||||
arguments: {},
|
||||
result: 'Unrecognized tool step',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
});
|
||||
|
||||
// Merge the tool executions, keeping only unique IDs
|
||||
const existingIds = new Set(toolExecutions.map((t: {id: string}) => t.id));
|
||||
for (const exec of extractedExecutions) {
|
||||
if (!existingIds.has(exec.id)) {
|
||||
toolExecutions.push(exec);
|
||||
existingIds.add(exec.id);
|
||||
}
|
||||
}
|
||||
|
||||
const dataToSave = {
|
||||
messages: this.messages,
|
||||
noteId: targetNoteId,
|
||||
chatNoteId: targetNoteId, // For backward compatibility
|
||||
toolSteps: toolSteps,
|
||||
// Add sources if we have them
|
||||
sources: this.sources || [],
|
||||
// Add metadata
|
||||
metadata: {
|
||||
model: this.metadata?.model || undefined,
|
||||
provider: this.metadata?.provider || undefined,
|
||||
temperature: this.metadata?.temperature || 0.7,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
// Add tool executions
|
||||
toolExecutions: toolExecutions
|
||||
}
|
||||
};
|
||||
|
||||
console.log(`Saving chat data to specific note ${targetNoteId}, ${toolSteps.length} tool steps, ${this.sources?.length || 0} sources, ${toolExecutions.length} tool executions`);
|
||||
|
||||
// Save the data to the note attribute via the callback
|
||||
// This is the ONLY place we should save data, letting the container widget handle persistence
|
||||
await this.onSaveData(dataToSave);
|
||||
} catch (error) {
|
||||
console.error('Error saving chat data to specific note:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load saved chat data from the note attribute
|
||||
*/
|
||||
@@ -867,8 +976,8 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
this.showSources(postResponse.sources);
|
||||
}
|
||||
|
||||
// Process the assistant response
|
||||
this.processAssistantResponse(postResponse.content, postResponse);
|
||||
// Process the assistant response with original chat note ID
|
||||
this.processAssistantResponse(postResponse.content, postResponse, this.noteId);
|
||||
|
||||
hideLoadingIndicator(this.loadingIndicator);
|
||||
return true;
|
||||
@@ -884,7 +993,7 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
/**
|
||||
* Process an assistant response - add to UI and save
|
||||
*/
|
||||
private async processAssistantResponse(content: string, fullResponse?: any) {
|
||||
private async processAssistantResponse(content: string, fullResponse?: any, originalChatNoteId?: string | null) {
|
||||
// Add the response to the chat UI
|
||||
this.addMessageToChat('assistant', content);
|
||||
|
||||
@@ -910,8 +1019,8 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
];
|
||||
}
|
||||
|
||||
// Save to note
|
||||
this.saveCurrentData().catch(err => {
|
||||
// Save to note - use original chat note ID if provided
|
||||
this.saveCurrentDataToSpecificNote(originalChatNoteId || this.noteId).catch(err => {
|
||||
console.error("Failed to save assistant response to note:", err);
|
||||
});
|
||||
}
|
||||
@@ -936,12 +1045,15 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
timestamp: string;
|
||||
}> = [];
|
||||
|
||||
// Store the original chat note ID to ensure we save to the correct note even if user switches
|
||||
const originalChatNoteId = this.noteId;
|
||||
|
||||
return setupStreamingResponse(
|
||||
this.noteId,
|
||||
messageParams,
|
||||
// Content update handler
|
||||
(content: string, isDone: boolean = false) => {
|
||||
this.updateStreamingUI(content, isDone);
|
||||
this.updateStreamingUI(content, isDone, originalChatNoteId);
|
||||
|
||||
// Update session data with additional metadata when streaming is complete
|
||||
if (isDone) {
|
||||
@@ -1067,13 +1179,13 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
/**
|
||||
* Update the UI with streaming content
|
||||
*/
|
||||
private updateStreamingUI(assistantResponse: string, isDone: boolean = false) {
|
||||
private updateStreamingUI(assistantResponse: string, isDone: boolean = false, originalChatNoteId?: string | null) {
|
||||
// Track if we have a streaming message in progress
|
||||
const hasStreamingMessage = !!this.noteContextChatMessages.querySelector('.assistant-message.streaming');
|
||||
|
||||
|
||||
// Create a new message element or use the existing streaming one
|
||||
let assistantMessageEl: HTMLElement;
|
||||
|
||||
|
||||
if (hasStreamingMessage) {
|
||||
// Use the existing streaming message
|
||||
assistantMessageEl = this.noteContextChatMessages.querySelector('.assistant-message.streaming')!;
|
||||
@@ -1103,7 +1215,7 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
if (isDone) {
|
||||
// Remove the streaming class to mark this message as complete
|
||||
assistantMessageEl.classList.remove('streaming');
|
||||
|
||||
|
||||
// Apply syntax highlighting
|
||||
formatCodeBlocks($(assistantMessageEl as HTMLElement));
|
||||
|
||||
@@ -1118,8 +1230,8 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
// Save the updated message list
|
||||
this.saveCurrentData();
|
||||
// Save the updated message list to the original chat note
|
||||
this.saveCurrentDataToSpecificNote(originalChatNoteId || this.noteId);
|
||||
}
|
||||
|
||||
// Scroll to bottom
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Validation functions for LLM Chat
|
||||
*/
|
||||
import options from "../../services/options.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
/**
|
||||
* Validate providers configuration
|
||||
@@ -37,6 +38,9 @@ export async function validateProviders(validationWarning: HTMLElement): Promise
|
||||
// Check for configuration issues with providers in the precedence list
|
||||
const configIssues: string[] = [];
|
||||
|
||||
// Always add experimental warning as the first item
|
||||
configIssues.push(t("ai_llm.experimental_warning"));
|
||||
|
||||
// Check each provider in the precedence list for proper configuration
|
||||
for (const provider of precedenceList) {
|
||||
if (provider === 'openai') {
|
||||
|
||||
@@ -182,17 +182,30 @@ export default class AiChatTypeWidget extends TypeWidget {
|
||||
|
||||
// Save chat data to the note
|
||||
async saveData(data: any) {
|
||||
if (!this.note) {
|
||||
// If we have a noteId in the data, that's the AI Chat note we should save to
|
||||
// This happens when the chat panel is saving its conversation
|
||||
const targetNoteId = data.noteId;
|
||||
|
||||
// If no noteId in data, use the current note (for new chats)
|
||||
const noteIdToUse = targetNoteId || this.note?.noteId;
|
||||
|
||||
if (!noteIdToUse) {
|
||||
console.warn("Cannot save AI Chat data: no note ID available");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`AiChatTypeWidget: Saving data for note ${this.note.noteId}`);
|
||||
console.log(`AiChatTypeWidget: Saving data for note ${noteIdToUse} (current note: ${this.note?.noteId}, data.noteId: ${data.noteId})`);
|
||||
|
||||
// Safety check: if we have both IDs and they don't match, warn about it
|
||||
if (targetNoteId && this.note?.noteId && targetNoteId !== this.note.noteId) {
|
||||
console.warn(`Note ID mismatch: saving to ${targetNoteId} but current note is ${this.note.noteId}`);
|
||||
}
|
||||
|
||||
// Format the data properly - this is the canonical format of the data
|
||||
const formattedData = {
|
||||
messages: data.messages || [],
|
||||
noteId: this.note.noteId, // Always use the note's own ID
|
||||
noteId: noteIdToUse, // Always preserve the correct note ID
|
||||
toolSteps: data.toolSteps || [],
|
||||
sources: data.sources || [],
|
||||
metadata: {
|
||||
@@ -201,8 +214,8 @@ export default class AiChatTypeWidget extends TypeWidget {
|
||||
}
|
||||
};
|
||||
|
||||
// Save the data to the note
|
||||
await server.put(`notes/${this.note.noteId}/data`, {
|
||||
// Save the data to the correct note
|
||||
await server.put(`notes/${noteIdToUse}/data`, {
|
||||
content: JSON.stringify(formattedData, null, 2)
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -166,7 +166,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
// is shorter than minimumNonErrorTimePeriod, the watchdog changes
|
||||
// its state to crashedPermanently, and it stops restarting the editor.
|
||||
// This prevents an infinite restart loop.
|
||||
crashNumberLimit: 3,
|
||||
crashNumberLimit: 10,
|
||||
// A minimum number of milliseconds between saving the editor data internally (defaults to 5000).
|
||||
// Note that for large documents, this might impact the editor performance.
|
||||
saveInterval: 5000
|
||||
@@ -181,8 +181,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
logInfo(`CKEditor crash logs: ${JSON.stringify(this.watchdog.crashes)}`);
|
||||
this.watchdog.crashes.forEach((crashInfo) => console.log(crashInfo));
|
||||
logError(`CKEditor crash logs: ${JSON.stringify(this.watchdog.crashes, null, 4)}`);
|
||||
|
||||
if (currentState === "crashedPermanently") {
|
||||
dialogService.info(`Editing component keeps crashing. Please try restarting Trilium. If problem persists, consider creating a bug report.`);
|
||||
@@ -191,7 +190,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
});
|
||||
|
||||
this.watchdog.setCreator(async (elementOrData, editorConfig) => {
|
||||
this.watchdog.setCreator(async (_, editorConfig) => {
|
||||
logInfo("Creating new CKEditor");
|
||||
|
||||
const finalConfig = {
|
||||
@@ -221,7 +220,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const editor = await editorClass.create(elementOrData, finalConfig);
|
||||
const editor = await editorClass.create(this.$editor[0], finalConfig);
|
||||
|
||||
const notificationsPlugin = editor.plugins.get("Notification");
|
||||
notificationsPlugin.on("show:warning", (evt, data) => {
|
||||
@@ -337,6 +336,11 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
|
||||
getData() {
|
||||
if (!this.watchdog.editor) {
|
||||
// There is nothing to save, most likely a result of the editor crashing and reinitializing.
|
||||
return;
|
||||
}
|
||||
|
||||
const content = this.watchdog.editor?.getData() ?? "";
|
||||
|
||||
// if content is only tags/whitespace (typically <p> </p>), then just make it empty,
|
||||
@@ -375,7 +379,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
insertDateTimeToTextCommand() {
|
||||
insertDateTimeToTextCommand() {
|
||||
const date = new Date();
|
||||
const customDateTimeFormat = options.get("customDateTimeFormat");
|
||||
const dateString = utils.formatDateTime(date, customDateTimeFormat);
|
||||
|
||||
@@ -48,7 +48,7 @@ export default class AiSettingsWidget extends OptionsWidget {
|
||||
if (optionName === 'aiEnabled') {
|
||||
try {
|
||||
const isEnabled = value === 'true';
|
||||
|
||||
|
||||
if (isEnabled) {
|
||||
toastService.showMessage(t("ai_llm.ai_enabled") || "AI features enabled");
|
||||
} else {
|
||||
@@ -203,6 +203,11 @@ export default class AiSettingsWidget extends OptionsWidget {
|
||||
// Get selected provider
|
||||
const selectedProvider = this.$widget.find('.ai-selected-provider').val() as string;
|
||||
|
||||
// Start with experimental warning
|
||||
const allWarnings = [
|
||||
t("ai_llm.experimental_warning")
|
||||
];
|
||||
|
||||
// Check for selected provider configuration
|
||||
const providerWarnings: string[] = [];
|
||||
if (selectedProvider === 'openai') {
|
||||
@@ -222,10 +227,8 @@ export default class AiSettingsWidget extends OptionsWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Combine all warnings
|
||||
const allWarnings = [
|
||||
...providerWarnings
|
||||
];
|
||||
// Add provider warnings to all warnings
|
||||
allWarnings.push(...providerWarnings);
|
||||
|
||||
// Show or hide warnings
|
||||
if (allWarnings.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user