- Add Cloudflare Workers AI as third provider alongside Claude and Ollama - New cloudflare.js API handler with format conversion - Tool converter functions for Cloudflare's OpenAI-compatible format - Handle [TOOL_CALLS] and [Called tool:] text formats from Mistral - Robust parser that handles truncated JSON responses - Add usage tracking with cost display - New usageTracker.js service for tracking token usage per session - UsageDetailModal component showing per-request breakdown - Cost display in ChatPanel header - Add new diagram manipulation features - Entity scale and rotation support via modify_entity tool - Wikipedia search tool for researching topics before diagramming - Clear conversation tool to reset chat history - JSON import from hamburger menu (moved from ChatPanel) - Fix connection label rotation in billboard mode - Labels no longer have conflicting local rotation when billboard enabled - Update rotation when rendering mode changes - Improve tool calling reliability - Add MAX_TOOL_ITERATIONS safety limit - Break loop after model switch to prevent context issues - Increase max_tokens to 4096 to prevent truncation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
141 lines
3.7 KiB
JavaScript
141 lines
3.7 KiB
JavaScript
/**
|
|
* AI Provider Configuration
|
|
* Manages configuration for different AI providers (Claude, Ollama, Cloudflare)
|
|
*/
|
|
|
|
// Default configuration
|
|
const DEFAULT_PROVIDER = 'claude';
|
|
// Use 127.0.0.1 instead of localhost to avoid IPv6 resolution issues
|
|
const DEFAULT_OLLAMA_URL = 'http://127.0.0.1:11434';
|
|
const DEFAULT_CLOUDFLARE_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID || '';
|
|
|
|
/**
|
|
* Get the current AI provider
|
|
* @returns {string} Provider name ('claude' or 'ollama')
|
|
*/
|
|
export function getProvider() {
|
|
return process.env.AI_PROVIDER || DEFAULT_PROVIDER;
|
|
}
|
|
|
|
/**
|
|
* Get Ollama API URL
|
|
* @returns {string} Ollama base URL
|
|
*/
|
|
export function getOllamaUrl() {
|
|
return process.env.OLLAMA_URL || DEFAULT_OLLAMA_URL;
|
|
}
|
|
|
|
/**
|
|
* Get Anthropic API URL
|
|
* @returns {string} Anthropic base URL
|
|
*/
|
|
export function getAnthropicUrl() {
|
|
return process.env.ANTHROPIC_API_URL || 'https://api.anthropic.com';
|
|
}
|
|
|
|
/**
|
|
* Get Cloudflare Account ID
|
|
* @returns {string} Cloudflare account ID
|
|
*/
|
|
export function getCloudflareAccountId() {
|
|
return process.env.CLOUDFLARE_ACCOUNT_ID || DEFAULT_CLOUDFLARE_ACCOUNT_ID;
|
|
}
|
|
|
|
/**
|
|
* Get Cloudflare API Token
|
|
* @returns {string} Cloudflare API token
|
|
*/
|
|
export function getCloudflareApiToken() {
|
|
return process.env.CLOUDFLARE_API_TOKEN || '';
|
|
}
|
|
|
|
/**
|
|
* Get Cloudflare Workers AI base URL
|
|
* @returns {string} Cloudflare Workers AI base URL
|
|
*/
|
|
export function getCloudflareUrl() {
|
|
const accountId = getCloudflareAccountId();
|
|
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run`;
|
|
}
|
|
|
|
/**
|
|
* Get provider configuration for a specific provider
|
|
* @param {string} provider - Provider name
|
|
* @returns {object} Provider configuration
|
|
*/
|
|
export function getProviderConfig(provider) {
|
|
switch (provider) {
|
|
case 'ollama':
|
|
return {
|
|
name: 'ollama',
|
|
baseUrl: getOllamaUrl(),
|
|
chatEndpoint: '/api/chat',
|
|
requiresAuth: false
|
|
};
|
|
case 'cloudflare':
|
|
return {
|
|
name: 'cloudflare',
|
|
baseUrl: getCloudflareUrl(),
|
|
chatEndpoint: '', // Model is appended to baseUrl
|
|
requiresAuth: true,
|
|
apiKey: getCloudflareApiToken(),
|
|
accountId: getCloudflareAccountId()
|
|
};
|
|
case 'claude':
|
|
default:
|
|
return {
|
|
name: 'claude',
|
|
baseUrl: getAnthropicUrl(),
|
|
chatEndpoint: '/v1/messages',
|
|
requiresAuth: true,
|
|
apiKey: process.env.ANTHROPIC_API_KEY
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine provider from model ID
|
|
* @param {string} modelId - Model identifier
|
|
* @returns {string} Provider name
|
|
*/
|
|
export function getProviderFromModel(modelId) {
|
|
if (!modelId) return getProvider();
|
|
|
|
// Claude models start with 'claude-'
|
|
if (modelId.startsWith('claude-')) {
|
|
return 'claude';
|
|
}
|
|
|
|
// Cloudflare models start with '@cf/' or '@hf/'
|
|
if (modelId.startsWith('@cf/') || modelId.startsWith('@hf/')) {
|
|
return 'cloudflare';
|
|
}
|
|
|
|
// Known Ollama models
|
|
const ollamaModels = [
|
|
'llama', 'mistral', 'qwen', 'codellama', 'phi',
|
|
'gemma', 'neural-chat', 'starling', 'orca', 'vicuna',
|
|
'deepseek', 'dolphin', 'nous-hermes', 'openhermes'
|
|
];
|
|
|
|
for (const prefix of ollamaModels) {
|
|
if (modelId.toLowerCase().startsWith(prefix)) {
|
|
return 'ollama';
|
|
}
|
|
}
|
|
|
|
// Default to configured provider
|
|
return getProvider();
|
|
}
|
|
|
|
export default {
|
|
getProvider,
|
|
getOllamaUrl,
|
|
getAnthropicUrl,
|
|
getCloudflareAccountId,
|
|
getCloudflareApiToken,
|
|
getCloudflareUrl,
|
|
getProviderConfig,
|
|
getProviderFromModel
|
|
};
|