mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 11:02:28 +08:00
add anthropic options as well
This commit is contained in:
parent
4a4eac6f25
commit
c40c702761
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user