mirror of
https://github.com/zadam/trilium.git
synced 2026-05-07 08:07:04 +02:00
chore(llm): address requested changes
This commit is contained in:
@@ -81,7 +81,6 @@
|
||||
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
||||
"@prefresh/vite": "2.4.12",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/dompurify": "3.2.0",
|
||||
"@types/jquery": "4.0.0",
|
||||
"@types/leaflet": "1.9.21",
|
||||
"@types/leaflet-gpx": "1.3.8",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import dateNoteService, { type RecentLlmChat } from "../../services/date_notes.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import { formatDateTime } from "../../utils/formatters";
|
||||
import ActionButton from "../react/ActionButton.js";
|
||||
import Dropdown from "../react/Dropdown.js";
|
||||
import { FormListItem } from "../react/FormList.js";
|
||||
@@ -80,6 +81,13 @@ export default function SidebarChat() {
|
||||
console.error("Failed to save chat:", err);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
if (saveTimeoutRef.current) {
|
||||
clearTimeout(saveTimeoutRef.current);
|
||||
saveTimeoutRef.current = undefined;
|
||||
}
|
||||
};
|
||||
}, [shouldSave, chatNoteId, chat]);
|
||||
|
||||
// Load the most recent chat on mount (runs once)
|
||||
@@ -254,7 +262,7 @@ export default function SidebarChat() {
|
||||
? <strong>{chatItem.title}</strong>
|
||||
: <span>{chatItem.title}</span>}
|
||||
<span className="sidebar-chat-history-date">
|
||||
{new Date(chatItem.dateModified).toLocaleDateString()}
|
||||
{formatDateTime(new Date(chatItem.dateModified), "short", "short")}
|
||||
</span>
|
||||
</div>
|
||||
</FormListItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "./LlmChat.css";
|
||||
|
||||
import { marked } from "marked";
|
||||
import { Marked } from "marked";
|
||||
import { useMemo } from "preact/hooks";
|
||||
|
||||
import { t } from "../../../services/i18n.js";
|
||||
@@ -15,14 +15,14 @@ function shortenNumber(n: number): string {
|
||||
}
|
||||
|
||||
// Configure marked for safe rendering
|
||||
marked.setOptions({
|
||||
const markedInstance = new Marked({
|
||||
breaks: true, // Convert \n to <br>
|
||||
gfm: true // GitHub Flavored Markdown
|
||||
});
|
||||
|
||||
/** Parse markdown to HTML. Sanitization is handled by SanitizedHtml. */
|
||||
function renderMarkdown(markdown: string): string {
|
||||
return marked.parse(markdown) as string;
|
||||
return markedInstance.parse(markdown) as string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { LlmCitation, LlmMessage, LlmModelInfo, LlmUsage } from "@triliumnext/commons";
|
||||
import { RefObject } from "preact";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import { getAvailableModels, streamChatCompletion } from "../../../services/llm_chat.js";
|
||||
import { randomString } from "../../../services/utils.js";
|
||||
import type { ContentBlock, LlmChatContent, StoredMessage, ToolCall } from "./llm_chat_types.js";
|
||||
import type { ContentBlock, LlmChatContent, StoredMessage } from "./llm_chat_types.js";
|
||||
|
||||
export interface ModelOption extends LlmModelInfo {
|
||||
costDescription?: string;
|
||||
@@ -37,8 +38,8 @@ export interface UseLlmChatReturn {
|
||||
enableExtendedThinking: boolean;
|
||||
contextNoteId: string | undefined;
|
||||
lastPromptTokens: number;
|
||||
messagesEndRef: React.RefObject<HTMLDivElement>;
|
||||
textareaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
messagesEndRef: RefObject<HTMLDivElement>;
|
||||
textareaRef: RefObject<HTMLTextAreaElement>;
|
||||
/** Whether a provider is configured and available */
|
||||
hasProvider: boolean;
|
||||
/** Whether we're still checking for providers */
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Request, Response } from "express";
|
||||
import type { LlmMessage } from "@triliumnext/commons";
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
import { generateChatTitle } from "../../services/llm/chat_title.js";
|
||||
import { getProviderByType, hasConfiguredProviders, type LlmProviderConfig } from "../../services/llm/index.js";
|
||||
import { streamToChunks } from "../../services/llm/stream.js";
|
||||
import { generateChatTitle } from "../../services/llm/chat_title.js";
|
||||
import log from "../../services/log.js";
|
||||
import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
|
||||
|
||||
interface ChatRequest {
|
||||
messages: LlmMessage[];
|
||||
@@ -34,7 +36,6 @@ async function streamChat(req: Request, res: Response) {
|
||||
res.setHeader("Cache-Control", "no-cache, no-transform");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.setHeader("X-Accel-Buffering", "no"); // Disable nginx buffering
|
||||
res.setHeader("Content-Encoding", "none"); // Disable compression
|
||||
res.flushHeaders();
|
||||
|
||||
// Mark response as handled to prevent double-handling by apiResultHandler
|
||||
@@ -46,7 +47,6 @@ async function streamChat(req: Request, res: Response) {
|
||||
try {
|
||||
if (!hasConfiguredProviders()) {
|
||||
res.write(`data: ${JSON.stringify({ type: "error", error: "No LLM providers configured. Please add a provider in Options → AI / LLM." })}\n\n`);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,12 @@ async function streamChat(req: Request, res: Response) {
|
||||
const result = provider.chat(messages, config);
|
||||
|
||||
// Get pricing and display name for the model
|
||||
const modelId = config.model || "claude-sonnet-4-6";
|
||||
const modelId = config.model || provider.getAvailableModels().find(m => m.isDefault)?.id;
|
||||
if (!modelId) {
|
||||
res.write(`data: ${JSON.stringify({ type: "error", error: "No model specified and no default model available for the provider." })}\n\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
const pricing = provider.getModelPricing(modelId);
|
||||
const modelDisplayName = provider.getAvailableModels().find(m => m.id === modelId)?.name || modelId;
|
||||
for await (const chunk of streamToChunks(result, { model: modelDisplayName, pricing })) {
|
||||
@@ -71,7 +76,7 @@ async function streamChat(req: Request, res: Response) {
|
||||
await generateChatTitle(config.chatNoteId, userMessages[0].content);
|
||||
} catch (err) {
|
||||
// Title generation is best-effort; don't fail the chat
|
||||
console.error("Failed to generate chat title:", err);
|
||||
log.error(`Failed to generate chat title: ${safeExtractMessageAndStackFromError(err)}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -56,7 +56,7 @@ export const searchNotes = tool({
|
||||
if (!note) return null;
|
||||
return {
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
title: note.getTitleOrProtected(),
|
||||
type: note.type
|
||||
};
|
||||
}).filter(Boolean);
|
||||
@@ -76,13 +76,13 @@ export const readNote = tool({
|
||||
if (!note) {
|
||||
return { error: "Note not found" };
|
||||
}
|
||||
if (note.isProtected) {
|
||||
if (!note.isContentAvailable()) {
|
||||
return { error: "Note is protected" };
|
||||
}
|
||||
|
||||
return {
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
title: note.getTitleOrProtected(),
|
||||
type: note.type,
|
||||
content: getNoteContentForLlm(note)
|
||||
};
|
||||
@@ -103,7 +103,7 @@ export const updateNoteContent = tool({
|
||||
if (!note) {
|
||||
return { error: "Note not found" };
|
||||
}
|
||||
if (note.isProtected) {
|
||||
if (!note.isContentAvailable()) {
|
||||
return { error: "Note is protected and cannot be modified" };
|
||||
}
|
||||
if (!note.hasStringContent()) {
|
||||
@@ -115,7 +115,7 @@ export const updateNoteContent = tool({
|
||||
return {
|
||||
success: true,
|
||||
noteId: note.noteId,
|
||||
title: note.title
|
||||
title: note.getTitleOrProtected()
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -134,7 +134,7 @@ export const appendToNote = tool({
|
||||
if (!note) {
|
||||
return { error: "Note not found" };
|
||||
}
|
||||
if (note.isProtected) {
|
||||
if (!note.isContentAvailable()) {
|
||||
return { error: "Note is protected and cannot be modified" };
|
||||
}
|
||||
if (!note.hasStringContent()) {
|
||||
@@ -148,7 +148,7 @@ export const appendToNote = tool({
|
||||
|
||||
let newContent: string;
|
||||
if (note.type === "text") {
|
||||
const htmlToAppend = markdownImport.renderToHtml(content, note.title);
|
||||
const htmlToAppend = markdownImport.renderToHtml(content, note.getTitleOrProtected());
|
||||
newContent = existingContent + htmlToAppend;
|
||||
} else {
|
||||
newContent = existingContent + (existingContent.endsWith("\n") ? "" : "\n") + content;
|
||||
@@ -159,7 +159,7 @@ export const appendToNote = tool({
|
||||
return {
|
||||
success: true,
|
||||
noteId: note.noteId,
|
||||
title: note.title
|
||||
title: note.getTitleOrProtected()
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -180,7 +180,7 @@ export const createNote = tool({
|
||||
if (!parentNote) {
|
||||
return { error: "Parent note not found" };
|
||||
}
|
||||
if (parentNote.isProtected) {
|
||||
if (!parentNote.isContentAvailable()) {
|
||||
return { error: "Cannot create note under a protected parent" };
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ export const createNote = tool({
|
||||
return {
|
||||
success: true,
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
title: note.getTitleOrProtected(),
|
||||
type: note.type
|
||||
};
|
||||
} catch (err) {
|
||||
@@ -222,13 +222,13 @@ export function currentNoteTools(contextNoteId: string) {
|
||||
if (!note) {
|
||||
return { error: "Note not found" };
|
||||
}
|
||||
if (note.isProtected) {
|
||||
if (!note.isContentAvailable()) {
|
||||
return { error: "Note is protected" };
|
||||
}
|
||||
|
||||
return {
|
||||
noteId: note.noteId,
|
||||
title: note.title,
|
||||
title: note.getTitleOrProtected(),
|
||||
type: note.type,
|
||||
content: getNoteContentForLlm(note)
|
||||
};
|
||||
|
||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -376,9 +376,6 @@ importers:
|
||||
'@types/bootstrap':
|
||||
specifier: 5.2.10
|
||||
version: 5.2.10
|
||||
'@types/dompurify':
|
||||
specifier: 3.2.0
|
||||
version: 3.2.0
|
||||
'@types/jquery':
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
@@ -6067,10 +6064,6 @@ packages:
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
'@types/dompurify@3.2.0':
|
||||
resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==}
|
||||
deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/ejs@3.1.5':
|
||||
resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==}
|
||||
|
||||
@@ -17004,8 +16997,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-cloud-services@47.6.1':
|
||||
dependencies:
|
||||
@@ -17390,8 +17381,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-highlight@47.6.1':
|
||||
dependencies:
|
||||
@@ -17401,8 +17390,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-horizontal-line@47.6.1':
|
||||
dependencies:
|
||||
@@ -17412,8 +17399,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-html-embed@47.6.1':
|
||||
dependencies:
|
||||
@@ -17423,8 +17408,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-html-support@47.6.1':
|
||||
dependencies:
|
||||
@@ -17604,6 +17587,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-operations-compressor@47.6.1':
|
||||
dependencies:
|
||||
@@ -22170,10 +22155,6 @@ snapshots:
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/dompurify@3.2.0':
|
||||
dependencies:
|
||||
dompurify: 3.3.3
|
||||
|
||||
'@types/ejs@3.1.5': {}
|
||||
|
||||
'@types/electron-squirrel-startup@1.0.2': {}
|
||||
|
||||
Reference in New Issue
Block a user