diff --git a/src/public/app/widgets/type_widgets/options/ai_settings.ts b/src/public/app/widgets/type_widgets/options/ai_settings.ts
index 3dde7e084..d5fd05a36 100644
--- a/src/public/app/widgets/type_widgets/options/ai_settings.ts
+++ b/src/public/app/widgets/type_widgets/options/ai_settings.ts
@@ -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 {
${t("ai_llm.anthropic_model_description")}
+
@@ -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(``);
+
+ try {
+ const anthropicBaseUrl = this.$widget.find('.anthropic-base-url').val() as string;
+ const response = await server.post('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(``);
+ });
+
+ // 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(``);
+ }
+ });
+
// Embedding options event handlers
const $embeddingAutoUpdateEnabled = this.$widget.find('.embedding-auto-update-enabled');
$embeddingAutoUpdateEnabled.on('change', async () => {
diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json
index 6e09192d1..1e0ffa99d 100644
--- a/src/public/translations/en/translation.json
+++ b/src/public/translations/en/translation.json
@@ -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",
diff --git a/src/routes/api/anthropic.ts b/src/routes/api/anthropic.ts
new file mode 100644
index 000000000..67caa575e
--- /dev/null
+++ b/src/routes/api/anthropic.ts
@@ -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
+};
diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts
index c5e0874bc..2430eff7e 100644
--- a/src/routes/api/options.ts
+++ b/src/routes/api/options.ts
@@ -87,6 +87,7 @@ const ALLOWED_OPTIONS = new Set([
"openaiBaseUrl",
"anthropicApiKey",
"anthropicDefaultModel",
+ "anthropicEmbeddingModel",
"anthropicBaseUrl",
"ollamaEnabled",
"ollamaBaseUrl",
diff --git a/src/routes/routes.ts b/src/routes/routes.ts
index 7513c21b9..fd6428770 100644
--- a/src/routes/routes.ts
+++ b/src/routes/routes.ts
@@ -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);
diff --git a/src/services/options_init.ts b/src/services/options_init.ts
index ebc4b5f36..b70ad8780 100644
--- a/src/services/options_init.ts
+++ b/src/services/options_init.ts
@@ -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 },
diff --git a/src/services/options_interface.ts b/src/services/options_interface.ts
index e540de880..f850b7c86 100644
--- a/src/services/options_interface.ts
+++ b/src/services/options_interface.ts
@@ -57,6 +57,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions