mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	add anthropic options as well
This commit is contained in:
		| @@ -60,6 +60,20 @@ interface OpenAIModelResponse { | |||||||
|     }>; |     }>; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | interface AnthropicModelResponse { | ||||||
|  |     success: boolean; | ||||||
|  |     chatModels: Array<{ | ||||||
|  |         id: string; | ||||||
|  |         name: string; | ||||||
|  |         type: string; | ||||||
|  |     }>; | ||||||
|  |     embeddingModels: Array<{ | ||||||
|  |         id: string; | ||||||
|  |         name: string; | ||||||
|  |         type: string; | ||||||
|  |     }>; | ||||||
|  | } | ||||||
|  |  | ||||||
| export default class AiSettingsWidget extends OptionsWidget { | export default class AiSettingsWidget extends OptionsWidget { | ||||||
|     private statsRefreshInterval: NodeJS.Timeout | null = null; |     private statsRefreshInterval: NodeJS.Timeout | null = null; | ||||||
|     private indexRebuildRefreshInterval: NodeJS.Timeout | null = null; |     private indexRebuildRefreshInterval: NodeJS.Timeout | null = null; | ||||||
| @@ -221,6 +235,7 @@ export default class AiSettingsWidget extends OptionsWidget { | |||||||
|                                     <option value="claude-3-haiku-20240307">Claude 3 Haiku</option> |                                     <option value="claude-3-haiku-20240307">Claude 3 Haiku</option> | ||||||
|                                 </select> |                                 </select> | ||||||
|                                 <div class="form-text">${t("ai_llm.anthropic_model_description")}</div> |                                 <div class="form-text">${t("ai_llm.anthropic_model_description")}</div> | ||||||
|  |                                 <button class="btn btn-sm btn-outline-secondary refresh-anthropic-models">${t("ai_llm.refresh_models")}</button> | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
| @@ -623,6 +638,63 @@ export default class AiSettingsWidget extends OptionsWidget { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         // Anthropic models refresh button | ||||||
|  |         const $refreshAnthropicModels = this.$widget.find('.refresh-anthropic-models'); | ||||||
|  |         $refreshAnthropicModels.on('click', async () => { | ||||||
|  |             $refreshAnthropicModels.prop('disabled', true); | ||||||
|  |             $refreshAnthropicModels.html(`<i class="spinner-border spinner-border-sm"></i>`); | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 const anthropicBaseUrl = this.$widget.find('.anthropic-base-url').val() as string; | ||||||
|  |                 const response = await server.post<AnthropicModelResponse>('anthropic/list-models', { baseUrl: anthropicBaseUrl }); | ||||||
|  |  | ||||||
|  |                 if (response && response.success) { | ||||||
|  |                     // Update the chat models dropdown | ||||||
|  |                     if (response.chatModels?.length > 0) { | ||||||
|  |                         const $chatModelSelect = this.$widget.find('.anthropic-default-model'); | ||||||
|  |                         const currentChatValue = $chatModelSelect.val(); | ||||||
|  |  | ||||||
|  |                         // Clear existing options | ||||||
|  |                         $chatModelSelect.empty(); | ||||||
|  |  | ||||||
|  |                         // Sort models by name | ||||||
|  |                         const sortedChatModels = [...response.chatModels].sort((a, b) => a.name.localeCompare(b.name)); | ||||||
|  |  | ||||||
|  |                         // Add models to the dropdown | ||||||
|  |                         sortedChatModels.forEach(model => { | ||||||
|  |                             $chatModelSelect.append(`<option value="${model.id}">${model.name}</option>`); | ||||||
|  |                         }); | ||||||
|  |  | ||||||
|  |                         // Try to restore the previously selected value | ||||||
|  |                         if (currentChatValue) { | ||||||
|  |                             $chatModelSelect.val(currentChatValue); | ||||||
|  |                             // If the value doesn't exist anymore, select the first option | ||||||
|  |                             if (!$chatModelSelect.val()) { | ||||||
|  |                                 $chatModelSelect.prop('selectedIndex', 0); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     // Handle embedding models if they exist | ||||||
|  |                     if (response.embeddingModels?.length > 0) { | ||||||
|  |                         toastService.showMessage(`Found ${response.embeddingModels.length} Anthropic embedding models.`); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     // Show success message | ||||||
|  |                     const totalModels = (response.chatModels?.length || 0) + (response.embeddingModels?.length || 0); | ||||||
|  |                     toastService.showMessage(`${totalModels} Anthropic models found.`); | ||||||
|  |                 } else { | ||||||
|  |                     toastService.showError(`No Anthropic models found. Please check your API key and settings.`); | ||||||
|  |                 } | ||||||
|  |             } catch (e) { | ||||||
|  |                 console.error(`Error fetching Anthropic models:`, e); | ||||||
|  |                 toastService.showError(`Error fetching Anthropic models: ${e}`); | ||||||
|  |             } finally { | ||||||
|  |                 $refreshAnthropicModels.prop('disabled', false); | ||||||
|  |                 $refreshAnthropicModels.html(`<span class="bx bx-refresh"></span>`); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         // Embedding options event handlers |         // Embedding options event handlers | ||||||
|         const $embeddingAutoUpdateEnabled = this.$widget.find('.embedding-auto-update-enabled'); |         const $embeddingAutoUpdateEnabled = this.$widget.find('.embedding-auto-update-enabled'); | ||||||
|         $embeddingAutoUpdateEnabled.on('change', async () => { |         $embeddingAutoUpdateEnabled.on('change', async () => { | ||||||
|   | |||||||
| @@ -1149,6 +1149,7 @@ | |||||||
|     "openai_url_description": "Default: https://api.openai.com/v1", |     "openai_url_description": "Default: https://api.openai.com/v1", | ||||||
|     "anthropic_configuration": "Anthropic Configuration", |     "anthropic_configuration": "Anthropic Configuration", | ||||||
|     "anthropic_model_description": "Examples: claude-3-opus-20240229, claude-3-sonnet-20240229", |     "anthropic_model_description": "Examples: claude-3-opus-20240229, claude-3-sonnet-20240229", | ||||||
|  |     "anthropic_embedding_model_description": "Anthropic embedding model (not available yet)", | ||||||
|     "anthropic_url_description": "Default: https://api.anthropic.com/v1", |     "anthropic_url_description": "Default: https://api.anthropic.com/v1", | ||||||
|     "ollama_configuration": "Ollama Configuration", |     "ollama_configuration": "Ollama Configuration", | ||||||
|     "enable_ollama": "Enable Ollama", |     "enable_ollama": "Enable Ollama", | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								src/routes/api/anthropic.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/routes/api/anthropic.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | import axios from 'axios'; | ||||||
|  | import options from "../../services/options.js"; | ||||||
|  | import log from "../../services/log.js"; | ||||||
|  | import type { Request, Response } from "express"; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * List available models from Anthropic | ||||||
|  |  */ | ||||||
|  | async function listModels(req: Request, res: Response) { | ||||||
|  |     try { | ||||||
|  |         const { baseUrl } = req.body; | ||||||
|  |  | ||||||
|  |         // Use provided base URL or default from options | ||||||
|  |         const anthropicBaseUrl = baseUrl || await options.getOption('anthropicBaseUrl') || 'https://api.anthropic.com/v1'; | ||||||
|  |         const apiKey = await options.getOption('anthropicApiKey'); | ||||||
|  |  | ||||||
|  |         if (!apiKey) { | ||||||
|  |             throw new Error('Anthropic API key is not configured'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Call Anthropic API to get models | ||||||
|  |         const response = await axios.get(`${anthropicBaseUrl}/models`, { | ||||||
|  |             headers: { | ||||||
|  |                 'Content-Type': 'application/json', | ||||||
|  |                 'x-api-key': apiKey, | ||||||
|  |                 'anthropic-version': '2023-06-01' | ||||||
|  |             }, | ||||||
|  |             timeout: 10000 | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Process the models | ||||||
|  |         const allModels = response.data.models || []; | ||||||
|  |  | ||||||
|  |         // Separate models into chat models and embedding models | ||||||
|  |         const chatModels = allModels | ||||||
|  |             .filter((model: any) => | ||||||
|  |                 // Claude models are for chat | ||||||
|  |                 model.id.includes('claude') | ||||||
|  |             ) | ||||||
|  |             .map((model: any) => ({ | ||||||
|  |                 id: model.id, | ||||||
|  |                 name: model.id, | ||||||
|  |                 type: 'chat' | ||||||
|  |             })); | ||||||
|  |  | ||||||
|  |         // Note: Anthropic might not have embedding models yet, but we'll include this for future compatibility | ||||||
|  |         const embeddingModels = allModels | ||||||
|  |             .filter((model: any) => | ||||||
|  |                 // If Anthropic releases embedding models, they'd likely include 'embed' in the name | ||||||
|  |                 model.id.includes('embed') | ||||||
|  |             ) | ||||||
|  |             .map((model: any) => ({ | ||||||
|  |                 id: model.id, | ||||||
|  |                 name: model.id, | ||||||
|  |                 type: 'embedding' | ||||||
|  |             })); | ||||||
|  |  | ||||||
|  |         // Return the models list | ||||||
|  |         return { | ||||||
|  |             success: true, | ||||||
|  |             chatModels, | ||||||
|  |             embeddingModels | ||||||
|  |         }; | ||||||
|  |     } catch (error: any) { | ||||||
|  |         log.error(`Error listing Anthropic models: ${error.message || 'Unknown error'}`); | ||||||
|  |  | ||||||
|  |         // Properly throw the error to be handled by the global error handler | ||||||
|  |         throw new Error(`Failed to list Anthropic models: ${error.message || 'Unknown error'}`); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |     listModels | ||||||
|  | }; | ||||||
| @@ -87,6 +87,7 @@ const ALLOWED_OPTIONS = new Set([ | |||||||
|     "openaiBaseUrl", |     "openaiBaseUrl", | ||||||
|     "anthropicApiKey", |     "anthropicApiKey", | ||||||
|     "anthropicDefaultModel", |     "anthropicDefaultModel", | ||||||
|  |     "anthropicEmbeddingModel", | ||||||
|     "anthropicBaseUrl", |     "anthropicBaseUrl", | ||||||
|     "ollamaEnabled", |     "ollamaEnabled", | ||||||
|     "ollamaBaseUrl", |     "ollamaBaseUrl", | ||||||
|   | |||||||
| @@ -63,6 +63,7 @@ import shareRoutes from "../share/routes.js"; | |||||||
| import embeddingsRoute from "./api/embeddings.js"; | import embeddingsRoute from "./api/embeddings.js"; | ||||||
| import ollamaRoute from "./api/ollama.js"; | import ollamaRoute from "./api/ollama.js"; | ||||||
| import openaiRoute from "./api/openai.js"; | import openaiRoute from "./api/openai.js"; | ||||||
|  | import anthropicRoute from "./api/anthropic.js"; | ||||||
| import llmRoute from "./api/llm.js"; | import llmRoute from "./api/llm.js"; | ||||||
|  |  | ||||||
| import etapiAuthRoutes from "../etapi/auth.js"; | import etapiAuthRoutes from "../etapi/auth.js"; | ||||||
| @@ -412,6 +413,9 @@ function register(app: express.Application) { | |||||||
|     // OpenAI API endpoints |     // OpenAI API endpoints | ||||||
|     route(PST, "/api/openai/list-models", [auth.checkApiAuth, csrfMiddleware], openaiRoute.listModels, apiResultHandler); |     route(PST, "/api/openai/list-models", [auth.checkApiAuth, csrfMiddleware], openaiRoute.listModels, apiResultHandler); | ||||||
|  |  | ||||||
|  |     // Anthropic API endpoints | ||||||
|  |     route(PST, "/api/anthropic/list-models", [auth.checkApiAuth, csrfMiddleware], anthropicRoute.listModels, apiResultHandler); | ||||||
|  |  | ||||||
|     // API Documentation |     // API Documentation | ||||||
|     apiDocsRoute.register(app); |     apiDocsRoute.register(app); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -176,6 +176,7 @@ const defaultOptions: DefaultOption[] = [ | |||||||
|     { name: "openaiBaseUrl", value: "https://api.openai.com/v1", isSynced: true }, |     { name: "openaiBaseUrl", value: "https://api.openai.com/v1", isSynced: true }, | ||||||
|     { name: "anthropicApiKey", value: "", isSynced: false }, |     { name: "anthropicApiKey", value: "", isSynced: false }, | ||||||
|     { name: "anthropicDefaultModel", value: "claude-3-opus-20240229", isSynced: true }, |     { name: "anthropicDefaultModel", value: "claude-3-opus-20240229", isSynced: true }, | ||||||
|  |     { name: "anthropicEmbeddingModel", value: "", isSynced: true }, | ||||||
|     { name: "anthropicBaseUrl", value: "https://api.anthropic.com/v1", isSynced: true }, |     { name: "anthropicBaseUrl", value: "https://api.anthropic.com/v1", isSynced: true }, | ||||||
|     { name: "ollamaEnabled", value: "false", isSynced: true }, |     { name: "ollamaEnabled", value: "false", isSynced: true }, | ||||||
|     { name: "ollamaDefaultModel", value: "llama3", isSynced: true }, |     { name: "ollamaDefaultModel", value: "llama3", isSynced: true }, | ||||||
|   | |||||||
| @@ -57,6 +57,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi | |||||||
|     openaiBaseUrl: string; |     openaiBaseUrl: string; | ||||||
|     anthropicApiKey: string; |     anthropicApiKey: string; | ||||||
|     anthropicDefaultModel: string; |     anthropicDefaultModel: string; | ||||||
|  |     anthropicEmbeddingModel: string; | ||||||
|     anthropicBaseUrl: string; |     anthropicBaseUrl: string; | ||||||
|     ollamaEnabled: boolean; |     ollamaEnabled: boolean; | ||||||
|     ollamaBaseUrl: string; |     ollamaBaseUrl: string; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user