diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb index 86d1b70ef..35b068649 100644 --- a/app/controllers/super_admin/app_configs_controller.rb +++ b/app/controllers/super_admin/app_configs_controller.rb @@ -50,7 +50,7 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController 'whatsapp_embedded' => %w[WHATSAPP_APP_ID WHATSAPP_APP_SECRET WHATSAPP_CONFIGURATION_ID WHATSAPP_API_VERSION], 'notion' => %w[NOTION_CLIENT_ID NOTION_CLIENT_SECRET], 'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI ENABLE_GOOGLE_OAUTH_LOGIN], - 'captain' => %w[CAPTAIN_OPEN_AI_API_KEY CAPTAIN_OPEN_AI_MODEL CAPTAIN_OPEN_AI_ENDPOINT] + 'captain' => captain_config_options } @allowed_configs = mapping.fetch( @@ -73,6 +73,18 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController def restart_required_config_saved? params.fetch('app_config', {}).keys.intersect?(InstallationConfig::RESTART_REQUIRED_CONFIG_KEYS) end + + def captain_config_options + %w[ + CAPTAIN_OPEN_AI_API_KEY + CAPTAIN_OPEN_AI_MODEL + CAPTAIN_OPEN_AI_ENDPOINT + CAPTAIN_ANTHROPIC_API_KEY + CAPTAIN_ANTHROPIC_API_BASE + CAPTAIN_GEMINI_API_KEY + CAPTAIN_GEMINI_API_BASE + ] + end end SuperAdmin::AppConfigsController.prepend_mod_with('SuperAdmin::AppConfigsController') diff --git a/app/models/installation_config.rb b/app/models/installation_config.rb index ef349799f..4444431f7 100644 --- a/app/models/installation_config.rb +++ b/app/models/installation_config.rb @@ -19,6 +19,10 @@ class InstallationConfig < ApplicationRecord CAPTAIN_OPEN_AI_API_KEY CAPTAIN_OPEN_AI_ENDPOINT CAPTAIN_OPEN_AI_MODEL + CAPTAIN_ANTHROPIC_API_KEY + CAPTAIN_ANTHROPIC_API_BASE + CAPTAIN_GEMINI_API_KEY + CAPTAIN_GEMINI_API_BASE ].freeze RESTART_REQUIRED_CONFIG_KEYS = (CAPTAIN_LLM_CONFIG_KEYS + %w[ diff --git a/config/locales/en.yml b/config/locales/en.yml index 22d3630af..5f6cce91a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -577,6 +577,7 @@ en: captain_model_overrides: form: helper_text: 'Leave a model blank to use the YAML default for that AI feature.' + default_group: 'Default routing' use_default: 'Use default: %{model} (%{model_id})' show: summary: 'View model routing' @@ -591,6 +592,7 @@ en: copilot: 'Copilot' label_suggestion: 'Label suggestion' document_faq_generation: 'Document FAQ generation' + pdf_faq_generation: 'PDF FAQ generation' help_center_article_generation: 'Help center article generation' onboarding_content_generation: 'Onboarding content generation' help_center_query_translation: 'Help center query translation' diff --git a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb index 0a9807eb0..a71841204 100644 --- a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb +++ b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb @@ -45,6 +45,10 @@ module Enterprise::SuperAdmin::AppConfigsController CAPTAIN_OPEN_AI_API_KEY CAPTAIN_OPEN_AI_MODEL CAPTAIN_OPEN_AI_ENDPOINT + CAPTAIN_ANTHROPIC_API_KEY + CAPTAIN_ANTHROPIC_API_BASE + CAPTAIN_GEMINI_API_KEY + CAPTAIN_GEMINI_API_BASE CAPTAIN_EMBEDDING_MODEL CAPTAIN_FIRECRAWL_API_KEY ] diff --git a/enterprise/app/fields/captain_model_overrides_field.rb b/enterprise/app/fields/captain_model_overrides_field.rb index a8f3fe399..6f0f1b700 100644 --- a/enterprise/app/fields/captain_model_overrides_field.rb +++ b/enterprise/app/fields/captain_model_overrides_field.rb @@ -33,8 +33,14 @@ class CaptainModelOverridesField < Administrate::Field::Base end def model_options(feature_key) - Llm::Models.feature_config(feature_key)[:models].map do |model| - [model[:display_name] || model[:id], model[:id]] + models_by_provider = Llm::Models.feature_config(feature_key)[:models].group_by { |model| model[:provider] } + + grouped_models = models_by_provider.transform_keys do |provider| + provider_label(provider) + end + + grouped_models.transform_values do |models| + models.map { |model| [model[:display_name] || model[:id], model[:id]] } end end diff --git a/enterprise/app/views/fields/captain_model_overrides_field/_form.html.erb b/enterprise/app/views/fields/captain_model_overrides_field/_form.html.erb index 0420ab09a..9355d72c8 100644 --- a/enterprise/app/views/fields/captain_model_overrides_field/_form.html.erb +++ b/enterprise/app/views/fields/captain_model_overrides_field/_form.html.erb @@ -15,8 +15,10 @@ <%= select_tag( "account[captain_models][#{feature[:key]}]", - options_for_select( - [[t('super_admin.captain_model_overrides.form.use_default', model: feature[:default_model], model_id: feature[:default_model_id]), '']] + feature[:options], + grouped_options_for_select( + { t('super_admin.captain_model_overrides.form.default_group') => [ + [t('super_admin.captain_model_overrides.form.use_default', model: feature[:default_model], model_id: feature[:default_model_id]), ''] + ] }.merge(feature[:options]), feature[:selected_override] ), class: 'block w-full rounded-md border-slate-300 text-sm' diff --git a/spec/controllers/super_admin/accounts_controller_spec.rb b/spec/controllers/super_admin/accounts_controller_spec.rb index b2f4ff405..21f51ecd3 100644 --- a/spec/controllers/super_admin/accounts_controller_spec.rb +++ b/spec/controllers/super_admin/accounts_controller_spec.rb @@ -64,6 +64,7 @@ RSpec.describe 'Super Admin accounts API', type: :request do default_model = Llm::Models.model_config(default_model_id)['display_name'] expect(editor_select.at_css('option[value=""]').text.squish).to eq("Use default: #{default_model} (#{default_model_id})") + expect(editor_select.css('optgroup').map { |group| group['label'] }).to include('Default routing', 'OpenAI') end end end diff --git a/spec/controllers/super_admin/app_config_controller_spec.rb b/spec/controllers/super_admin/app_config_controller_spec.rb index c57955dfc..4140cb6ae 100644 --- a/spec/controllers/super_admin/app_config_controller_spec.rb +++ b/spec/controllers/super_admin/app_config_controller_spec.rb @@ -56,6 +56,17 @@ RSpec.describe 'Super Admin Application Config API', type: :request do expect(flash[:alert]).to be_blank expect(flash[:notice]).to be_blank end + + it 'allows Captain provider credentials to be configured' do + sign_in(super_admin, scope: :super_admin) + + post '/super_admin/app_config?config=captain', + params: { app_config: { CAPTAIN_ANTHROPIC_API_KEY: 'anthropic-key', CAPTAIN_GEMINI_API_KEY: 'gemini-key' } } + + expect(response).to have_http_status(:found) + expect(GlobalConfig.get('CAPTAIN_ANTHROPIC_API_KEY')['CAPTAIN_ANTHROPIC_API_KEY']).to eq('anthropic-key') + expect(GlobalConfig.get('CAPTAIN_GEMINI_API_KEY')['CAPTAIN_GEMINI_API_KEY']).to eq('gemini-key') + end end end end