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 { | ||||
|     private statsRefreshInterval: 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> | ||||
|                                 </select> | ||||
|                                 <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> | ||||
| @@ -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 | ||||
|         const $embeddingAutoUpdateEnabled = this.$widget.find('.embedding-auto-update-enabled'); | ||||
|         $embeddingAutoUpdateEnabled.on('change', async () => { | ||||
|   | ||||
| @@ -1149,6 +1149,7 @@ | ||||
|     "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_embedding_model_description": "Anthropic embedding model (not available yet)", | ||||
|     "anthropic_url_description": "Default: https://api.anthropic.com/v1", | ||||
|     "ollama_configuration": "Ollama Configuration", | ||||
|     "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", | ||||
|     "anthropicApiKey", | ||||
|     "anthropicDefaultModel", | ||||
|     "anthropicEmbeddingModel", | ||||
|     "anthropicBaseUrl", | ||||
|     "ollamaEnabled", | ||||
|     "ollamaBaseUrl", | ||||
|   | ||||
| @@ -63,6 +63,7 @@ import shareRoutes from "../share/routes.js"; | ||||
| import embeddingsRoute from "./api/embeddings.js"; | ||||
| import ollamaRoute from "./api/ollama.js"; | ||||
| import openaiRoute from "./api/openai.js"; | ||||
| import anthropicRoute from "./api/anthropic.js"; | ||||
| import llmRoute from "./api/llm.js"; | ||||
|  | ||||
| import etapiAuthRoutes from "../etapi/auth.js"; | ||||
| @@ -412,6 +413,9 @@ function register(app: express.Application) { | ||||
|     // OpenAI API endpoints | ||||
|     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 | ||||
|     apiDocsRoute.register(app); | ||||
|  | ||||
|   | ||||
| @@ -176,6 +176,7 @@ const defaultOptions: DefaultOption[] = [ | ||||
|     { name: "openaiBaseUrl", value: "https://api.openai.com/v1", isSynced: true }, | ||||
|     { name: "anthropicApiKey", value: "", isSynced: false }, | ||||
|     { 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: "ollamaEnabled", value: "false", isSynced: true }, | ||||
|     { name: "ollamaDefaultModel", value: "llama3", isSynced: true }, | ||||
|   | ||||
| @@ -57,6 +57,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi | ||||
|     openaiBaseUrl: string; | ||||
|     anthropicApiKey: string; | ||||
|     anthropicDefaultModel: string; | ||||
|     anthropicEmbeddingModel: string; | ||||
|     anthropicBaseUrl: string; | ||||
|     ollamaEnabled: boolean; | ||||
|     ollamaBaseUrl: string; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user