Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/components/llm_connections/form_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<%=
component_wrapper(tag: "turbo-frame", **wrapper_options) do
concat(
settings_primer_form_with(**form_options) do |f|
render(LlmConnections::ConnectionForm.new(f))
end
)

concat(
render(
Primer::Beta::Text.new(
tag: :div,
mt: 3,
hidden: true,
display: :flex,
align_items: :center,
color: :muted,
data: { "admin--llm-connection-form-target": "progressBanner" }
)
) do
concat(render(Primer::Beta::Spinner.new(size: :small, mr: 2)))
concat(t("admin.llm_connections.form.label_connecting"))
end
)
end
%>
60 changes: 60 additions & 0 deletions app/components/llm_connections/form_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module LlmConnections
class FormComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable

def self.wrapper_key = :llm_connection_form

alias_method :connection, :model

private

def wrapper_options
{
data: {
controller: "admin--llm-connection-form",
test_selector: "llm-connection--form"
}
}
end

def form_options
{
model: connection,
url: llm_connection_path,
method: :patch
}
end
end
end
65 changes: 65 additions & 0 deletions app/contracts/llm_connections/base_contract.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module LlmConnections
# Validations that hold for every write, including provisioning from the
# environment. Deliberately makes no network request -- see UpdateContract.
class BaseContract < ModelContract
attribute :enabled
attribute :api_format
attribute :base_url
attribute :api_key
attribute :default_chat_model_id
attribute :default_embedding_model_id

validates :base_url, presence: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this next to the other one :)

validates :api_format, inclusion: { in: Llm::Adapters::FORMATS }
# Bedrock and Vertex AI can be discovered against, but not called: they need

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated comment, let's keep only the one at the source.

# a secret key and region, or a project and location, and there is one
# api_key column. Offering them would only fail at request time.
validates :api_format, exclusion: { in: Llm::Session::UNSUPPORTED_FORMATS, message: :not_supported },
unless: -> { api_format.blank? }
# Resolves to the validate_url gem, which defaults to http and https. Plain
# http is deliberately allowed: an on-premise LLM server on an internal
# network commonly terminates TLS elsewhere, or not at all.
validates :base_url, url: { message: :invalid_url }, unless: -> { base_url.blank? }

validate :enabled_requires_connection

private

def enabled_requires_connection
return unless model.enabled?
return if model.base_url.present?

errors.add :enabled, :requires_connection
end
end
end
45 changes: 45 additions & 0 deletions app/contracts/llm_connections/update_contract.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module LlmConnections
# The contract used when an administrator saves the form.
#
# This is the only contract that talks to the remote server, so that a failed
# probe persists nothing. It must NOT move to BaseContract: provisioning from
# the environment reuses that base, and a validates line cannot be un-declared
# by a subclass -- an outbound request during seeding would fail the boot of a
# container whose LLM server has not started yet.
#
# The probe itself is guarded on changed attributes, so toggling +enabled+ or
# picking a default model contacts nothing.
class UpdateContract < BaseContract
validates :base_url, llm_server: true, unless: -> { errors.include?(:base_url) }
end
end
102 changes: 102 additions & 0 deletions app/controllers/admin/llm_connections_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module Admin
class LlmConnectionsController < ApplicationController
include OpTurbo::ComponentStream
include PaginationHelper

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 PaginationHelper is not used anywhere in this PR — show is empty and nothing paginates until the model table arrives in #24883. Worth moving to the PR that needs it.


layout "admin"
menu_item :llm_connection

before_action :require_feature
before_action :require_admin
before_action :set_connection

def show; end

def update
result = ::LlmConnections::UpdateService
.new(user: current_user, model: @connection)
.call(**llm_connection_params)

result.on_success { redirect_after_save }
result.on_failure { render_form_with_errors }
end

private

def set_connection
@connection = LlmConnection.instance
end

# The flag gates the endpoints, not only the menu entry: an unfinished page
# must not accept writes just because somebody knows the URL.
def require_feature
render_404 unless OpenProject::FeatureDecisions.llm_connection_active?
end

def redirect_after_save
redirect_with_notice(t(".success"))
end

def render_form_with_errors
update_via_turbo_stream(component: ::LlmConnections::FormComponent.new(@connection))
# The HTML fallback re-renders the whole page, which needs everything the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 This comment describes a show that assigns several things, but in this PR show is def show; end. The comment becomes accurate in #24883. Either trim it here or move it with the code it describes.

# show action assigns -- not just the form that failed.
respond_with_turbo_streams do |format|
format.html do
show
render :show
end
end
end

def redirect_with_notice(message)
flash[:notice] = message
redirect_to llm_connection_path, status: :see_other
end

def redirect_with_error(message)
flash[:error] = message
redirect_to llm_connection_path, status: :see_other
end

# A blank API key means "keep the stored one": the form never renders the
# saved value, so submitting it unchanged posts an empty string.
def llm_connection_params
permitted = params.expect(
llm_connection: %i[enabled api_format base_url api_key]
)
permitted.delete(:api_key) if permitted[:api_key].blank?
permitted.to_h.symbolize_keys
end
end
end
Loading
Loading