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
5 changes: 4 additions & 1 deletion app/components/llm_connections/form_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,14 @@ def wrapper_options
}
end

# The save can turn the connection on, which adds the tabs to the page
# header outside this frame, so the response replaces the whole page.
def form_options
{
model: connection,
url: llm_connection_path,
method: :patch
method: :patch,
data: { turbo_frame: "_top" }
}
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= component_wrapper do %>
<%= render(LlmConnections::ModelsTableComponent.new(rows:, connection:)) %>
<% end %>
49 changes: 49 additions & 0 deletions app/components/llm_connections/models/index_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 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
module Models
# A turbo-frame around the model table, so filtering can replace just the
# table rather than reloading the settings page.
class IndexComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

alias_method :rows, :model

def initialize(rows, connection:)
super(rows)
@connection = connection
end

attr_reader :connection
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<%= render(Primer::OpenProject::SubHeader.new(collapsed_search: false, data: sub_header_data_attributes)) do |subheader| %>
<% subheader.with_filter_input(
name: "name",
label: t("admin.llm_models.index.filter_label"),
visually_hide_label: true,
value: filter_input_value,
placeholder: t("admin.llm_models.index.filter_label"),
leading_visual: { icon: :search, size: :small },
show_clear_button: true,
clear_button_id:,
data: filter_input_data_attributes
) %>

<% subheader.with_action_button(
scheme: :default,
label: t("admin.llm_models.index.refresh"),
mobile_label: t("admin.llm_models.index.refresh"),
mobile_icon: :sync,
leading_icon: :sync,
tag: :a,
href: helpers.refresh_llm_models_path,
data: { turbo_method: :post, controller: "disable-when-clicked" },
test_selector: "llm-model--refresh-button"
) { t("admin.llm_models.index.refresh") } %>
<% end %>
65 changes: 65 additions & 0 deletions app/components/llm_connections/models/sub_header_component.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
module Models
# The filter bar above the model list.
class SubHeaderComponent < ApplicationComponent
include OpPrimer::ComponentHelpers

alias_method :query, :model

def filter_input_value
query.find_active_filter(:name)&.values&.first
end

def clear_button_id = "llm-models-filter-clear"

def sub_header_data_attributes
{
controller: "filter--filters-form",
"filter--filters-form-perform-turbo-requests-value": true,
"filter--filters-form-output-format-value": "json",
"filter--filters-form-url-path-name-value": helpers.search_llm_models_path,
"filter--filters-form-clear-button-id-value": clear_button_id
}
end

def filter_input_data_attributes
{
"filter-name": "name",
"filter-type": "string",
"filter-operator": "~",
"filter--filters-form-target": "simpleFilter filterValueContainer simpleValue"
}
end
end
end
end
85 changes: 85 additions & 0 deletions app/components/llm_connections/models_row_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 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
# A row of the model list. Note +model+ is the row's record (an LlmModel),
# not the connection -- aliased to avoid confusion with either.
class ModelsRowComponent < OpPrimer::BorderBoxRowComponent
alias_method :llm_model, :model

def column_css_class(column)
return "#{super} -no-ellipsis" if column == :source

super
end

def identifier
render(Primer::Beta::Truncate.new(font_weight: :bold)) do |truncate|
truncate.with_item(expandable: true, max_width: 320, title: llm_model.name) { llm_model.name }
end
end

# vLLM and SGLang report the operator's real --max-model-len here, which is
# more trustworthy than any published figure for the model. Servers that do
# not report it simply show nothing rather than a guess.
def context_window
window = llm_model.context_window
return render(Primer::Beta::Text.new(color: :muted)) { "—" } if window.blank?

number_with_delimiter(window)
end

# Derived from the embeddings verdict rather than stored separately: a model
# that produces vectors is an embedding model, and everything else is a chat
# model.
def kind
if table.embeddings_states[llm_model.external_id] == "supported"
render(Primer::Beta::Label.new(scheme: :success)) { I18n.t("llm.model_kinds.embedding") }
else
render(Primer::Beta::Label.new(scheme: :secondary)) { I18n.t("llm.model_kinds.chat") }
end
end

def source
scheme, key = source_label

render(Primer::Beta::Label.new(scheme:, title: I18n.t("admin.llm_models.index.#{key}_description"))) do
I18n.t("admin.llm_models.index.#{key}")
end
end

def source_label
return %i[accent source_manual] if llm_model.manual?
return %i[attention source_withdrawn] if llm_model.withdrawn?

%i[secondary source_discovered]
end
end
end
86 changes: 86 additions & 0 deletions app/components/llm_connections/models_table_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 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
# Lists the models the remote server reported, read from the cached catalogue.
#
# Rendering never issues an HTTP request: the catalogue is refreshed explicitly
# through the "Refresh models" action.
class ModelsTableComponent < OpPrimer::BorderBoxTableComponent
columns :identifier, :kind, :context_window, :source
Comment thread
tangopium marked this conversation as resolved.

main_column :identifier

mobile_columns :identifier

# The connection is passed in rather than derived from the first row: with a
# paginated, filtered list a page can legitimately be empty.
def initialize(connection:, **)
super(**)
@connection = connection
end

attr_reader :connection

def initial_sort = %i[identifier asc]

def has_footer? = false

def mobile_title = I18n.t("admin.llm_connections.tabs.models")

# The row class is otherwise derived by convention as LlmConnections::RowComponent.
def row_class = ModelsRowComponent

def headers
[
[:identifier, { caption: I18n.t("admin.llm_models.index.identifier") }],
[:kind, { caption: I18n.t("admin.llm_models.index.kind") }],
[:context_window, { caption: I18n.t("admin.llm_models.index.context_window") }],
[:source, { caption: I18n.t("admin.llm_models.index.source") }]
]
end

# Built once for the whole table so each row does not query for its own
# verdict. Rows reach this through their +table+ accessor.
def embeddings_states
@embeddings_states ||= load_embeddings_states
end

def load_embeddings_states
connection.capability_verdicts.for_capability(:embeddings).pluck(:model_id, :state).to_h
end

def blank_title = I18n.t("admin.llm_models.index.blank_title")

def blank_description = I18n.t("admin.llm_models.index.blank_description")

def blank_icon = :sparkle
end
end
5 changes: 1 addition & 4 deletions app/controllers/admin/llm_connections_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,7 @@ def redirect_after_save
def render_form_with_errors
update_via_turbo_stream(component: ::LlmConnections::FormComponent.new(@connection))
respond_with_turbo_streams do |format|
format.html do
show
render :show
end
format.html { render :show }
end
end

Expand Down
Loading
Loading