diff --git a/apps/client/src/services/llm_chat.ts b/apps/client/src/services/llm_chat.ts index fa0a0279d38..57af8e1dd5a 100644 --- a/apps/client/src/services/llm_chat.ts +++ b/apps/client/src/services/llm_chat.ts @@ -13,7 +13,7 @@ export async function getAvailableModels(): Promise { export interface StreamCallbacks { onChunk: (text: string) => void; onThinking?: (text: string) => void; - onToolUse?: (toolName: string, input: Record) => void; + onToolUse?: (toolName: string, input: Record, requiresApproval?: boolean) => void; onToolResult?: (toolName: string, result: string, isError?: boolean) => void; onCitation?: (citation: LlmCitation) => void; onUsage?: (usage: LlmUsage) => void; @@ -21,13 +21,30 @@ export interface StreamCallbacks { onDone: () => void; } +/** + * Execute a mutating tool call after user approval. + */ +export async function executeToolCall(toolName: string, toolInput: Record): Promise<{ result: string; isError?: boolean }> { + const response = await server.post<{ result?: object; error?: string }>("llm-chat/execute-tool", { toolName, toolInput }); + + if (response.error) { + return { result: response.error, isError: true }; + } + + return { + result: typeof response.result === "string" ? response.result : JSON.stringify(response.result) + }; +} + /** * Stream a chat completion from the LLM API using Server-Sent Events. + * Returns an AbortController that can be used to cancel the stream. */ export async function streamChatCompletion( messages: LlmMessage[], config: LlmChatConfig, - callbacks: StreamCallbacks + callbacks: StreamCallbacks, + abortSignal?: AbortSignal ): Promise { const headers = await server.getHeaders(); @@ -37,7 +54,8 @@ export async function streamChatCompletion( ...headers, "Content-Type": "application/json" } as HeadersInit, - body: JSON.stringify({ messages, config }) + body: JSON.stringify({ messages, config }), + signal: abortSignal }); if (!response.ok) { @@ -76,7 +94,7 @@ export async function streamChatCompletion( callbacks.onThinking?.(data.content); break; case "tool_use": - callbacks.onToolUse?.(data.toolName, data.toolInput); + callbacks.onToolUse?.(data.toolName, data.toolInput, data.requiresApproval); // Yield to force Preact to commit the pending tool call // state before we process the result. await new Promise((r) => setTimeout(r, 1)); diff --git a/apps/client/src/services/note_autocomplete.ts b/apps/client/src/services/note_autocomplete.ts index 9ca4fa86fb1..0d68dd68d9f 100644 --- a/apps/client/src/services/note_autocomplete.ts +++ b/apps/client/src/services/note_autocomplete.ts @@ -52,6 +52,8 @@ export interface Options { hideAllButtons?: boolean; /** If set, enables command palette mode */ isCommandPalette?: boolean; + /** If true, close the dropdown when the input loses focus (even when using a container). Defaults to false. */ + closeOnBlur?: boolean; } async function autocompleteSourceForCKEditor(queryText: string) { @@ -258,7 +260,9 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { let autocompleteOptions: AutoCompleteConfig = {}; if (options.container) { autocompleteOptions.dropdownMenuContainer = options.container; - autocompleteOptions.debug = true; // don't close on blur + if (!options.closeOnBlur) { + autocompleteOptions.debug = true; // don't close on blur + } } if (options.allowJumpToSearchNotes) { diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 9605212f6e2..25050789d38 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1641,6 +1641,10 @@ "sources": "Sources", "sources_summary": "{{count}} sources from {{sites}} sites", "extended_thinking": "Extended thinking", + "knowledge_base": "Knowledge base", + "knowledge_base_sources": "Knowledge base sources", + "knowledge_base_add": "Add a note as source...", + "knowledge_base_remove": "Remove source", "legacy_models": "Legacy models", "thinking": "Thinking...", "thought_process": "Thought process", @@ -1649,6 +1653,11 @@ "result": "Result", "error": "Error", "tool_error": "failed", + "approve": "Approve", + "reject": "Reject", + "pending_approval": "This action requires your approval", + "rejected_by_user": "Rejected by user", + "stop": "Stop", "total_tokens": "{{total}} tokens", "tokens_detail": "{{prompt}} prompt + {{completion}} completion", "tokens_used": "{{prompt}} prompt + {{completion}} completion = {{total}} tokens", @@ -1660,7 +1669,8 @@ "note_context_enabled": "Click to disable note context: {{title}}", "note_context_disabled": "Click to include current note in context", "no_provider_message": "No AI provider configured. Add one to start chatting.", - "add_provider": "Add AI Provider" + "add_provider": "Add AI Provider", + "free": "Free" }, "sidebar_chat": { "title": "AI Chat", @@ -2340,7 +2350,19 @@ "delete_provider_confirmation": "Are you sure you want to delete the provider \"{{name}}\"?", "api_key": "API Key", "api_key_placeholder": "Enter your API key", + "base_url": "Base URL", "cancel": "Cancel", + "web_search_title": "Web Search Engine", + "web_search_description": "Choose which search engine the AI agent uses for web searches. Provider default uses the built-in search of each LLM provider (Anthropic, OpenAI, Google). Tavily and SearXNG work with all providers including Ollama.", + "web_search_engine": "Search engine", + "web_search_engine_description": "Select the search engine to use for AI web searches", + "web_search_provider_default": "Provider default (built-in)", + "tavily_api_key": "Tavily API key", + "tavily_api_key_description": "Get a free API key at tavily.com (1,000 searches/month free)", + "searxng_url": "SearXNG instance URL", + "searxng_url_description": "URL of your self-hosted SearXNG instance", + "search_timeout": "Search timeout (seconds)", + "search_timeout_description": "Maximum time to wait for web search results before timing out", "mcp_title": "MCP (Model Context Protocol)", "mcp_enabled": "MCP server", "mcp_enabled_description": "Expose a Model Context Protocol (MCP) endpoint so that AI coding assistants (e.g. Claude Code, GitHub Copilot) can read and modify your notes. The endpoint is only accessible from localhost.", @@ -2363,7 +2385,11 @@ "web_search": "Web search", "note_in_parent": " in ", "get_attachment": "Get attachment", - "get_attachment_content": "Read attachment content" + "get_attachment_content": "Read attachment content", + "rename_note": "Rename note", + "delete_note": "Delete note", + "move_note": "Move note", + "clone_note": "Clone note" } } } diff --git a/apps/client/src/widgets/sidebar/SidebarChat.tsx b/apps/client/src/widgets/sidebar/SidebarChat.tsx index 51551262740..ebed9b06cb9 100644 --- a/apps/client/src/widgets/sidebar/SidebarChat.tsx +++ b/apps/client/src/widgets/sidebar/SidebarChat.tsx @@ -287,7 +287,12 @@ export default function SidebarChat() { /> )} {chat.messages.map(msg => ( - + ))} {chat.isStreaming && chat.streamingThinking && ( >({}); + const [kbPanelOpen, setKbPanelOpen] = useState(chat.sourceNoteIds.length > 0); + const kbContainerRef = useRef(null); + + // Open KB panel when sources are loaded from saved content + useEffect(() => { + if (chat.sourceNoteIds.length > 0) { + setKbPanelOpen(true); + } + }, [chat.sourceNoteIds.length]); + + // Resolve note titles for source note chips + useEffect(() => { + const ids = chat.sourceNoteIds.filter(id => !sourceTitles[id]); + if (ids.length === 0) return; + + Promise.all(ids.map(id => froca.getNote(id, true))).then(notes => { + const newTitles: Record = {}; + for (let i = 0; i < ids.length; i++) { + newTitles[ids[i]] = notes[i]?.title ?? ids[i]; + } + setSourceTitles(prev => ({ ...prev, ...newTitles })); + }); + }, [chat.sourceNoteIds]); + + const handleAddSourceNote = useCallback((noteId: string) => { + if (noteId && !chat.sourceNoteIds.includes(noteId)) { + chat.addSourceNote(noteId); + } + }, [chat.sourceNoteIds, chat.addSourceNote]); + const currentModel = chat.availableModels.find(m => m.id === chat.selectedModel); const currentModels = chat.availableModels.filter(m => !m.isLegacy); const legacyModels = chat.availableModels.filter(m => m.isLegacy); @@ -139,6 +172,45 @@ export default function ChatInputBar({ onKeyDown={handleKeyDown} rows={rows} /> + {kbPanelOpen && ( +
+
+ + {t("llm_chat.knowledge_base_sources")} +
+
+ {chat.sourceNoteIds.map(noteId => ( + + {sourceTitles[noteId] ?? noteId} + + + ))} +
+
+ +
+
+ )}
@@ -153,7 +225,7 @@ export default function ChatInputBar({ onClick={() => handleModelSelect(model.id)} checked={chat.selectedModel === model.id} > - {model.name} ({model.costDescription}) + {model.name}{model.costDescription && <> ({model.costDescription})} ))} {legacyModels.length > 0 && ( @@ -169,7 +241,7 @@ export default function ChatInputBar({ onClick={() => handleModelSelect(model.id)} checked={chat.selectedModel === model.id} > - {model.name} ({model.costDescription}) + {model.name}{model.costDescription && <> ({model.costDescription})} ))} @@ -197,6 +269,20 @@ export default function ChatInputBar({ onChange={handleExtendedThinkingToggle} disabled={chat.isStreaming} /> + + { + setKbPanelOpen(newValue); + if (!newValue) { + chat.setSourceNoteIds([]); + setSourceTitles({}); + } + }} + disabled={chat.isStreaming} + /> {activeNoteId && activeNoteTitle && (
diff --git a/apps/client/src/widgets/type_widgets/llm_chat/ChatMessage.tsx b/apps/client/src/widgets/type_widgets/llm_chat/ChatMessage.tsx index 26a7f36c464..e0d0b6d9523 100644 --- a/apps/client/src/widgets/type_widgets/llm_chat/ChatMessage.tsx +++ b/apps/client/src/widgets/type_widgets/llm_chat/ChatMessage.tsx @@ -62,6 +62,8 @@ function MarkdownContent({ html, isStreaming }: { html: string; isStreaming?: bo interface Props { message: StoredMessage; isStreaming?: boolean; + onApproveToolCall?: (toolCallId: string) => Promise; + onRejectToolCall?: (toolCallId: string) => void; } type ContentGroup = @@ -127,7 +129,7 @@ function CitationsSection({ citations }: { citations: LlmCitation[] }) { ); } -export default function ChatMessage({ message, isStreaming }: Props) { +export default function ChatMessage({ message, isStreaming, onApproveToolCall, onRejectToolCall }: Props) { const isError = message.type === "error"; const isThinking = message.type === "thinking"; const textContent = typeof message.content === "string" ? message.content : getMessageText(message.content); @@ -172,7 +174,7 @@ export default function ChatMessage({ message, isStreaming }: Props) {
{message.role === "assistant" && !isError ? ( hasBlockContent ? ( - renderContentBlocks(message.content as ContentBlock[], isStreaming) + renderContentBlocks(message.content as ContentBlock[], isStreaming, onApproveToolCall, onRejectToolCall) ) : ( ) @@ -244,7 +246,12 @@ function groupContentBlocks(blocks: ContentBlock[]): ContentGroup[] { return groups; } -function renderContentBlocks(blocks: ContentBlock[], isStreaming?: boolean) { +function renderContentBlocks( + blocks: ContentBlock[], + isStreaming?: boolean, + onApproveToolCall?: (toolCallId: string) => Promise, + onRejectToolCall?: (toolCallId: string) => void +) { return groupContentBlocks(blocks).map((group) => { if (group.type === "text") { const html = renderMarkdown(group.block.content); @@ -256,6 +263,13 @@ function renderContentBlocks(blocks: ContentBlock[], isStreaming?: boolean) { ); } - return b.toolCall)} />; + return ( + b.toolCall)} + onApprove={onApproveToolCall} + onReject={onRejectToolCall} + /> + ); }); } diff --git a/apps/client/src/widgets/type_widgets/llm_chat/ExpandableCard.tsx b/apps/client/src/widgets/type_widgets/llm_chat/ExpandableCard.tsx index 2e12c08e644..d9e5476cc2a 100644 --- a/apps/client/src/widgets/type_widgets/llm_chat/ExpandableCard.tsx +++ b/apps/client/src/widgets/type_widgets/llm_chat/ExpandableCard.tsx @@ -7,12 +7,14 @@ interface ExpandableSectionProps { label: ComponentChildren; className?: string; children: ComponentChildren; + /** Whether the section should be expanded by default */ + defaultExpanded?: boolean; } /** A collapsible section within an ExpandableCard. */ -export function ExpandableSection({ icon, label, className, children }: ExpandableSectionProps) { +export function ExpandableSection({ icon, label, className, children, defaultExpanded }: ExpandableSectionProps) { return ( -
+
{label} diff --git a/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx b/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx index 301141f0d88..3f210714524 100644 --- a/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx +++ b/apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx @@ -63,7 +63,12 @@ export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) { /> )} {chat.messages.map(msg => ( - + ))} {chat.isStreaming && chat.streamingThinking && ( Promise; + onReject?: (toolCallId: string) => void; +}) { const hasError = toolCall.isError; + const isPendingApproval = toolCall.requiresApproval && !toolCall.result && !toolCall.rejected; return ( } className={hasError ? "llm-chat-tool-call-error" : ""} + defaultExpanded={isPendingApproval} >
{t("llm_chat.input")}
+ {isPendingApproval && onApprove && onReject && ( +
+ {t("llm_chat.pending_approval")} +
+ + +
+
+ )} {toolCall.result && (
{hasError ? t("llm_chat.error") : t("llm_chat.result")} @@ -202,11 +228,15 @@ function ToolCallSection({ toolCall }: { toolCall: ToolCall }) { } /** A card that groups one or more sequential tool calls together. */ -export default function ToolCallCard({ toolCalls }: { toolCalls: ToolCall[] }) { +export default function ToolCallCard({ toolCalls, onApprove, onReject }: { + toolCalls: ToolCall[]; + onApprove?: (toolCallId: string) => Promise; + onReject?: (toolCallId: string) => void; +}) { return ( {toolCalls.map((tc, idx) => ( - + ))} ); diff --git a/apps/client/src/widgets/type_widgets/llm_chat/llm_chat_types.ts b/apps/client/src/widgets/type_widgets/llm_chat/llm_chat_types.ts index d05bf291b5b..f6db2ab14ad 100644 --- a/apps/client/src/widgets/type_widgets/llm_chat/llm_chat_types.ts +++ b/apps/client/src/widgets/type_widgets/llm_chat/llm_chat_types.ts @@ -8,6 +8,10 @@ export interface ToolCall { input: Record; result?: string; isError?: boolean; + /** When true, the tool requires user approval before execution. */ + requiresApproval?: boolean; + /** Whether the user rejected this tool call. */ + rejected?: boolean; } /** A block of text content (rendered as Markdown for assistant messages). */ @@ -70,4 +74,5 @@ export interface LlmChatContent { enableWebSearch?: boolean; enableNoteTools?: boolean; enableExtendedThinking?: boolean; + sourceNoteIds?: string[]; } diff --git a/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts b/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts index 63cbf4bbf42..1f200ed3491 100644 --- a/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts +++ b/apps/client/src/widgets/type_widgets/llm_chat/useLlmChat.ts @@ -2,7 +2,8 @@ import type { LlmCitation, LlmMessage, LlmModelInfo, LlmUsage } from "@triliumne import { RefObject } from "preact"; import { useCallback, useEffect, useRef, useState } from "preact/hooks"; -import { getAvailableModels, streamChatCompletion } from "../../../services/llm_chat.js"; +import { executeToolCall, getAvailableModels, streamChatCompletion } from "../../../services/llm_chat.js"; +import { t } from "../../../services/i18n.js"; import { randomString } from "../../../services/utils.js"; import type { ContentBlock, LlmChatContent, StoredMessage } from "./llm_chat_types.js"; @@ -36,6 +37,7 @@ export interface UseLlmChatReturn { enableNoteTools: boolean; enableExtendedThinking: boolean; contextNoteId: string | undefined; + sourceNoteIds: string[]; lastPromptTokens: number; messagesEndRef: RefObject; textareaRef: RefObject; @@ -53,6 +55,9 @@ export interface UseLlmChatReturn { setEnableExtendedThinking: (value: boolean) => void; setContextNoteId: (noteId: string | undefined) => void; setChatNoteId: (noteId: string | undefined) => void; + setSourceNoteIds: (noteIds: string[]) => void; + addSourceNote: (noteId: string) => void; + removeSourceNote: (noteId: string) => void; // Actions handleSubmit: (e: Event) => Promise; @@ -62,6 +67,12 @@ export interface UseLlmChatReturn { clearMessages: () => void; /** Refresh the provider/models list */ refreshModels: () => void; + /** Approve a pending mutating tool call */ + approveToolCall: (toolCallId: string) => Promise; + /** Reject a pending mutating tool call */ + rejectToolCall: (toolCallId: string) => void; + /** Stop the current generation */ + stopStreaming: () => void; } export function useLlmChat( @@ -84,11 +95,13 @@ export function useLlmChat( const [enableExtendedThinking, setEnableExtendedThinking] = useState(false); const [contextNoteId, setContextNoteId] = useState(initialContextNoteId); const [chatNoteId, setChatNoteIdState] = useState(initialChatNoteId); + const [sourceNoteIds, setSourceNoteIds] = useState([]); const [lastPromptTokens, setLastPromptTokens] = useState(0); const [hasProvider, setHasProvider] = useState(true); // Assume true initially const [isCheckingProvider, setIsCheckingProvider] = useState(true); const messagesEndRef = useRef(null); const textareaRef = useRef(null); + const abortControllerRef = useRef(null); // Refs to get fresh values in getContent (avoids stale closures) const messagesRef = useRef(messages); @@ -109,6 +122,16 @@ export function useLlmChat( }, []); const contextNoteIdRef = useRef(contextNoteId); contextNoteIdRef.current = contextNoteId; + const sourceNoteIdsRef = useRef(sourceNoteIds); + sourceNoteIdsRef.current = sourceNoteIds; + + const addSourceNote = useCallback((noteId: string) => { + setSourceNoteIds(prev => prev.includes(noteId) ? prev : [...prev, noteId]); + }, []); + + const removeSourceNote = useCallback((noteId: string) => { + setSourceNoteIds(prev => prev.filter(id => id !== noteId)); + }, []); // Wrapper to call onMessagesChange when messages update const setMessages = useCallback((newMessages: StoredMessage[]) => { @@ -122,7 +145,11 @@ export function useLlmChat( getAvailableModels().then(models => { const modelsWithDescription = models.map(m => ({ ...m, - costDescription: m.costMultiplier ? `${m.costMultiplier}x` : undefined + costDescription: m.costMultiplier + ? `${m.costMultiplier}x` + : m.pricing.input === 0 && m.pricing.output === 0 + ? t("llm_chat.free") + : undefined })); setAvailableModels(modelsWithDescription); setHasProvider(models.length > 0); @@ -168,6 +195,9 @@ export function useLlmChat( if (supportsExtendedThinking && typeof content.enableExtendedThinking === "boolean") { setEnableExtendedThinking(content.enableExtendedThinking); } + if (Array.isArray(content.sourceNoteIds)) { + setSourceNoteIds(content.sourceNoteIds); + } // Restore last prompt tokens from the most recent message with usage const lastUsage = [...(content.messages || [])].reverse().find(m => m.usage)?.usage; setLastPromptTokens(lastUsage?.promptTokens ?? 0); @@ -185,6 +215,9 @@ export function useLlmChat( if (supportsExtendedThinking) { content.enableExtendedThinking = enableExtendedThinkingRef.current; } + if (sourceNoteIdsRef.current.length > 0) { + content.sourceNoteIds = sourceNoteIdsRef.current; + } return content; }, [supportsExtendedThinking]); @@ -243,14 +276,18 @@ export function useLlmChat( model: selectedModel || undefined, provider: selectedModelProvider, enableWebSearch, - enableNoteTools, + enableNoteTools: enableNoteTools || sourceNoteIds.length > 0, contextNoteId, - chatNoteId: chatNoteIdRef.current + chatNoteId: chatNoteIdRef.current, + sourceNoteIds: sourceNoteIds.length > 0 ? sourceNoteIds : undefined }; if (supportsExtendedThinking) { streamOptions.enableExtendedThinking = enableExtendedThinking; } + const abortController = new AbortController(); + abortControllerRef.current = abortController; + await streamChatCompletion( apiMessages, streamOptions, @@ -267,13 +304,14 @@ export function useLlmChat( thinkingContent += text; setStreamingThinking(thinkingContent); }, - onToolUse: (toolName, toolInput) => { + onToolUse: (toolName, toolInput, requiresApproval) => { contentBlocks.push({ type: "tool_call", toolCall: { id: randomString(), toolName, - input: toolInput + input: toolInput, + requiresApproval } }); setStreamingBlocks([...contentBlocks]); @@ -353,10 +391,45 @@ export function useLlmChat( setStreamingThinking(""); setPendingCitations([]); setIsStreaming(false); + abortControllerRef.current = null; } + }, + abortController.signal + ).catch((e) => { + // AbortError is expected when user stops generation + if (e instanceof DOMException && e.name === "AbortError") { + // Finalize whatever we have so far + const finalNewMessages: StoredMessage[] = []; + if (thinkingContent) { + finalNewMessages.push({ + id: randomString(), + role: "assistant", + content: thinkingContent, + createdAt: new Date().toISOString(), + type: "thinking" + }); + } + if (contentBlocks.length > 0) { + finalNewMessages.push({ + id: randomString(), + role: "assistant", + content: contentBlocks, + createdAt: new Date().toISOString(), + citations: citations.length > 0 ? citations : undefined + }); + } + if (finalNewMessages.length > 0) { + setMessages([...newMessages, ...finalNewMessages]); + } + setStreamingContent(""); + setStreamingBlocks([]); + setStreamingThinking(""); + setPendingCitations([]); + setIsStreaming(false); + abortControllerRef.current = null; } - ); - }, [input, isStreaming, messages, selectedModel, enableWebSearch, enableNoteTools, enableExtendedThinking, contextNoteId, supportsExtendedThinking, setMessages]); + }); + }, [input, isStreaming, messages, selectedModel, enableWebSearch, enableNoteTools, enableExtendedThinking, contextNoteId, sourceNoteIds, supportsExtendedThinking, setMessages]); const handleKeyDown = useCallback((e: KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { @@ -365,6 +438,51 @@ export function useLlmChat( } }, [handleSubmit]); + /** Stop the current generation by aborting the SSE connection. */ + const stopStreaming = useCallback(() => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }, []); + + /** Approve a pending mutating tool call — execute it server-side and update the message. */ + const approveToolCall = useCallback(async (toolCallId: string) => { + // Find the tool call in messages + for (const msg of messages) { + if (!Array.isArray(msg.content)) continue; + for (const block of msg.content) { + if (block.type === "tool_call" && block.toolCall.id === toolCallId && block.toolCall.requiresApproval && !block.toolCall.result) { + const { result, isError } = await executeToolCall(block.toolCall.toolName, block.toolCall.input); + // Update the tool call block immutably + const updatedContent = msg.content.map(b => + b.type === "tool_call" && b.toolCall.id === toolCallId + ? { ...b, toolCall: { ...b.toolCall, result, isError } } + : b + ); + const updatedMessages = messages.map(m => + m.id === msg.id ? { ...m, content: updatedContent } : m + ); + setMessages(updatedMessages); + return; + } + } + } + }, [messages, setMessages]); + + /** Reject a pending mutating tool call. */ + const rejectToolCall = useCallback((toolCallId: string) => { + const updatedMessages = messages.map(msg => { + if (!Array.isArray(msg.content)) return msg; + const updatedContent = msg.content.map(b => + b.type === "tool_call" && b.toolCall.id === toolCallId + ? { ...b, toolCall: { ...b.toolCall, rejected: true, result: t("llm_chat.rejected_by_user"), isError: true } } + : b + ); + return { ...msg, content: updatedContent }; + }); + setMessages(updatedMessages); + }, [messages, setMessages]); + return { // State messages, @@ -380,6 +498,7 @@ export function useLlmChat( enableNoteTools, enableExtendedThinking, contextNoteId, + sourceNoteIds, lastPromptTokens, messagesEndRef, textareaRef, @@ -395,6 +514,9 @@ export function useLlmChat( setEnableExtendedThinking, setContextNoteId, setChatNoteId, + setSourceNoteIds, + addSourceNote, + removeSourceNote, // Actions handleSubmit, @@ -402,6 +524,9 @@ export function useLlmChat( loadFromContent, getContent, clearMessages, - refreshModels + refreshModels, + approveToolCall, + rejectToolCall, + stopStreaming }; } diff --git a/apps/client/src/widgets/type_widgets/options/llm.tsx b/apps/client/src/widgets/type_widgets/options/llm.tsx index 9da9f50ca62..922abed7977 100644 --- a/apps/client/src/widgets/type_widgets/options/llm.tsx +++ b/apps/client/src/widgets/type_widgets/options/llm.tsx @@ -23,6 +23,7 @@ export default function LlmSettings() { return ( <> + ); @@ -86,6 +87,66 @@ function getMcpEndpointUrl() { return `${window.location.protocol}//localhost:${port}/mcp`; } +function WebSearchSettings() { + const [searchEngine, setSearchEngine] = useTriliumOption("llmWebSearchEngine"); + const [tavilyApiKey, setTavilyApiKey] = useTriliumOption("llmTavilyApiKey"); + const [searxngUrl, setSearxngUrl] = useTriliumOption("llmSearxngUrl"); + const [searchTimeout, setSearchTimeout] = useTriliumOption("llmSearchTimeout"); + + return ( + +

{t("llm.web_search_description")}

+ + + + + + {searchEngine === "tavily" && ( + + setTavilyApiKey((e.target as HTMLInputElement).value)} + placeholder="tvly-..." + /> + + )} + + {searchEngine === "searxng" && ( + + setSearxngUrl((e.target as HTMLInputElement).value)} + placeholder="http://localhost:8888" + /> + + )} + + + setSearchTimeout((e.target as HTMLInputElement).value)} + /> + +
+ ); +} + function McpSettings() { const [mcpEnabled, setMcpEnabled] = useTriliumOptionBool("mcpEnabled"); const endpointUrl = useMemo(() => getMcpEndpointUrl(), []); diff --git a/apps/client/src/widgets/type_widgets/options/llm/AddProviderModal.tsx b/apps/client/src/widgets/type_widgets/options/llm/AddProviderModal.tsx index 4538cde3b88..755bcd8d645 100644 --- a/apps/client/src/widgets/type_widgets/options/llm/AddProviderModal.tsx +++ b/apps/client/src/widgets/type_widgets/options/llm/AddProviderModal.tsx @@ -11,17 +11,26 @@ export interface LlmProviderConfig { name: string; provider: string; apiKey: string; + /** Base URL for self-hosted providers (e.g. Ollama). */ + baseUrl?: string; } export interface ProviderType { id: string; name: string; + /** Whether this provider needs an API key (defaults to true). */ + needsApiKey?: boolean; + /** Whether this provider needs a base URL. */ + needsBaseUrl?: boolean; + /** Default base URL for the provider. */ + defaultBaseUrl?: string; } export const PROVIDER_TYPES: ProviderType[] = [ { id: "anthropic", name: "Anthropic" }, { id: "openai", name: "OpenAI" }, - { id: "google", name: "Google Gemini" } + { id: "google", name: "Google Gemini" }, + { id: "ollama", name: "Ollama", needsApiKey: false, needsBaseUrl: true, defaultBaseUrl: "http://localhost:11434" } ]; interface AddProviderModalProps { @@ -33,19 +42,34 @@ interface AddProviderModalProps { export default function AddProviderModal({ show, onHidden, onSave }: AddProviderModalProps) { const [selectedProvider, setSelectedProvider] = useState(PROVIDER_TYPES[0].id); const [apiKey, setApiKey] = useState(""); + const [baseUrl, setBaseUrl] = useState(""); const formRef = useRef(null); + const providerType = PROVIDER_TYPES.find(p => p.id === selectedProvider); + const needsApiKey = providerType?.needsApiKey !== false; + const needsBaseUrl = providerType?.needsBaseUrl === true; + + function handleProviderChange(value: string) { + setSelectedProvider(value); + const pt = PROVIDER_TYPES.find(p => p.id === value); + if (pt?.defaultBaseUrl) { + setBaseUrl(pt.defaultBaseUrl); + } else { + setBaseUrl(""); + } + } + function handleSubmit() { - if (!apiKey.trim()) { + if (needsApiKey && !apiKey.trim()) { return; } - const providerType = PROVIDER_TYPES.find(p => p.id === selectedProvider); const newProvider: LlmProviderConfig = { id: `${selectedProvider}_${Date.now()}`, name: providerType?.name || selectedProvider, provider: selectedProvider, - apiKey: apiKey.trim() + apiKey: apiKey.trim(), + ...(needsBaseUrl && baseUrl.trim() ? { baseUrl: baseUrl.trim() } : {}) }; onSave(newProvider); @@ -56,6 +80,7 @@ export default function AddProviderModal({ show, onHidden, onSave }: AddProvider function resetForm() { setSelectedProvider(PROVIDER_TYPES[0].id); setApiKey(""); + setBaseUrl(""); } function handleCancel() { @@ -63,6 +88,8 @@ export default function AddProviderModal({ show, onHidden, onSave }: AddProvider onHidden(); } + const isSubmitDisabled = needsApiKey ? !apiKey.trim() : false; + return createPortal( {t("llm.cancel")} - @@ -89,19 +116,31 @@ export default function AddProviderModal({ show, onHidden, onSave }: AddProvider keyProperty="id" titleProperty="name" currentValue={selectedProvider} - onChange={setSelectedProvider} + onChange={handleProviderChange} /> - - - + {needsApiKey && ( + + + + )} + + {needsBaseUrl && ( + + + + )} , document.body ); diff --git a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json index 3385aa4cdb7..2d9f3a4eb03 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json +++ b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json @@ -1 +1 @@ -[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-desktop","type":"label"}],"children":[{"id":"_help_nRqcgfTb97uV","title":"Using the desktop application as a server","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Using the desktop application "},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_Rp0q8bSP6Ayl","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Nix flake"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache using Docker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LLzSMXACKhUs","title":"Trusted proxy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Trusted proxy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_5ERVJb9s4FRD","title":"Traefik","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Traefik"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"HTTPS (TLS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/HTTPS (TLS)"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-user","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Nix flake.clone"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_yeEaYqosGLSh","title":"Third-party cloud hosting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Third-party cloud hosting"},{"name":"iconClass","value":"bx bx-cloud","type":"label"}]},{"id":"_help_iGTnKjubbXkA","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-up-arrow-alt","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-hdd","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_wyaGBBQrl4i3","title":"Hiding the subtree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Hiding the subtree"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]},{"id":"_help_IjZS7iK5EXtb","title":"New Layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout"},{"name":"iconClass","value":"bx bx-layout","type":"label"}],"children":[{"id":"_help_I6p2a06hdnL6","title":"Breadcrumb","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb"},{"name":"iconClass","value":"bx bx-chevron-right","type":"label"}]},{"id":"_help_AlJ73vBCjWDw","title":"Status bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Status bar"},{"name":"iconClass","value":"bx bx-dock-bottom","type":"label"}]}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Printing & Exporting as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF"},{"name":"iconClass","value":"bx bx-printer","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit"},{"name":"iconClass","value":"bx bx-edit","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]},{"id":"_help_gOKqSJgXLcIj","title":"Icon Packs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Icon Packs"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export"},{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}],"children":[{"id":"_help_dj3j8dG4th4l","title":"Process internal links by title","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]},{"id":"_help_YzMcWlCVeW09","title":"Active content","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Active content"},{"name":"iconClass","value":"bx bxs-widget","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]},{"id":"_help_5wZallV2Qo1t","title":"Format Painter","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Format Painter"},{"name":"iconClass","value":"bx bxs-paint-roll","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_WWgeUaBb7UfC","title":"Syntax reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://mermaid.js.org/intro/syntax-reference.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file-blank","type":"label"}],"children":[{"id":"_help_XJGJrpu7F9sh","title":"PDFs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File/PDFs"},{"name":"iconClass","value":"bx bxs-file-pdf","type":"label"}]},{"id":"_help_AjqEeiDUOzj4","title":"Videos","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File/Videos"},{"name":"iconClass","value":"bx bx-video","type":"label"}]}]},{"id":"_help_GWHEkY4I4OE3","title":"Spreadsheets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Spreadsheets"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Calendar"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Table"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Kanban Board","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Kanban Board"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Geo Map"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_zP3PMqaG71Ct","title":"Presentation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Presentation"},{"name":"iconClass","value":"bx bx-slideshow","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_CssoWBu8I7jF","title":"Collection Properties","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Collection Properties"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-bug-alt","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-low-vision","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-error","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-refresh","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bxs-color","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-news","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-book-open","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bxs-file-css","type":"label"}]},{"id":"_help_g1mlRoU8CsqC","title":"Creating an icon pack","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating an icon pack"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_ycBFjKrrwE9p","title":"Exporting static HTML for web publishing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Exporting static HTML for web "},{"name":"iconClass","value":"bx bxs-file-html","type":"label"}]},{"id":"_help_sLIJ6f1dkJYW","title":"Reverse proxy configuration","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Reverse proxy configuration"},{"name":"iconClass","value":"bx bx-world","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-line-chart","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-globe","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bxs-file-plus","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-extension","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/etapi/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bxs-edit","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-windows","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-lock","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/internal/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_64ZTlUPgEPtW","title":"Safe mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Safe mode"},{"name":"iconClass","value":"bx bxs-virus-block","type":"label"}]},{"id":"_help_HAIOFBoYIIdO","title":"Nightly release","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Nightly release"},{"name":"iconClass","value":"bx bx-moon","type":"label"}]},{"id":"_help_ZmT9ln8XJX2o","title":"Read-only database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Read-only database"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_GBBMSlVSOIGP","title":"AI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI"},{"name":"iconClass","value":"bx bx-bot","type":"label"}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-window","type":"label"}],"children":[{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets"},{"name":"iconClass","value":"bx bxs-widget","type":"label"}],"children":[{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GhurYZjh8e1V","title":"Note context aware widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Note context aware widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_M8IppdwVHSjG","title":"Right pane widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Right pane widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_VqGQnnPGnqAU","title":"CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_gMkgcLJ6jBkg","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Troubleshooting"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_4Gn3psZKsfSm","title":"Launch Bar Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets"},{"name":"iconClass","value":"bx bx-dock-left","type":"label"}],"children":[{"id":"_help_IPArqVfDQ4We","title":"Note Title Widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Note Title Widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_gcI7RPbaNSh3","title":"Analog Watch","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Analog Watch"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_KLsqhjaqh1QW","title":"Preact","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact"},{"name":"iconClass","value":"bx bxl-react","type":"label"}],"children":[{"id":"_help_Bqde6BvPo05g","title":"Component libraries","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/Component libraries"},{"name":"iconClass","value":"bx bxs-component","type":"label"}]},{"id":"_help_ykYtbM9k3a7B","title":"Hooks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/Hooks"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_Sg9GrCtyftZf","title":"CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/CSS"},{"name":"iconClass","value":"bx bxs-file-css","type":"label"}]},{"id":"_help_RSssb9S3xgSr","title":"Built-in components","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/Built-in components"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_i9B4IW7b6V6z","title":"Widget showcase","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]}]},{"id":"_help_SPirpZypehBG","title":"Backend scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts"},{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_fZ2IGYFXjkEy","title":"Server-side imports","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Server-side imports"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]}]},{"id":"_help_wqXwKJl6VpNk","title":"Common concepts","type":"book","attributes":[{"name":"iconClass","value":"bx bxl-nodejs","type":"label"}],"children":[{"id":"_help_hA834UaHhSNn","title":"Script bundles","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Common concepts/Script bundles"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-code-curly","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"enforceAttributes":true,"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend/interfaces/FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/backend"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true},{"id":"_help_ApVHZ8JY5ofC","title":"Day.js","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API/Day.js"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]}]},{"id":"_help_vElnKeDNPSVl","title":"Logging","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Logging"},{"name":"iconClass","value":"bx bx-terminal","type":"label"}]},{"id":"_help_cNpC0ITcfX0N","title":"Breaking changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Breaking changes"},{"name":"iconClass","value":"bx bx-up-arrow-alt","type":"label"}]}]},{"id":"_help_Fm0j45KqyHpU","title":"Miscellaneous","type":"book","attributes":[{"name":"iconClass","value":"bx bx-info-circle","type":"label"}],"children":[{"id":"_help_WFbFXrgnDyyU","title":"Privacy Policy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Privacy Policy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_NcsmUYZRWEW4","title":"Patterns of personal knowledge","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Patterns of personal knowledge"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}] \ No newline at end of file +[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-desktop","type":"label"}],"children":[{"id":"_help_nRqcgfTb97uV","title":"Using the desktop application as a server","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Using the desktop application "},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_Rp0q8bSP6Ayl","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Nix flake"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache using Docker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LLzSMXACKhUs","title":"Trusted proxy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Trusted proxy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_5ERVJb9s4FRD","title":"Traefik","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Traefik"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"HTTPS (TLS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/HTTPS (TLS)"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-user","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]},{"id":"_help_Un4wj2Mak2Ky","title":"Nix flake","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Nix flake.clone"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_yeEaYqosGLSh","title":"Third-party cloud hosting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Third-party cloud hosting"},{"name":"iconClass","value":"bx bx-cloud","type":"label"}]},{"id":"_help_iGTnKjubbXkA","title":"System Requirements","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/System Requirements"},{"name":"iconClass","value":"bx bx-chip","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-up-arrow-alt","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-hdd","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_wyaGBBQrl4i3","title":"Hiding the subtree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Hiding the subtree"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]},{"id":"_help_IjZS7iK5EXtb","title":"New Layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout"},{"name":"iconClass","value":"bx bx-layout","type":"label"}],"children":[{"id":"_help_I6p2a06hdnL6","title":"Breadcrumb","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Breadcrumb"},{"name":"iconClass","value":"bx bx-chevron-right","type":"label"}]},{"id":"_help_AlJ73vBCjWDw","title":"Status bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/New Layout/Status bar"},{"name":"iconClass","value":"bx bx-dock-bottom","type":"label"}]}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Printing & Exporting as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF"},{"name":"iconClass","value":"bx bx-printer","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit"},{"name":"iconClass","value":"bx bx-edit","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]},{"id":"_help_gOKqSJgXLcIj","title":"Icon Packs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Icon Packs"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export"},{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}],"children":[{"id":"_help_dj3j8dG4th4l","title":"Process internal links by title","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-window-open","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]},{"id":"_help_YzMcWlCVeW09","title":"Active content","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Active content"},{"name":"iconClass","value":"bx bxs-widget","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]},{"id":"_help_5wZallV2Qo1t","title":"Format Painter","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Format Painter"},{"name":"iconClass","value":"bx bxs-paint-roll","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_WWgeUaBb7UfC","title":"Syntax reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://mermaid.js.org/intro/syntax-reference.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file-blank","type":"label"}],"children":[{"id":"_help_XJGJrpu7F9sh","title":"PDFs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File/PDFs"},{"name":"iconClass","value":"bx bxs-file-pdf","type":"label"}]},{"id":"_help_AjqEeiDUOzj4","title":"Videos","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File/Videos"},{"name":"iconClass","value":"bx bx-video","type":"label"}]}]},{"id":"_help_GWHEkY4I4OE3","title":"Spreadsheets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Spreadsheets"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Calendar"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Table"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Kanban Board","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Kanban Board"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Geo Map"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_zP3PMqaG71Ct","title":"Presentation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Presentation"},{"name":"iconClass","value":"bx bx-slideshow","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_CssoWBu8I7jF","title":"Collection Properties","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Collections/Collection Properties"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-bug-alt","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-low-vision","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-error","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-refresh","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bxs-color","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-news","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-book-open","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bxs-file-css","type":"label"}]},{"id":"_help_g1mlRoU8CsqC","title":"Creating an icon pack","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating an icon pack"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_ycBFjKrrwE9p","title":"Exporting static HTML for web publishing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Exporting static HTML for web "},{"name":"iconClass","value":"bx bxs-file-html","type":"label"}]},{"id":"_help_sLIJ6f1dkJYW","title":"Reverse proxy configuration","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Reverse proxy configuration"},{"name":"iconClass","value":"bx bx-world","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-line-chart","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-globe","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bxs-file-plus","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-extension","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/etapi/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bxs-edit","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-windows","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-lock","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/rest-api/internal/"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_64ZTlUPgEPtW","title":"Safe mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Safe mode"},{"name":"iconClass","value":"bx bxs-virus-block","type":"label"}]},{"id":"_help_HAIOFBoYIIdO","title":"Nightly release","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Nightly release"},{"name":"iconClass","value":"bx bx-moon","type":"label"}]},{"id":"_help_ZmT9ln8XJX2o","title":"Read-only database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Read-only database"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_GBBMSlVSOIGP","title":"AI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI"},{"name":"iconClass","value":"bx bx-bot","type":"label"}],"children":[{"id":"_help_KBdoc00000001","title":"Knowledge Base","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Knowledge Base"},{"name":"iconClass","value":"bx bx-book-open","type":"label"}]}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-window","type":"label"}],"children":[{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets"},{"name":"iconClass","value":"bx bxs-widget","type":"label"}],"children":[{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GhurYZjh8e1V","title":"Note context aware widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Note context aware widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_M8IppdwVHSjG","title":"Right pane widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Right pane widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_VqGQnnPGnqAU","title":"CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_gMkgcLJ6jBkg","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Custom Widgets/Troubleshooting"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_4Gn3psZKsfSm","title":"Launch Bar Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets"},{"name":"iconClass","value":"bx bx-dock-left","type":"label"}],"children":[{"id":"_help_IPArqVfDQ4We","title":"Note Title Widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Note Title Widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_gcI7RPbaNSh3","title":"Analog Watch","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Launch Bar Widgets/Analog Watch"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_KLsqhjaqh1QW","title":"Preact","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact"},{"name":"iconClass","value":"bx bxl-react","type":"label"}],"children":[{"id":"_help_Bqde6BvPo05g","title":"Component libraries","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/Component libraries"},{"name":"iconClass","value":"bx bxs-component","type":"label"}]},{"id":"_help_ykYtbM9k3a7B","title":"Hooks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/Hooks"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_Sg9GrCtyftZf","title":"CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/CSS"},{"name":"iconClass","value":"bx bxs-file-css","type":"label"}]},{"id":"_help_RSssb9S3xgSr","title":"Built-in components","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics/Preact/Built-in components"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_i9B4IW7b6V6z","title":"Widget showcase","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]}]},{"id":"_help_SPirpZypehBG","title":"Backend scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts"},{"name":"iconClass","value":"bx bx-server","type":"label"}],"children":[{"id":"_help_fZ2IGYFXjkEy","title":"Server-side imports","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Server-side imports"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Backend scripts/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]}]},{"id":"_help_wqXwKJl6VpNk","title":"Common concepts","type":"book","attributes":[{"name":"iconClass","value":"bx bxl-nodejs","type":"label"}],"children":[{"id":"_help_hA834UaHhSNn","title":"Script bundles","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Common concepts/Script bundles"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-code-curly","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"enforceAttributes":true,"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/frontend/interfaces/FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://docs.triliumnotes.org/script-api/backend"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"enforceAttributes":true},{"id":"_help_ApVHZ8JY5ofC","title":"Day.js","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API/Day.js"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]}]},{"id":"_help_vElnKeDNPSVl","title":"Logging","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Logging"},{"name":"iconClass","value":"bx bx-terminal","type":"label"}]},{"id":"_help_cNpC0ITcfX0N","title":"Breaking changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Breaking changes"},{"name":"iconClass","value":"bx bx-up-arrow-alt","type":"label"}]}]},{"id":"_help_Fm0j45KqyHpU","title":"Miscellaneous","type":"book","attributes":[{"name":"iconClass","value":"bx bx-info-circle","type":"label"}],"children":[{"id":"_help_WFbFXrgnDyyU","title":"Privacy Policy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Privacy Policy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_NcsmUYZRWEW4","title":"Patterns of personal knowledge","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Miscellaneous/Patterns of personal knowledge"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}] \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html index 00bbfb1eb05..7736be8aa44 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI.html @@ -1,24 +1,45 @@ -

Starting with version v0.102.0, AI/LLM integration has been removed from - the Trilium Notes core.

-

While a significant amount of effort went into developing this feature, - maintaining and supporting it long-term proved to be unsustainable.

-

When upgrading to v0.102.0, your Chat notes will be preserved, but instead - of the dedicated chat window they will be turned to a normal Code note, revealing the underlying - JSON of the conversation.

-

Alternative solutions (MCP)

-

Given the recent advancements of the AI scene, MCP has grown to be more - powerful and facilitates easier integrations with various application.

-

As such, there are third-party solutions that integrate an MCP server - that can be used with Trilium:

+

Trilium includes a built-in AI Chat that lets you interact + with large language models (LLMs) directly within your notes.

+ +

Getting started

+
    +
  1. Go to Options → AI / LLM and add a provider (e.g. + OpenAI, Anthropic, Google, Ollama).
  2. +
  3. Create a new note and set its type to AI Chat, or open + an existing chat from the AI Chat section in the note + tree.
  4. +
  5. Select a model from the dropdown and start chatting.
  6. +
+ +

Features

    -
  • tan-yong-sheng/triliumnext-mcp -
  • -
  • perfectra1n/triliumnext-mcp -
  • +
  • Multiple providers — OpenAI, Anthropic, Google Gemini, + and Ollama (local models) are supported.
  • +
  • Streaming responses — replies appear word by word in + real time.
  • +
  • Note tools — the AI can search, read, create, and + modify your notes when enabled.
  • +
  • Web search — the AI can search the web for up-to-date + information.
  • +
  • Extended thinking — for supported models (Anthropic), + enables deeper reasoning with a visible thought process.
  • +
  • Knowledge Base — select notes as primary sources and + the AI will answer based on their content. See the + Knowledge Base + page for details.
  • +
  • Note context — attach the currently viewed note as + context for more relevant answers.
- \ No newline at end of file + +

Chat options

+

The model selector dropdown includes several toggles:

+
    +
  • Web search (globe icon) — enables web search + tool.
  • +
  • Note access (note icon) — enables tools for reading + and modifying notes.
  • +
  • Extended thinking (brain icon) — enables extended + reasoning (Anthropic models only).
  • +
  • Knowledge Base (book icon) — opens the source + selection panel for knowledge-base mode.
  • +
\ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Knowledge Base.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Knowledge Base.html new file mode 100644 index 00000000000..5831a1c0f1f --- /dev/null +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/AI/Knowledge Base.html @@ -0,0 +1,70 @@ +

What is the Knowledge Base?

+

The Knowledge Base mode allows the AI to answer your questions primarily + using information from your own notes. Think of it as a personal research + assistant that has read your selected notes and can answer questions + about them.

+

This is similar in concept to tools like Google NotebookLM — you select + your sources, and the AI focuses its answers on that material.

+ +

How to use it

+
    +
  1. Open or create an AI Chat note.
  2. +
  3. Click the model selector dropdown in the input area.
  4. +
  5. Toggle Knowledge Base (book icon) to enable it.
  6. +
  7. A source panel will appear below the text input. Use the + autocomplete field to search for and add notes as sources.
  8. +
  9. Add as many source notes as you need (up to 20).
  10. +
  11. Ask your question — the AI will answer primarily from your selected + sources.
  12. +
+ +

How it works

+

When you add source notes, the system:

+
    +
  • Loads a content preview (up to 1500 characters) of each + source note into the AI's context.
  • +
  • Lists child notes of each source, so the AI knows what + sub-topics are available for deeper exploration.
  • +
  • Automatically enables Note tools, allowing the AI to + fetch full note content, search your notes, and navigate the note + hierarchy when it needs more detail.
  • +
  • Instructs the AI to answer primarily from your sources, + cite them when possible, and tell you when the answer is not found in the + provided material.
  • +
+ +

Hybrid approach

+

The Knowledge Base uses a hybrid approach to balance context size and + accuracy:

+
    +
  • Previews give the AI an overview of each source without + consuming the entire context window.
  • +
  • Note tools let the AI fetch the full content of specific + notes when it needs more detail.
  • +
  • Child note listings help the AI navigate your note + hierarchy without unnecessary searching.
  • +
+ +

Managing sources

+
    +
  • To add a source, type in the autocomplete field and + select a note.
  • +
  • To remove a source, click the × button + on its chip.
  • +
  • To disable the Knowledge Base entirely, toggle it off + in the dropdown. This will clear all sources.
  • +
  • Sources are saved with the chat note, so they persist + when you reopen the chat.
  • +
+ +

Tips

+
    +
  • Add parent notes as sources — the AI can see their + children and drill down as needed.
  • +
  • For large topics, add the top-level note and let the AI + explore the subtree using note tools.
  • +
  • The AI will tell you when an answer is not found in + your sources, so you know when to add more material.
  • +
  • You can combine Knowledge Base with Web Search for + answers that reference both your notes and the web.
  • +
diff --git a/apps/server/src/routes/api/llm_chat.ts b/apps/server/src/routes/api/llm_chat.ts index dd5bf149c8b..0aead15cc71 100644 --- a/apps/server/src/routes/api/llm_chat.ts +++ b/apps/server/src/routes/api/llm_chat.ts @@ -3,7 +3,10 @@ import type { Request, Response } from "express"; import { generateChatTitle } from "../../services/llm/chat_title.js"; import { getAllModels, getProviderByType, hasConfiguredProviders, type LlmProviderConfig } from "../../services/llm/index.js"; +import { OllamaProvider } from "../../services/llm/providers/ollama.js"; import { streamToChunks } from "../../services/llm/stream.js"; +import { allToolRegistries } from "../../services/llm/tools/index.js"; +import sql from "../../services/sql.js"; import log from "../../services/log.js"; import { safeExtractMessageAndStackFromError } from "../../services/utils.js"; @@ -51,6 +54,12 @@ async function streamChat(req: Request, res: Response) { } const provider = getProviderByType(config.provider || "anthropic"); + + // Ensure Ollama models are loaded so defaultModel/titleModel are set + if (provider instanceof OllamaProvider) { + await provider.loadModels(); + } + const result = provider.chat(messages, config); // Get pricing and display name for the model @@ -62,7 +71,16 @@ async function streamChat(req: Request, res: Response) { 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 })) { + + // Collect mutating tool names so the stream can flag them for approval + const mutatingToolNames = new Set(); + for (const registry of allToolRegistries) { + for (const name of registry.getMutatingToolNames()) { + mutatingToolNames.add(name); + } + } + + for await (const chunk of streamToChunks(result, { model: modelDisplayName, pricing, mutatingToolNames })) { res.write(`data: ${JSON.stringify(chunk)}\n\n`); // Flush immediately to ensure real-time streaming if (typeof flushableRes.flush === "function") { @@ -90,15 +108,44 @@ async function streamChat(req: Request, res: Response) { /** * Get available models from all configured providers. */ -function getModels(_req: Request, _res: Response) { +async function getModels(_req: Request, _res: Response) { if (!hasConfiguredProviders()) { return { models: [] }; } - return { models: getAllModels() }; + return { models: await getAllModels() }; +} + +/** + * Execute a single tool call after user approval. + * Used for mutating tools that require human-in-the-loop confirmation. + */ +function executeTool(req: Request, _res: Response) { + const { toolName, toolInput } = req.body as { toolName: string; toolInput: Record }; + + if (!toolName || typeof toolName !== "string") { + return { error: "toolName is required" }; + } + + // Find the tool definition across all registries + for (const registry of allToolRegistries) { + for (const [name, def] of registry) { + if (name === toolName) { + if (!def.mutates) { + return { error: "Only mutating tools can be executed via this endpoint" }; + } + + const result = sql.transactional(() => def.execute(toolInput)); + return { result }; + } + } + } + + return { error: `Tool '${toolName}' not found` }; } export default { streamChat, - getModels + getModels, + executeTool }; diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 9e21dcb7b73..d171ad392d6 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -108,6 +108,10 @@ const ALLOWED_OPTIONS = new Set([ // LLM options "llmProviders", "mcpEnabled", + "llmWebSearchEngine", + "llmTavilyApiKey", + "llmSearxngUrl", + "llmSearchTimeout", // OCR options "ocrAutoProcessImages", "ocrMinConfidence" diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index 198fa5c22e4..c5d18f32eb5 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -331,7 +331,8 @@ function register(app: express.Application) { // LLM chat endpoints asyncRoute(PST, "/api/llm-chat/stream", [auth.checkApiAuthOrElectron, csrfMiddleware], llmChatRoute.streamChat, null); - apiRoute(GET, "/api/llm-chat/models", llmChatRoute.getModels); + asyncApiRoute(GET, "/api/llm-chat/models", llmChatRoute.getModels); + apiRoute(PST, "/api/llm-chat/execute-tool", llmChatRoute.executeTool); // no CSRF since this is called from android app route(PST, "/api/sender/login", [loginRateLimiter], loginApiRoute.token, apiResultHandler); diff --git a/apps/server/src/services/llm/chat_title.ts b/apps/server/src/services/llm/chat_title.ts index 7350244cae2..fffec5b2d69 100644 --- a/apps/server/src/services/llm/chat_title.ts +++ b/apps/server/src/services/llm/chat_title.ts @@ -1,5 +1,6 @@ import becca from "../../becca/becca.js"; import { getProvider } from "./index.js"; +import { OllamaProvider } from "./providers/ollama.js"; import log from "../log.js"; import { t } from "i18next"; @@ -28,6 +29,12 @@ export async function generateChatTitle(chatNoteId: string, firstMessage: string } const provider = getProvider(); + + // Ensure Ollama models are loaded so titleModel is set + if (provider instanceof OllamaProvider) { + await provider.loadModels(); + } + const title = await provider.generateTitle(firstMessage); if (title) { note.title = title; diff --git a/apps/server/src/services/llm/index.ts b/apps/server/src/services/llm/index.ts index ebf0a066395..b64cdddf3d0 100644 --- a/apps/server/src/services/llm/index.ts +++ b/apps/server/src/services/llm/index.ts @@ -1,6 +1,7 @@ import type { LlmProvider, ModelInfo } from "./types.js"; import { AnthropicProvider } from "./providers/anthropic.js"; import { GoogleProvider } from "./providers/google.js"; +import { OllamaProvider } from "./providers/ollama.js"; import { OpenAiProvider } from "./providers/openai.js"; import optionService from "../options.js"; import log from "../log.js"; @@ -14,13 +15,16 @@ export interface LlmProviderSetup { name: string; provider: string; apiKey: string; + /** Base URL for self-hosted providers (e.g. Ollama). */ + baseUrl?: string; } /** Factory functions for creating provider instances */ -const providerFactories: Record LlmProvider> = { +const providerFactories: Record LlmProvider> = { anthropic: (apiKey) => new AnthropicProvider(apiKey), openai: (apiKey) => new OpenAiProvider(apiKey), - google: (apiKey) => new GoogleProvider(apiKey) + google: (apiKey) => new GoogleProvider(apiKey), + ollama: (_apiKey, baseUrl) => new OllamaProvider(baseUrl) }; /** Cache of instantiated providers by their config ID */ @@ -73,7 +77,7 @@ export function getProvider(providerId?: string): LlmProvider { throw new Error(`Unknown LLM provider type: ${config.provider}. Available: ${Object.keys(providerFactories).join(", ")}`); } - const provider = factory(config.apiKey); + const provider = factory(config.apiKey, config.baseUrl); cachedProviders[config.id] = provider; return provider; } @@ -102,7 +106,7 @@ export function hasConfiguredProviders(): boolean { /** * Get all models from all configured providers, tagged with their provider type. */ -export function getAllModels(): ModelInfo[] { +export async function getAllModels(): Promise { const configs = getConfiguredProviders(); const seenProviderTypes = new Set(); const allModels: ModelInfo[] = []; @@ -116,6 +120,12 @@ export function getAllModels(): ModelInfo[] { try { const provider = getProvider(config.id); + + // Ollama needs to fetch models from the running instance + if (provider instanceof OllamaProvider) { + await provider.loadModels(); + } + const models = provider.getAvailableModels(); for (const model of models) { allModels.push({ ...model, provider: config.provider }); diff --git a/apps/server/src/services/llm/providers/base_provider.ts b/apps/server/src/services/llm/providers/base_provider.ts index 5000b4fcbfd..114acf857ac 100644 --- a/apps/server/src/services/llm/providers/base_provider.ts +++ b/apps/server/src/services/llm/providers/base_provider.ts @@ -9,9 +9,11 @@ import { generateText, type ModelMessage, stepCountIs, streamText, type ToolSet import yaml from "js-yaml"; import becca from "../../../becca/becca.js"; +import optionService from "../../options.js"; import { getSkillsSummary } from "../skills/index.js"; -import { getNoteMeta,SYSTEM_PROMPT_LIMITS } from "../tools/helpers.js"; +import { getContentPreview, getNoteMeta, SYSTEM_PROMPT_LIMITS } from "../tools/helpers.js"; import { allToolRegistries } from "../tools/index.js"; +import { addTavilySearchTool, addSearxngSearchTool } from "../web_search_tools.js"; import type { LlmProvider, LlmProviderConfig, ModelInfo, ModelPricing, StreamResult } from "../types.js"; const DEFAULT_MAX_TOKENS = 8096; @@ -44,6 +46,90 @@ function buildNoteHint(noteId: string): string | null { ].join("\n"); } +/** Maximum number of source notes to include in the knowledge base prompt. */ +const KB_MAX_SOURCES = 20; +/** Maximum characters of content preview per source note in the KB prompt. */ +const KB_PREVIEW_MAX = 1500; + +/** + * Build the knowledge base section of the system prompt from source note IDs. + * Includes note metadata and extended content previews for each source. + */ +function buildKnowledgeBaseSources(sourceNoteIds: string[]): string | null { + const sources: string[] = []; + + for (const noteId of sourceNoteIds.slice(0, KB_MAX_SOURCES)) { + const note = becca.getNote(noteId); + if (!note) continue; + + const title = note.getTitleOrProtected(); + const preview = note.isContentAvailable() ? getContentPreview(note) : null; + const childNotes = note.getChildNotes().slice(0, 10); + + let entry = `### ${title} (noteId: ${noteId})`; + if (note.type !== "text") { + entry += `\nType: ${note.type}`; + } + if (childNotes.length > 0) { + entry += `\nChild notes: ${childNotes.map(c => `${c.getTitleOrProtected()} (${c.noteId})`).join(", ")}`; + } + if (preview) { + // Use a longer preview for KB sources than the default 500 chars. + // For text notes, strip HTML tags directly instead of running a full + // markdown conversion — this is much cheaper for large documents. + let extendedPreview = preview; + if (preview.length >= 490 && note.isContentAvailable()) { + const content = note.getContent(); + if (typeof content === "string") { + const plain = note.type === "text" + ? content.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim() + : content; + extendedPreview = plain.length > KB_PREVIEW_MAX + ? `${plain.slice(0, KB_PREVIEW_MAX)}…` + : plain; + } + } + entry += `\n\n${extendedPreview}`; + } + sources.push(entry); + } + + if (sources.length === 0) return null; + + // Build a numbered reference list for Harvard-style citations + const refList = sourceNoteIds.slice(0, KB_MAX_SOURCES) + .map((id, i) => { + const note = becca.getNote(id); + return note ? `[${i + 1}] ${note.getTitleOrProtected()} [[${id}]]` : null; + }) + .filter(Boolean); + + return [ + "## Knowledge Base Sources", + "", + "The following notes are the user's selected knowledge base. " + + "Answer questions primarily using information found in these sources. " + + "Use `get_note_content` to read the full content of any source when the preview is insufficient. " + + "You can also use `search_notes` to find related information within source subtrees.", + "", + "**Citation rules**: When citing a source, use Harvard-style numbered references inline, e.g. [1], [2]. " + + "At the end of your response, include a **References** section listing each cited source " + + "with its number and a wiki-link to the note, for example:", + "```", + "## References", + "[1] Note Title [[noteId]]", + "[2] Another Note [[noteId]]", + "```", + "", + "Reference list for this conversation:", + ...refList, + "", + "If the user's question cannot be answered from these sources, clearly say so and offer to search the broader note collection.", + "", + ...sources + ].join("\n"); +} + /** * Build the model list with cost multipliers from a base model definition array. */ @@ -97,14 +183,33 @@ export abstract class BaseProvider implements LlmProvider { } } + // Knowledge base sources + const hasKnowledgeBase = config.sourceNoteIds && config.sourceNoteIds.length > 0; + if (hasKnowledgeBase) { + const kbSection = buildKnowledgeBaseSources(config.sourceNoteIds!); + if (kbSection) { + parts.push(kbSection); + } + } + // Note tools hint - if (config.enableNoteTools) { + if (config.enableNoteTools || hasKnowledgeBase) { parts.push( `You have access to skills that provide specialized instructions. Load a skill with the load_skill tool before performing complex operations.\n\nAvailable skills:\n${getSkillsSummary()}` ); parts.push( `When referring to notes in your responses, use the wiki-link format [[noteId]] to create clickable internal links. Use the note ID (not the title) from tool results. The link will automatically display the note's title and icon, so don't repeat the title in your text. For example: "You can find more details in [[ZjSfLhzlqNY6]]" instead of "You can find more details in the Meeting Notes note ([[ZjSfLhzlqNY6]])".` ); + parts.push( + [ + "You can fully manage the user's notes: search, read, create, edit, rename, delete, move, and clone.", + "Trilium uses a tree hierarchy where notes can have multiple parents (via cloning).", + "Workflow: use search_notes or get_child_notes to find notes, get_note/get_note_content to read them,", + "then create_note, update_note_content, append_to_note, rename_note to edit content,", + "and move_note, clone_note, delete_note to organize the tree.", + "Always confirm destructive actions (delete, overwrite) with the user before proceeding." + ].join(" ") + ); } else if (config.contextNoteId) { parts.push( `You can see the current note's metadata above, but you cannot search or access other notes. If the user asks about other notes, inform them that "Note access" is disabled and they need to enable it in the chat settings (click on the model name dropdown and toggle "Note access").` @@ -157,10 +262,33 @@ export abstract class BaseProvider implements LlmProvider { const tools: ToolSet = {}; if (config.enableWebSearch) { - this.addWebSearchTool(tools); + const searchEngine = optionService.getOptionOrNull("llmWebSearchEngine") || "provider"; + const timeoutSec = parseInt(optionService.getOptionOrNull("llmSearchTimeout") || "15", 10); + const timeoutMs = (timeoutSec > 0 ? timeoutSec : 15) * 1000; + + if (searchEngine === "tavily") { + const apiKey = optionService.getOptionOrNull("llmTavilyApiKey"); + if (apiKey) { + addTavilySearchTool(tools, apiKey, timeoutMs); + } else { + // Fallback to provider default if no API key + this.addWebSearchTool(tools); + } + } else if (searchEngine === "searxng") { + const instanceUrl = optionService.getOptionOrNull("llmSearxngUrl"); + if (instanceUrl) { + addSearxngSearchTool(tools, instanceUrl, timeoutMs); + } else { + // Fallback to provider default if no URL + this.addWebSearchTool(tools); + } + } else { + // "provider" — use provider-specific built-in search + this.addWebSearchTool(tools); + } } - if (config.enableNoteTools) { + if (config.enableNoteTools || (config.sourceNoteIds && config.sourceNoteIds.length > 0)) { for (const registry of allToolRegistries) { Object.assign(tools, registry.toToolSet()); } diff --git a/apps/server/src/services/llm/providers/ollama.ts b/apps/server/src/services/llm/providers/ollama.ts new file mode 100644 index 00000000000..506e53a34dd --- /dev/null +++ b/apps/server/src/services/llm/providers/ollama.ts @@ -0,0 +1,157 @@ +/** + * Ollama provider — uses the OpenAI-compatible API that Ollama exposes. + * + * Because Ollama runs locally with a dynamic model list, this provider + * fetches available models from the Ollama instance at runtime instead + * of using a hardcoded list. + */ + +import { createOpenAI, type OpenAIProvider as OpenAISDKProvider } from "@ai-sdk/openai"; + +import log from "../../log.js"; +import { BaseProvider } from "./base_provider.js"; +import type { ModelInfo, ModelPricing } from "../types.js"; + +const DEFAULT_BASE_URL = "http://localhost:11434"; + +/** Ollama models are local and free. */ +const FREE_PRICING: ModelPricing = { input: 0, output: 0 }; + +/** + * Shape of the Ollama `/api/tags` response. + * See https://github.com/ollama/ollama/blob/main/docs/api.md#list-local-models + */ +interface OllamaTagsResponse { + models: Array<{ + name: string; + model: string; + modified_at?: string; + size?: number; + digest?: string; + details?: { + parent_model?: string; + format?: string; + family?: string; + families?: string[]; + parameter_size?: string; + quantization_level?: string; + }; + }>; +} + +/** + * Parse a parameter_size string like "7.6B" or "3.2B" into a number of billions. + * Returns undefined if the string cannot be parsed. + */ +function parseParamSize(paramSize?: string): number | undefined { + if (!paramSize) return undefined; + const match = paramSize.match(/^([\d.]+)\s*([BMK])/i); + if (!match) return undefined; + const value = parseFloat(match[1]); + const unit = match[2].toUpperCase(); + if (unit === "B") return value; + if (unit === "M") return value / 1000; + if (unit === "K") return value / 1_000_000; + return undefined; +} + +/** + * Build a human-readable display name from Ollama model metadata. + * Example: "llama3.2:latest" → "llama3.2:latest (3.2B, Q4_K_M)" + */ +function formatModelName(m: OllamaTagsResponse["models"][number]): string { + const parts: string[] = []; + if (m.details?.parameter_size) { + parts.push(m.details.parameter_size); + } + if (m.details?.quantization_level) { + parts.push(m.details.quantization_level); + } + if (parts.length > 0) { + return `${m.name} (${parts.join(", ")})`; + } + return m.name; +} + +export class OllamaProvider extends BaseProvider { + name = "ollama"; + protected defaultModel = ""; + protected titleModel = ""; + protected availableModels: ModelInfo[] = []; + protected modelPricing: Record = {}; + + private openai: OpenAISDKProvider; + private baseUrl: string; + private modelsLoaded = false; + + constructor(baseUrl?: string) { + super(); + this.baseUrl = baseUrl || DEFAULT_BASE_URL; + + // Ollama exposes an OpenAI-compatible endpoint at /v1 + this.openai = createOpenAI({ + apiKey: "ollama", // Ollama ignores this but the SDK requires it + baseURL: `${this.baseUrl}/v1` + }); + } + + protected createModel(modelId: string) { + return this.openai(modelId); + } + + override getAvailableModels(): ModelInfo[] { + if (!this.modelsLoaded) { + // Synchronously return whatever we have; the route handler + // calls loadModels() async before returning. + return this.availableModels; + } + return this.availableModels; + } + + /** + * Fetch available models from the Ollama instance. + * Called by the route handler before returning models to the client. + */ + async loadModels(): Promise { + try { + const res = await fetch(`${this.baseUrl}/api/tags`, { + signal: AbortSignal.timeout(5000) + }); + if (!res.ok) { + log.error(`Ollama: failed to fetch models (${res.status})`); + return []; + } + + const data = (await res.json()) as OllamaTagsResponse; + this.availableModels = data.models.map((m, i) => ({ + id: m.name, + name: formatModelName(m), + pricing: FREE_PRICING, + costMultiplier: 0, + isDefault: i === 0 + })); + + this.modelPricing = Object.fromEntries( + this.availableModels.map((m) => [m.id, FREE_PRICING]) + ); + + if (this.availableModels.length > 0) { + this.defaultModel = this.availableModels[0].id; + // Prefer smaller model for titles if available (under 4B params) + const smallModel = data.models.find((m) => { + const size = parseParamSize(m.details?.parameter_size); + return size !== undefined && size < 4; + }) ?? data.models.find((m) => + /small|mini|tiny|phi|gemma.*2b/i.test(m.name) + ); + this.titleModel = smallModel?.name || this.defaultModel; + } + + this.modelsLoaded = true; + return this.availableModels; + } catch (e) { + log.error(`Ollama: failed to connect to ${this.baseUrl}: ${e}`); + return []; + } + } +} diff --git a/apps/server/src/services/llm/stream.ts b/apps/server/src/services/llm/stream.ts index e66da16a5b4..50d35166ec7 100644 --- a/apps/server/src/services/llm/stream.ts +++ b/apps/server/src/services/llm/stream.ts @@ -23,6 +23,8 @@ export interface StreamOptions { model?: string; /** Model pricing for cost calculation (from provider) */ pricing?: ModelPricing; + /** Names of mutating tools that require user approval before execution */ + mutatingToolNames?: Set; } /** @@ -45,7 +47,8 @@ export async function* streamToChunks(result: StreamResult, options: StreamOptio yield { type: "tool_use", toolName: part.toolName, - toolInput: part.input as Record + toolInput: part.input as Record, + ...(options.mutatingToolNames?.has(part.toolName) ? { requiresApproval: true } : {}) }; break; diff --git a/apps/server/src/services/llm/tools/hierarchy_tools.ts b/apps/server/src/services/llm/tools/hierarchy_tools.ts index cb75941c5e4..2e39f2ed31b 100644 --- a/apps/server/src/services/llm/tools/hierarchy_tools.ts +++ b/apps/server/src/services/llm/tools/hierarchy_tools.ts @@ -6,6 +6,8 @@ import { z } from "zod"; import becca from "../../../becca/becca.js"; import type BNote from "../../../becca/entities/bnote.js"; +import branchService from "../../branches.js"; +import cloningService from "../../cloning.js"; import { defineTools } from "./tool_registry.js"; //#region Subtree tool implementation @@ -89,5 +91,83 @@ export const hierarchyTools = defineTools({ return buildSubtree(note, 0, depth); } + }, + + move_note: { + description: "Move a note to a new parent. The note keeps its content and children but changes its location in the tree. Cannot move system notes.", + inputSchema: z.object({ + noteId: z.string().describe("The ID of the note to move"), + newParentNoteId: z.string().describe("The ID of the new parent note") + }), + mutates: true, + execute: ({ noteId, newParentNoteId }) => { + const note = becca.getNote(noteId); + if (!note) { + return { error: "Note not found" }; + } + if (note.noteId === "root") { + return { error: "Cannot move the root note" }; + } + if (note.isProtected) { + return { error: "Note is protected and cannot be moved" }; + } + + const targetParent = becca.getNote(newParentNoteId); + if (!targetParent) { + return { error: "Target parent note not found" }; + } + + // Use the first (primary) parent branch for the move + const branches = note.getParentBranches(); + if (branches.length === 0) { + return { error: "Note has no parent branches" }; + } + + const result = branchService.moveBranchToNote(branches[0], newParentNoteId); + if (Array.isArray(result)) { + // Validation error: [statusCode, { success: false, message }] + const validation = result[1] as { success: boolean; message?: string }; + return { error: validation.message || "Move validation failed" }; + } + if (!result.success) { + return { error: "Failed to move note" }; + } + + return { + success: true, + noteId: note.noteId, + title: note.getTitleOrProtected(), + newParentNoteId, + newParentTitle: targetParent.getTitleOrProtected() + }; + } + }, + + clone_note: { + description: "Clone a note to an additional parent (Trilium supports multiple parents). The note appears in both locations and stays in sync. Use this to organize notes under multiple categories.", + inputSchema: z.object({ + noteId: z.string().describe("The ID of the note to clone"), + parentNoteId: z.string().describe("The ID of the new additional parent note"), + prefix: z.string().optional().describe("Optional branch prefix (displayed before the note title in the tree)") + }), + mutates: true, + execute: ({ noteId, parentNoteId, prefix }) => { + const result = cloningService.cloneNoteToParentNote(noteId, parentNoteId, prefix ?? null); + if (!result.success) { + return { error: result.message || "Clone failed" }; + } + + const note = becca.getNote(noteId); + const parent = becca.getNote(parentNoteId); + + return { + success: true, + noteId, + title: note?.getTitleOrProtected() ?? noteId, + parentNoteId, + parentTitle: parent?.getTitleOrProtected() ?? parentNoteId, + branchId: result.branchId + }; + } } }); diff --git a/apps/server/src/services/llm/tools/note_tools.ts b/apps/server/src/services/llm/tools/note_tools.ts index 2daa55a1cac..e3d2d124321 100644 --- a/apps/server/src/services/llm/tools/note_tools.ts +++ b/apps/server/src/services/llm/tools/note_tools.ts @@ -9,9 +9,13 @@ import markdownImport from "../../import/markdown.js"; import noteService from "../../notes.js"; import SearchContext from "../../search/search_context.js"; import searchService from "../../search/services/search.js"; +import TaskContext from "../../task_context.js"; import { TOOL_LIMITS, getContentPreview, getNoteContentForLlm, getNoteMeta, setNoteContentFromLlm } from "./helpers.js"; import { defineTools } from "./tool_registry.js"; +/** Note IDs that must not be deleted or moved by the LLM. */ +const PROTECTED_SYSTEM_NOTES = new Set(["root", "_hidden", "_share", "_lbRoot", "_globalNoteMap"]); + export const noteTools = defineTools({ search_notes: { description: [ @@ -231,5 +235,68 @@ export const noteTools = defineTools({ return { error: err instanceof Error ? err.message : "Failed to create note" }; } } + }, + + rename_note: { + description: "Change the title of an existing note.", + inputSchema: z.object({ + noteId: z.string().describe("The ID of the note to rename"), + newTitle: z.string().describe("The new title for the note") + }), + mutates: true, + execute: ({ noteId, newTitle }) => { + const note = becca.getNote(noteId); + if (!note) { + return { error: "Note not found" }; + } + if (note.isProtected) { + return { error: "Note is protected and cannot be renamed" }; + } + + const trimmedTitle = newTitle.trim(); + if (!trimmedTitle) { + return { error: "Title cannot be empty" }; + } + + note.title = trimmedTitle; + note.save(); + + return { + success: true, + noteId: note.noteId, + title: note.getTitleOrProtected() + }; + } + }, + + delete_note: { + description: "Delete a note and all its branches (parent links). This is irreversible. The note will be marked as deleted. Cannot delete system notes (root, _hidden, etc.).", + inputSchema: z.object({ + noteId: z.string().describe("The ID of the note to delete") + }), + mutates: true, + execute: ({ noteId }) => { + if (PROTECTED_SYSTEM_NOTES.has(noteId)) { + return { error: "Cannot delete system notes" }; + } + + const note = becca.getNote(noteId); + if (!note) { + return { error: "Note not found" }; + } + if (note.isProtected) { + return { error: "Note is protected and cannot be deleted" }; + } + + const title = note.getTitleOrProtected(); + const taskContext = new TaskContext("no-progress-reporting", "deleteNotes", null); + note.deleteNote(null, taskContext); + + return { + success: true, + noteId, + deletedTitle: title + }; + } } }); diff --git a/apps/server/src/services/llm/tools/tool_registry.ts b/apps/server/src/services/llm/tools/tool_registry.ts index 35fc225182c..e0a399d00bf 100644 --- a/apps/server/src/services/llm/tools/tool_registry.ts +++ b/apps/server/src/services/llm/tools/tool_registry.ts @@ -12,8 +12,6 @@ import { tool } from "ai"; import type { z } from "zod"; import type { ToolSet } from "ai"; -import sql from "../../sql.js"; - /** * Type constraint that rejects Promises at compile time. * Works by requiring `then` to be void if present - Promises have `then: Function`. @@ -56,24 +54,35 @@ export class ToolRegistry implements Iterable<[string, ToolDefinition]> { /** * Convert to an AI SDK ToolSet for use with the LLM chat providers. - * Mutating tools are wrapped in a transaction for consistency with MCP. + * Read-only tools are given an `execute` function so the AI SDK auto-runs them. + * Mutating tools are registered WITHOUT `execute` so the SDK emits a tool-call + * event but does NOT auto-execute — the client must approve first. * (CLS context is provided by the route handler.) */ toToolSet(): ToolSet { const set: ToolSet = {}; for (const [name, def] of this) { - const execute = def.mutates - ? (args: unknown) => sql.transactional(() => def.execute(args)) - : def.execute; - - set[name] = tool({ - description: def.description, - inputSchema: def.inputSchema, - execute - }); + if (def.mutates) { + // No execute → AI SDK emits tool-call but doesn't auto-execute (human-in-the-loop) + set[name] = tool({ + description: def.description, + inputSchema: def.inputSchema + }); + } else { + set[name] = tool({ + description: def.description, + inputSchema: def.inputSchema, + execute: def.execute + }); + } } return set; } + + /** Return the names of all mutating tools in this registry. */ + getMutatingToolNames(): string[] { + return [...this].filter(([, def]) => def.mutates).map(([name]) => name); + } } /** diff --git a/apps/server/src/services/llm/web_search_tools.ts b/apps/server/src/services/llm/web_search_tools.ts new file mode 100644 index 00000000000..37204aad777 --- /dev/null +++ b/apps/server/src/services/llm/web_search_tools.ts @@ -0,0 +1,130 @@ +/** + * Custom web search tools for LLM chat. + * Provides Tavily and SearXNG search as alternatives to provider-built-in web search. + */ + +import { tool } from "ai"; +import type { ToolSet } from "ai"; +import { z } from "zod"; + +import log from "../log.js"; + +const MAX_RESULTS = 5; +const DEFAULT_TIMEOUT_MS = 15_000; + +/** Create an AbortSignal that times out after the given milliseconds. */ +function timeoutSignal(ms: number): AbortSignal { + return AbortSignal.timeout(ms); +} + +interface TavilyResult { + title: string; + url: string; + content: string; +} + +interface SearxngResult { + title: string; + url: string; + content: string; +} + +/** + * Add a Tavily web search tool to the tool set. + * Tavily is an AI-optimized search API that returns clean, relevant results. + * Free tier: 1000 queries/month at https://tavily.com + */ +export function addTavilySearchTool(tools: ToolSet, apiKey: string, timeoutMs: number = DEFAULT_TIMEOUT_MS): void { + tools.web_search = tool({ + description: "Search the web for current information using Tavily. Use this when the user asks about recent events, real-time data, or anything requiring up-to-date web information.", + inputSchema: z.object({ + query: z.string().describe("The search query") + }), + execute: async ({ query }) => { + try { + const response = await fetch("https://api.tavily.com/search", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + api_key: apiKey, + query, + max_results: MAX_RESULTS, + include_answer: true + }), + signal: timeoutSignal(timeoutMs) + }); + + if (!response.ok) { + const errorText = await response.text(); + log.error(`Tavily search failed: ${response.status} ${errorText}`); + return { error: `Search failed: ${response.status}` }; + } + + const data = await response.json() as { answer?: string; results: TavilyResult[] }; + + return { + answer: data.answer || undefined, + results: data.results.map((r: TavilyResult) => ({ + title: r.title, + url: r.url, + snippet: r.content + })) + }; + } catch (e) { + log.error(`Tavily search error: ${e}`); + return { error: `Search failed: ${e instanceof Error ? e.message : String(e)}` }; + } + } + }); +} + +/** + * Add a SearXNG web search tool to the tool set. + * SearXNG is a self-hosted metasearch engine that aggregates results from multiple sources. + * No API key required — just a running SearXNG instance URL. + */ +export function addSearxngSearchTool(tools: ToolSet, instanceUrl: string, timeoutMs: number = DEFAULT_TIMEOUT_MS): void { + // Normalize the URL (remove trailing slash) + const baseUrl = instanceUrl.replace(/\/+$/, ""); + + tools.web_search = tool({ + description: "Search the web for current information using SearXNG. Use this when the user asks about recent events, real-time data, or anything requiring up-to-date web information.", + inputSchema: z.object({ + query: z.string().describe("The search query") + }), + execute: async ({ query }) => { + try { + const params = new URLSearchParams({ + q: query, + format: "json", + categories: "general", + language: "auto" + }); + + const response = await fetch(`${baseUrl}/search?${params}`, { + headers: { "Accept": "application/json" }, + signal: timeoutSignal(timeoutMs) + }); + + if (!response.ok) { + const errorText = await response.text(); + log.error(`SearXNG search failed: ${response.status} ${errorText}`); + return { error: `Search failed: ${response.status}` }; + } + + const data = await response.json() as { results: SearxngResult[] }; + + return { + results: (data.results || []).slice(0, MAX_RESULTS).map((r: SearxngResult) => ({ + title: r.title, + url: r.url, + snippet: r.content + })) + }; + } catch (e) { + log.error(`SearXNG search error: ${e}`); + return { error: `Search failed: ${e instanceof Error ? e.message : String(e)}` }; + } + } + }); +} diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index 92912222d09..3d965422bc3 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -214,6 +214,10 @@ const defaultOptions: DefaultOption[] = [ // AI / LLM { name: "llmProviders", value: "[]", isSynced: true }, { name: "mcpEnabled", value: "false", isSynced: false }, + { name: "llmWebSearchEngine", value: "provider", isSynced: true }, + { name: "llmTavilyApiKey", value: "", isSynced: true }, + { name: "llmSearxngUrl", value: "", isSynced: true }, + { name: "llmSearchTimeout", value: "15", isSynced: true }, // OCR options { name: "ocrAutoProcessImages", value: "false", isSynced: true }, diff --git a/packages/commons/src/lib/llm_api.ts b/packages/commons/src/lib/llm_api.ts index 7554d9d40c6..8c87c61227d 100644 --- a/packages/commons/src/lib/llm_api.ts +++ b/packages/commons/src/lib/llm_api.ts @@ -43,6 +43,8 @@ export interface LlmChatConfig { contextNoteId?: string; /** The note ID of the chat note (used for auto-renaming on first message) */ chatNoteId?: string; + /** Note IDs to use as knowledge base sources */ + sourceNoteIds?: string[]; } /** @@ -97,7 +99,7 @@ export interface LlmUsage { export type LlmStreamChunk = | { type: "text"; content: string } | { type: "thinking"; content: string } - | { type: "tool_use"; toolName: string; toolInput: Record } + | { type: "tool_use"; toolName: string; toolInput: Record; requiresApproval?: boolean } | { type: "tool_result"; toolName: string; result: string; isError?: boolean } | { type: "citation"; citation: LlmCitation } | { type: "usage"; usage: LlmUsage } diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 590e3436dc6..a2f368235e2 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -146,6 +146,14 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions