mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	start from scratch again
This commit is contained in:
		
							
								
								
									
										22
									
								
								db/migrations/0230__ai_llm_options.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								db/migrations/0230__ai_llm_options.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | -- Add new options for AI/LLM integration | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('aiEnabled', 'false', 1); | ||||||
|  |  | ||||||
|  | -- OpenAI settings | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('openaiApiKey', '', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('openaiDefaultModel', 'gpt-4o', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('openaiBaseUrl', 'https://api.openai.com/v1', 1); | ||||||
|  |  | ||||||
|  | -- Anthropic settings   | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('anthropicApiKey', '', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('anthropicDefaultModel', 'claude-3-opus-20240229', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('anthropicBaseUrl', 'https://api.anthropic.com/v1', 1); | ||||||
|  |  | ||||||
|  | -- Ollama settings | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('ollamaEnabled', 'false', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('ollamaBaseUrl', 'http://localhost:11434', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('ollamaDefaultModel', 'llama3', 1); | ||||||
|  |  | ||||||
|  | -- General AI settings | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('aiProviderPrecedence', 'openai,anthropic,ollama', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('aiTemperature', '0.7', 1); | ||||||
|  | INSERT INTO options (name, value, isSynced) VALUES ('aiSystemPrompt', '', 1);  | ||||||
| @@ -37,6 +37,7 @@ import LocalizationOptions from "./options/appearance/i18n.js"; | |||||||
| import CodeBlockOptions from "./options/appearance/code_block.js"; | import CodeBlockOptions from "./options/appearance/code_block.js"; | ||||||
| import EditorOptions from "./options/text_notes/editor.js"; | import EditorOptions from "./options/text_notes/editor.js"; | ||||||
| import ShareSettingsOptions from "./options/other/share_settings.js"; | import ShareSettingsOptions from "./options/other/share_settings.js"; | ||||||
|  | import AiSettingsOptions from "./options/ai_settings.js"; | ||||||
| import type FNote from "../../entities/fnote.js"; | import type FNote from "../../entities/fnote.js"; | ||||||
| import type NoteContextAwareWidget from "../note_context_aware_widget.js"; | import type NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||||
|  |  | ||||||
| @@ -70,6 +71,7 @@ const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = { | |||||||
|     _optionsEtapi: [EtapiOptions], |     _optionsEtapi: [EtapiOptions], | ||||||
|     _optionsBackup: [BackupOptions], |     _optionsBackup: [BackupOptions], | ||||||
|     _optionsSync: [SyncOptions], |     _optionsSync: [SyncOptions], | ||||||
|  |     _optionsAi: [AiSettingsOptions], | ||||||
|     _optionsOther: [ |     _optionsOther: [ | ||||||
|         SearchEngineOptions, |         SearchEngineOptions, | ||||||
|         TrayOptions, |         TrayOptions, | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | import AiSettingsWidget from "./ai_settings.ts"; | ||||||
|  |  | ||||||
|  | export default AiSettingsWidget; | ||||||
							
								
								
									
										216
									
								
								src/public/app/widgets/type_widgets/options/ai_settings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/public/app/widgets/type_widgets/options/ai_settings.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | |||||||
|  | import OptionsWidget from "./options_widget.js"; | ||||||
|  | import { t } from "../../../services/i18n.js"; | ||||||
|  | import type { FilterOptionsByType, OptionMap } from "../../../../../services/options_interface.js"; | ||||||
|  |  | ||||||
|  | export default class AiSettingsWidget extends OptionsWidget { | ||||||
|  |     doRender() { | ||||||
|  |         this.$widget = $(` | ||||||
|  |         <div class="options-section"> | ||||||
|  |             <h4>${t("ai_llm.title")}</h4> | ||||||
|  |  | ||||||
|  |             <div class="form-group"> | ||||||
|  |                 <label> | ||||||
|  |                     <input class="ai-enabled" type="checkbox"> | ||||||
|  |                     ${t("ai_llm.enable_ai_features")} | ||||||
|  |                 </label> | ||||||
|  |                 <div class="help-text">${t("ai_llm.enable_ai_description")}</div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <hr /> | ||||||
|  |  | ||||||
|  |             <div class="ai-providers-section"> | ||||||
|  |                 <h5>${t("ai_llm.provider_configuration")}</h5> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.provider_precedence")}</label> | ||||||
|  |                     <input class="ai-provider-precedence form-control" type="text"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.provider_precedence_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.temperature")}</label> | ||||||
|  |                     <input class="ai-temperature form-control" type="number" min="0" max="2" step="0.1"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.temperature_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.system_prompt")}</label> | ||||||
|  |                     <textarea class="ai-system-prompt form-control" rows="3"></textarea> | ||||||
|  |                     <div class="help-text">${t("ai_llm.system_prompt_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <hr /> | ||||||
|  |  | ||||||
|  |             <div class="ai-provider"> | ||||||
|  |                 <h5>${t("ai_llm.openai_configuration")}</h5> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.api_key")}</label> | ||||||
|  |                     <input class="openai-api-key form-control" type="password"> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.default_model")}</label> | ||||||
|  |                     <input class="openai-default-model form-control" type="text"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.openai_model_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.base_url")}</label> | ||||||
|  |                     <input class="openai-base-url form-control" type="text"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.openai_url_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <hr /> | ||||||
|  |  | ||||||
|  |             <div class="ai-provider"> | ||||||
|  |                 <h5>${t("ai_llm.anthropic_configuration")}</h5> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.api_key")}</label> | ||||||
|  |                     <input class="anthropic-api-key form-control" type="password"> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.default_model")}</label> | ||||||
|  |                     <input class="anthropic-default-model form-control" type="text"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.anthropic_model_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.base_url")}</label> | ||||||
|  |                     <input class="anthropic-base-url form-control" type="text"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.anthropic_url_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <hr /> | ||||||
|  |  | ||||||
|  |             <div class="ai-provider"> | ||||||
|  |                 <h5>${t("ai_llm.ollama_configuration")}</h5> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label> | ||||||
|  |                         <input class="ollama-enabled" type="checkbox"> | ||||||
|  |                         ${t("ai_llm.enable_ollama")} | ||||||
|  |                     </label> | ||||||
|  |                     <div class="help-text">${t("ai_llm.enable_ollama_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.base_url")}</label> | ||||||
|  |                     <input class="ollama-base-url form-control" type="text"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.ollama_url_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|  |                 <div class="form-group"> | ||||||
|  |                     <label>${t("ai_llm.default_model")}</label> | ||||||
|  |                     <input class="ollama-default-model form-control" type="text"> | ||||||
|  |                     <div class="help-text">${t("ai_llm.ollama_model_description")}</div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div>`); | ||||||
|  |  | ||||||
|  |         const $aiEnabled = this.$widget.find('.ai-enabled'); | ||||||
|  |         $aiEnabled.on('change', async () => { | ||||||
|  |             await this.updateOption('aiEnabled', $aiEnabled.prop('checked') ? "true" : "false"); | ||||||
|  |             this.updateAiSectionVisibility(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $ollamaEnabled = this.$widget.find('.ollama-enabled'); | ||||||
|  |         $ollamaEnabled.on('change', async () => { | ||||||
|  |             await this.updateOption('ollamaEnabled', $ollamaEnabled.prop('checked') ? "true" : "false"); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $aiProviderPrecedence = this.$widget.find('.ai-provider-precedence'); | ||||||
|  |         $aiProviderPrecedence.on('change', async () => { | ||||||
|  |             await this.updateOption('aiProviderPrecedence', $aiProviderPrecedence.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $aiTemperature = this.$widget.find('.ai-temperature'); | ||||||
|  |         $aiTemperature.on('change', async () => { | ||||||
|  |             await this.updateOption('aiTemperature', $aiTemperature.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $aiSystemPrompt = this.$widget.find('.ai-system-prompt'); | ||||||
|  |         $aiSystemPrompt.on('change', async () => { | ||||||
|  |             await this.updateOption('aiSystemPrompt', $aiSystemPrompt.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $openaiApiKey = this.$widget.find('.openai-api-key'); | ||||||
|  |         $openaiApiKey.on('change', async () => { | ||||||
|  |             await this.updateOption('openaiApiKey', $openaiApiKey.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $openaiDefaultModel = this.$widget.find('.openai-default-model'); | ||||||
|  |         $openaiDefaultModel.on('change', async () => { | ||||||
|  |             await this.updateOption('openaiDefaultModel', $openaiDefaultModel.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $openaiBaseUrl = this.$widget.find('.openai-base-url'); | ||||||
|  |         $openaiBaseUrl.on('change', async () => { | ||||||
|  |             await this.updateOption('openaiBaseUrl', $openaiBaseUrl.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $anthropicApiKey = this.$widget.find('.anthropic-api-key'); | ||||||
|  |         $anthropicApiKey.on('change', async () => { | ||||||
|  |             await this.updateOption('anthropicApiKey', $anthropicApiKey.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $anthropicDefaultModel = this.$widget.find('.anthropic-default-model'); | ||||||
|  |         $anthropicDefaultModel.on('change', async () => { | ||||||
|  |             await this.updateOption('anthropicDefaultModel', $anthropicDefaultModel.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $anthropicBaseUrl = this.$widget.find('.anthropic-base-url'); | ||||||
|  |         $anthropicBaseUrl.on('change', async () => { | ||||||
|  |             await this.updateOption('anthropicBaseUrl', $anthropicBaseUrl.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $ollamaBaseUrl = this.$widget.find('.ollama-base-url'); | ||||||
|  |         $ollamaBaseUrl.on('change', async () => { | ||||||
|  |             await this.updateOption('ollamaBaseUrl', $ollamaBaseUrl.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         const $ollamaDefaultModel = this.$widget.find('.ollama-default-model'); | ||||||
|  |         $ollamaDefaultModel.on('change', async () => { | ||||||
|  |             await this.updateOption('ollamaDefaultModel', $ollamaDefaultModel.val() as string); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return this.$widget; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     updateAiSectionVisibility() { | ||||||
|  |         if (!this.$widget) return; | ||||||
|  |  | ||||||
|  |         const aiEnabled = this.$widget.find('.ai-enabled').prop('checked'); | ||||||
|  |         this.$widget.find('.ai-providers-section').toggle(aiEnabled); | ||||||
|  |         this.$widget.find('.ai-provider').toggle(aiEnabled); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     optionsLoaded(options: OptionMap) { | ||||||
|  |         if (!this.$widget) return; | ||||||
|  |  | ||||||
|  |         this.setCheckboxState(this.$widget.find('.ai-enabled'), options.aiEnabled); | ||||||
|  |         this.setCheckboxState(this.$widget.find('.ollama-enabled'), options.ollamaEnabled); | ||||||
|  |  | ||||||
|  |         this.$widget.find('.ai-provider-precedence').val(options.aiProviderPrecedence); | ||||||
|  |         this.$widget.find('.ai-temperature').val(options.aiTemperature); | ||||||
|  |         this.$widget.find('.ai-system-prompt').val(options.aiSystemPrompt); | ||||||
|  |  | ||||||
|  |         this.$widget.find('.openai-api-key').val(options.openaiApiKey); | ||||||
|  |         this.$widget.find('.openai-default-model').val(options.openaiDefaultModel); | ||||||
|  |         this.$widget.find('.openai-base-url').val(options.openaiBaseUrl); | ||||||
|  |  | ||||||
|  |         this.$widget.find('.anthropic-api-key').val(options.anthropicApiKey); | ||||||
|  |         this.$widget.find('.anthropic-default-model').val(options.anthropicDefaultModel); | ||||||
|  |         this.$widget.find('.anthropic-base-url').val(options.anthropicBaseUrl); | ||||||
|  |  | ||||||
|  |         this.$widget.find('.ollama-base-url').val(options.ollamaBaseUrl); | ||||||
|  |         this.$widget.find('.ollama-default-model').val(options.ollamaDefaultModel); | ||||||
|  |  | ||||||
|  |         this.updateAiSectionVisibility(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1120,6 +1120,32 @@ | |||||||
|     "layout-vertical-description": "launcher bar is on the left (default)", |     "layout-vertical-description": "launcher bar is on the left (default)", | ||||||
|     "layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width." |     "layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width." | ||||||
|   }, |   }, | ||||||
|  |   "ai_llm": { | ||||||
|  |     "title": "AI/LLM Integration", | ||||||
|  |     "enable_ai_features": "Enable AI/LLM features", | ||||||
|  |     "enable_ai_description": "Enable AI features like note summarization, content generation, and other LLM capabilities", | ||||||
|  |     "provider_configuration": "AI Provider Configuration", | ||||||
|  |     "provider_precedence": "Provider Precedence", | ||||||
|  |     "provider_precedence_description": "Comma-separated list of providers in order of precedence (e.g., 'openai,anthropic,ollama')", | ||||||
|  |     "temperature": "Temperature", | ||||||
|  |     "temperature_description": "Controls randomness in responses (0 = deterministic, 2 = maximum randomness)", | ||||||
|  |     "system_prompt": "System Prompt", | ||||||
|  |     "system_prompt_description": "Default system prompt used for all AI interactions", | ||||||
|  |     "openai_configuration": "OpenAI Configuration", | ||||||
|  |     "api_key": "API Key", | ||||||
|  |     "default_model": "Default Model", | ||||||
|  |     "openai_model_description": "Examples: gpt-4o, gpt-4-turbo, gpt-3.5-turbo", | ||||||
|  |     "base_url": "Base URL", | ||||||
|  |     "openai_url_description": "Default: https://api.openai.com/v1", | ||||||
|  |     "anthropic_configuration": "Anthropic Configuration", | ||||||
|  |     "anthropic_model_description": "Examples: claude-3-opus-20240229, claude-3-sonnet-20240229", | ||||||
|  |     "anthropic_url_description": "Default: https://api.anthropic.com/v1", | ||||||
|  |     "ollama_configuration": "Ollama Configuration", | ||||||
|  |     "enable_ollama": "Enable Ollama", | ||||||
|  |     "enable_ollama_description": "Enable Ollama for local AI model usage", | ||||||
|  |     "ollama_url_description": "Default: http://localhost:11434", | ||||||
|  |     "ollama_model_description": "Examples: llama3, mistral, phi3" | ||||||
|  |   }, | ||||||
|   "zoom_factor": { |   "zoom_factor": { | ||||||
|     "title": "Zoom Factor (desktop build only)", |     "title": "Zoom Factor (desktop build only)", | ||||||
|     "description": "Zooming can be controlled with CTRL+- and CTRL+= shortcuts as well." |     "description": "Zooming can be controlled with CTRL+- and CTRL+= shortcuts as well." | ||||||
|   | |||||||
| @@ -77,7 +77,21 @@ const ALLOWED_OPTIONS = new Set([ | |||||||
|     "backgroundEffects", |     "backgroundEffects", | ||||||
|     "allowedHtmlTags", |     "allowedHtmlTags", | ||||||
|     "redirectBareDomain", |     "redirectBareDomain", | ||||||
|     "showLoginInShareTheme" |     "showLoginInShareTheme", | ||||||
|  |     // AI/LLM integration options | ||||||
|  |     "aiEnabled", | ||||||
|  |     "openaiApiKey", | ||||||
|  |     "openaiDefaultModel", | ||||||
|  |     "openaiBaseUrl", | ||||||
|  |     "anthropicApiKey", | ||||||
|  |     "anthropicDefaultModel", | ||||||
|  |     "anthropicBaseUrl", | ||||||
|  |     "ollamaEnabled", | ||||||
|  |     "ollamaBaseUrl", | ||||||
|  |     "ollamaDefaultModel", | ||||||
|  |     "aiProviderPrecedence", | ||||||
|  |     "aiTemperature", | ||||||
|  |     "aiSystemPrompt" | ||||||
| ]); | ]); | ||||||
|  |  | ||||||
| function getOptions() { | function getOptions() { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import build from "./build.js"; | |||||||
| import packageJson from "../../package.json" with { type: "json" }; | import packageJson from "../../package.json" with { type: "json" }; | ||||||
| import dataDir from "./data_dir.js"; | import dataDir from "./data_dir.js"; | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 229; | const APP_DB_VERSION = 230; | ||||||
| const SYNC_VERSION = 35; | const SYNC_VERSION = 35; | ||||||
| const CLIPPER_PROTOCOL_VERSION = "1.0"; | const CLIPPER_PROTOCOL_VERSION = "1.0"; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -274,6 +274,7 @@ function buildHiddenSubtreeDefinition(helpSubtree: HiddenSubtreeItem[]): HiddenS | |||||||
|                     { id: "_optionsEtapi", title: t("hidden-subtree.etapi-title"), type: "contentWidget", icon: "bx-extension" }, |                     { id: "_optionsEtapi", title: t("hidden-subtree.etapi-title"), type: "contentWidget", icon: "bx-extension" }, | ||||||
|                     { id: "_optionsBackup", title: t("hidden-subtree.backup-title"), type: "contentWidget", icon: "bx-data" }, |                     { id: "_optionsBackup", title: t("hidden-subtree.backup-title"), type: "contentWidget", icon: "bx-data" }, | ||||||
|                     { id: "_optionsSync", title: t("hidden-subtree.sync-title"), type: "contentWidget", icon: "bx-wifi" }, |                     { id: "_optionsSync", title: t("hidden-subtree.sync-title"), type: "contentWidget", icon: "bx-wifi" }, | ||||||
|  |                     { id: "_optionsAi", title: t("hidden-subtree.ai-llm-title"), type: "contentWidget", icon: "bx-bot" }, | ||||||
|                     { id: "_optionsOther", title: t("hidden-subtree.other"), type: "contentWidget", icon: "bx-dots-horizontal" }, |                     { id: "_optionsOther", title: t("hidden-subtree.other"), type: "contentWidget", icon: "bx-dots-horizontal" }, | ||||||
|                     { id: "_optionsAdvanced", title: t("hidden-subtree.advanced-title"), type: "contentWidget" } |                     { id: "_optionsAdvanced", title: t("hidden-subtree.advanced-title"), type: "contentWidget" } | ||||||
|                 ] |                 ] | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								src/services/llm/embeddings/base_embeddings.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/services/llm/embeddings/base_embeddings.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | import type { EmbeddingProvider, EmbeddingConfig, NoteEmbeddingContext } from './embeddings_interface.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Base class that implements common functionality for embedding providers | ||||||
|  |  */ | ||||||
|  | export abstract class BaseEmbeddingProvider implements EmbeddingProvider { | ||||||
|  |     abstract name: string; | ||||||
|  |     protected config: EmbeddingConfig; | ||||||
|  |  | ||||||
|  |     constructor(config: EmbeddingConfig) { | ||||||
|  |         this.config = config; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getConfig(): EmbeddingConfig { | ||||||
|  |         return this.config; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     abstract generateEmbeddings(text: string): Promise<Float32Array>; | ||||||
|  |     abstract generateBatchEmbeddings(texts: string[]): Promise<Float32Array[]>; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Generates a rich text representation of a note's context for embedding | ||||||
|  |      */ | ||||||
|  |     protected generateNoteContextText(context: NoteEmbeddingContext): string { | ||||||
|  |         const parts = [ | ||||||
|  |             `Title: ${context.title}`, | ||||||
|  |             `Type: ${context.type}`, | ||||||
|  |             `MIME: ${context.mime}`, | ||||||
|  |             `Created: ${context.dateCreated}`, | ||||||
|  |             `Modified: ${context.dateModified}` | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         if (context.attributes.length > 0) { | ||||||
|  |             parts.push('Attributes:'); | ||||||
|  |             for (const attr of context.attributes) { | ||||||
|  |                 parts.push(`  ${attr.type} - ${attr.name}: ${attr.value}`); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (context.parentTitles.length > 0) { | ||||||
|  |             parts.push('Parent Notes:'); | ||||||
|  |             parts.push(...context.parentTitles.map(t => `  ${t}`)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (context.childTitles.length > 0) { | ||||||
|  |             parts.push('Child Notes:'); | ||||||
|  |             parts.push(...context.childTitles.map(t => `  ${t}`)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (context.attachments.length > 0) { | ||||||
|  |             parts.push('Attachments:'); | ||||||
|  |             for (const att of context.attachments) { | ||||||
|  |                 parts.push(`  ${att.title} (${att.mime})`); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         parts.push('Content:', context.content); | ||||||
|  |  | ||||||
|  |         return parts.join('\n'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Default implementation that converts note context to text and generates embeddings | ||||||
|  |      */ | ||||||
|  |     async generateNoteEmbeddings(context: NoteEmbeddingContext): Promise<Float32Array> { | ||||||
|  |         const text = this.generateNoteContextText(context); | ||||||
|  |         return this.generateEmbeddings(text); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Default implementation that processes notes in batch | ||||||
|  |      */ | ||||||
|  |     async generateBatchNoteEmbeddings(contexts: NoteEmbeddingContext[]): Promise<Float32Array[]> { | ||||||
|  |         const texts = contexts.map(ctx => this.generateNoteContextText(ctx)); | ||||||
|  |         return this.generateBatchEmbeddings(texts); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								src/services/llm/embeddings/embeddings_interface.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/services/llm/embeddings/embeddings_interface.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | import type { NoteType, AttributeType } from "../../../becca/entities/rows.js"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Represents the context of a note that will be embedded | ||||||
|  |  */ | ||||||
|  | export interface NoteEmbeddingContext { | ||||||
|  |     noteId: string; | ||||||
|  |     title: string; | ||||||
|  |     content: string; | ||||||
|  |     type: NoteType; | ||||||
|  |     mime: string; | ||||||
|  |     dateCreated: string; | ||||||
|  |     dateModified: string; | ||||||
|  |     attributes: { | ||||||
|  |         type: AttributeType; | ||||||
|  |         name: string; | ||||||
|  |         value: string; | ||||||
|  |     }[]; | ||||||
|  |     parentTitles: string[]; | ||||||
|  |     childTitles: string[]; | ||||||
|  |     attachments: { | ||||||
|  |         title: string; | ||||||
|  |         mime: string; | ||||||
|  |     }[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Configuration for how embeddings should be generated | ||||||
|  |  */ | ||||||
|  | export interface EmbeddingConfig { | ||||||
|  |     model: string; | ||||||
|  |     dimension: number; | ||||||
|  |     type: 'float32' | 'float64'; | ||||||
|  |     normalize?: boolean; | ||||||
|  |     batchSize?: number; | ||||||
|  |     contextWindowSize?: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Core interface that all embedding providers must implement | ||||||
|  |  */ | ||||||
|  | export interface EmbeddingProvider { | ||||||
|  |     name: string; | ||||||
|  |     getConfig(): EmbeddingConfig; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Generate embeddings for a single piece of text | ||||||
|  |      */ | ||||||
|  |     generateEmbeddings(text: string): Promise<Float32Array>; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Generate embeddings for multiple pieces of text in batch | ||||||
|  |      */ | ||||||
|  |     generateBatchEmbeddings(texts: string[]): Promise<Float32Array[]>; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Generate embeddings for a note with its full context | ||||||
|  |      */ | ||||||
|  |     generateNoteEmbeddings(context: NoteEmbeddingContext): Promise<Float32Array>; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Generate embeddings for multiple notes with their contexts in batch | ||||||
|  |      */ | ||||||
|  |     generateBatchNoteEmbeddings(contexts: NoteEmbeddingContext[]): Promise<Float32Array[]>; | ||||||
|  | } | ||||||
| @@ -46,6 +46,21 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi | |||||||
|     passwordDerivedKeySalt: string; |     passwordDerivedKeySalt: string; | ||||||
|     encryptedDataKey: string; |     encryptedDataKey: string; | ||||||
|  |  | ||||||
|  |     // AI/LLM integration options | ||||||
|  |     aiEnabled: string; | ||||||
|  |     openaiApiKey: string; | ||||||
|  |     openaiDefaultModel: string; | ||||||
|  |     openaiBaseUrl: string; | ||||||
|  |     anthropicApiKey: string; | ||||||
|  |     anthropicDefaultModel: string; | ||||||
|  |     anthropicBaseUrl: string; | ||||||
|  |     ollamaEnabled: string; | ||||||
|  |     ollamaBaseUrl: string; | ||||||
|  |     ollamaDefaultModel: string; | ||||||
|  |     aiProviderPrecedence: string; | ||||||
|  |     aiTemperature: string; | ||||||
|  |     aiSystemPrompt: string; | ||||||
|  |  | ||||||
|     lastSyncedPull: number; |     lastSyncedPull: number; | ||||||
|     lastSyncedPush: number; |     lastSyncedPush: number; | ||||||
|     revisionSnapshotTimeInterval: number; |     revisionSnapshotTimeInterval: number; | ||||||
|   | |||||||
| @@ -241,6 +241,7 @@ | |||||||
|     "etapi-title": "ETAPI", |     "etapi-title": "ETAPI", | ||||||
|     "backup-title": "Backup", |     "backup-title": "Backup", | ||||||
|     "sync-title": "Sync", |     "sync-title": "Sync", | ||||||
|  |     "ai-llm-title": "AI/LLM", | ||||||
|     "other": "Other", |     "other": "Other", | ||||||
|     "advanced-title": "Advanced", |     "advanced-title": "Advanced", | ||||||
|     "visible-launchers-title": "Visible Launchers", |     "visible-launchers-title": "Visible Launchers", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user