add anthropic options as well

This commit is contained in:
perf3ct 2025-03-17 20:17:28 +00:00
parent 4a4eac6f25
commit c40c702761
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
7 changed files with 154 additions and 0 deletions

View File

@ -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 () => {

View File

@ -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",

View 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
};

View File

@ -87,6 +87,7 @@ const ALLOWED_OPTIONS = new Set([
"openaiBaseUrl",
"anthropicApiKey",
"anthropicDefaultModel",
"anthropicEmbeddingModel",
"anthropicBaseUrl",
"ollamaEnabled",
"ollamaBaseUrl",

View File

@ -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);

View File

@ -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 },

View File

@ -57,6 +57,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
openaiBaseUrl: string;
anthropicApiKey: string;
anthropicDefaultModel: string;
anthropicEmbeddingModel: string;
anthropicBaseUrl: string;
ollamaEnabled: boolean;
ollamaBaseUrl: string;