lex-llm 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/CODEOWNERS +7 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +42 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +50 -0
- data/LICENSE +21 -0
- data/README.md +279 -0
- data/lex-llm.gemspec +43 -0
- data/lib/generators/lex_llm/agent/agent_generator.rb +36 -0
- data/lib/generators/lex_llm/agent/templates/agent.rb.tt +6 -0
- data/lib/generators/lex_llm/agent/templates/instructions.txt.erb.tt +0 -0
- data/lib/generators/lex_llm/chat_ui/chat_ui_generator.rb +256 -0
- data/lib/generators/lex_llm/chat_ui/templates/controllers/chats_controller.rb.tt +38 -0
- data/lib/generators/lex_llm/chat_ui/templates/controllers/messages_controller.rb.tt +21 -0
- data/lib/generators/lex_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
- data/lib/generators/lex_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
- data/lib/generators/lex_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/index.html.erb.tt +28 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/show.html.erb.tt +25 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +7 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/models/_model.html.erb.tt +15 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/models/index.html.erb.tt +38 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/models/show.html.erb.tt +17 -0
- data/lib/generators/lex_llm/generator_helpers.rb +214 -0
- data/lib/generators/lex_llm/install/install_generator.rb +109 -0
- data/lib/generators/lex_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
- data/lib/generators/lex_llm/install/templates/chat_model.rb.tt +3 -0
- data/lib/generators/lex_llm/install/templates/create_chats_migration.rb.tt +7 -0
- data/lib/generators/lex_llm/install/templates/create_messages_migration.rb.tt +19 -0
- data/lib/generators/lex_llm/install/templates/create_models_migration.rb.tt +39 -0
- data/lib/generators/lex_llm/install/templates/create_tool_calls_migration.rb.tt +21 -0
- data/lib/generators/lex_llm/install/templates/initializer.rb.tt +20 -0
- data/lib/generators/lex_llm/install/templates/message_model.rb.tt +4 -0
- data/lib/generators/lex_llm/install/templates/model_model.rb.tt +3 -0
- data/lib/generators/lex_llm/install/templates/tool_call_model.rb.tt +3 -0
- data/lib/generators/lex_llm/schema/schema_generator.rb +26 -0
- data/lib/generators/lex_llm/schema/templates/schema.rb.tt +2 -0
- data/lib/generators/lex_llm/tool/templates/tool.rb.tt +9 -0
- data/lib/generators/lex_llm/tool/templates/tool_call.html.erb.tt +13 -0
- data/lib/generators/lex_llm/tool/templates/tool_result.html.erb.tt +13 -0
- data/lib/generators/lex_llm/tool/tool_generator.rb +96 -0
- data/lib/generators/lex_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +19 -0
- data/lib/generators/lex_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +50 -0
- data/lib/generators/lex_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
- data/lib/generators/lex_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
- data/lib/generators/lex_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
- data/lib/generators/lex_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +122 -0
- data/lib/generators/lex_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
- data/lib/generators/lex_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
- data/lib/legion/extensions/llm/provider_settings.rb +49 -0
- data/lib/legion/extensions/llm/transport/fleet_lane.rb +70 -0
- data/lib/legion/extensions/llm.rb +50 -0
- data/lib/lex_llm/active_record/acts_as.rb +180 -0
- data/lib/lex_llm/active_record/acts_as_legacy.rb +503 -0
- data/lib/lex_llm/active_record/chat_methods.rb +468 -0
- data/lib/lex_llm/active_record/message_methods.rb +131 -0
- data/lib/lex_llm/active_record/model_methods.rb +76 -0
- data/lib/lex_llm/active_record/payload_helpers.rb +26 -0
- data/lib/lex_llm/active_record/tool_call_methods.rb +15 -0
- data/lib/lex_llm/agent.rb +365 -0
- data/lib/lex_llm/aliases.json +436 -0
- data/lib/lex_llm/aliases.rb +38 -0
- data/lib/lex_llm/attachment.rb +223 -0
- data/lib/lex_llm/chat.rb +351 -0
- data/lib/lex_llm/chunk.rb +6 -0
- data/lib/lex_llm/configuration.rb +81 -0
- data/lib/lex_llm/connection.rb +130 -0
- data/lib/lex_llm/content.rb +77 -0
- data/lib/lex_llm/context.rb +29 -0
- data/lib/lex_llm/embedding.rb +29 -0
- data/lib/lex_llm/error.rb +112 -0
- data/lib/lex_llm/image.rb +105 -0
- data/lib/lex_llm/message.rb +107 -0
- data/lib/lex_llm/mime_type.rb +71 -0
- data/lib/lex_llm/model/info.rb +113 -0
- data/lib/lex_llm/model/modalities.rb +22 -0
- data/lib/lex_llm/model/pricing.rb +48 -0
- data/lib/lex_llm/model/pricing_category.rb +46 -0
- data/lib/lex_llm/model/pricing_tier.rb +33 -0
- data/lib/lex_llm/model.rb +7 -0
- data/lib/lex_llm/models.json +57241 -0
- data/lib/lex_llm/models.rb +506 -0
- data/lib/lex_llm/models_schema.json +168 -0
- data/lib/lex_llm/moderation.rb +56 -0
- data/lib/lex_llm/provider.rb +278 -0
- data/lib/lex_llm/railtie.rb +35 -0
- data/lib/lex_llm/routing/lane_key.rb +51 -0
- data/lib/lex_llm/routing/model_offering.rb +169 -0
- data/lib/lex_llm/routing.rb +7 -0
- data/lib/lex_llm/stream_accumulator.rb +203 -0
- data/lib/lex_llm/streaming.rb +175 -0
- data/lib/lex_llm/thinking.rb +49 -0
- data/lib/lex_llm/tokens.rb +47 -0
- data/lib/lex_llm/tool.rb +254 -0
- data/lib/lex_llm/tool_call.rb +25 -0
- data/lib/lex_llm/transcription.rb +35 -0
- data/lib/lex_llm/utils.rb +91 -0
- data/lib/lex_llm/version.rb +5 -0
- data/lib/lex_llm.rb +95 -0
- data/lib/tasks/lex_llm.rake +23 -0
- metadata +349 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<%% content_for :title, "<%= model_model_name.pluralize %>" %>
|
|
2
|
+
|
|
3
|
+
<div class="w-full">
|
|
4
|
+
<%% if notice.present? %>
|
|
5
|
+
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-md inline-block" id="notice"><%%= notice %></p>
|
|
6
|
+
<%% end %>
|
|
7
|
+
|
|
8
|
+
<div class="flex justify-between items-center">
|
|
9
|
+
<h1 class="font-bold text-4xl"><%= model_model_name.pluralize %></h1>
|
|
10
|
+
<div class="flex items-center gap-2">
|
|
11
|
+
<%%= link_to "<%= chat_model_name.pluralize %>", <%= chat_table_name %>_path, class: "rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 text-gray-900 block font-medium" %>
|
|
12
|
+
<%%= button_to "Refresh <%= model_model_name.pluralize %>", refresh_<%= model_table_name %>_path, method: :post, class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white block font-medium cursor-pointer" %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div id="<%= model_table_name %>" class="mt-5 overflow-x-auto">
|
|
17
|
+
<%% if @<%= model_variable_name.pluralize %>.any? %>
|
|
18
|
+
<table class="min-w-full divide-y divide-gray-300 border border-gray-300">
|
|
19
|
+
<thead class="bg-gray-50">
|
|
20
|
+
<tr>
|
|
21
|
+
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-900">Provider</th>
|
|
22
|
+
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-900">Model</th>
|
|
23
|
+
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-900">Context Window</th>
|
|
24
|
+
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-900">$/1M tokens (In/Out)</th>
|
|
25
|
+
<th class="px-3 py-2 text-left text-sm font-semibold text-gray-900"></th>
|
|
26
|
+
</tr>
|
|
27
|
+
</thead>
|
|
28
|
+
<tbody class="divide-y divide-gray-200 bg-white">
|
|
29
|
+
<%% @<%= model_variable_name.pluralize %>.each do |model_info| %>
|
|
30
|
+
<%%= render "<%= model_model_name.underscore.pluralize %>/<%= model_model_name.demodulize.underscore %>",
|
|
31
|
+
<%= model_model_name.demodulize.underscore %>: model_info %>
|
|
32
|
+
<%% end %>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
<%% else %>
|
|
36
|
+
<p class="text-center my-10">No chat models found.</p>
|
|
37
|
+
<%% end %>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%% content_for :title, "Showing <%= model_table_name.singularize.humanize.downcase %>" %>
|
|
2
|
+
|
|
3
|
+
<div class="md:w-2/3 w-full">
|
|
4
|
+
<%% if notice.present? %>
|
|
5
|
+
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-md inline-block" id="notice"><%%= notice %></p>
|
|
6
|
+
<%% end %>
|
|
7
|
+
|
|
8
|
+
<h1 class="font-bold text-4xl">Showing <%= model_table_name.singularize.humanize.downcase %></h1>
|
|
9
|
+
|
|
10
|
+
<div class="my-5 space-y-3">
|
|
11
|
+
<p><strong class="block font-medium mb-1">Provider:</strong> <%%= @<%= model_variable_name %>.provider_class&.name || @<%= model_variable_name %>.provider %></p>
|
|
12
|
+
<p><strong class="block font-medium mb-1">Model:</strong> <%%= @<%= model_variable_name %>.name %></p>
|
|
13
|
+
<p><strong class="block font-medium mb-1">ID:</strong> <%%= @<%= model_variable_name %>.model_id %></p>
|
|
14
|
+
<p><strong class="block font-medium mb-1">Context Window:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.context_window) if @<%= model_variable_name %>.context_window %></p>
|
|
15
|
+
<p><strong class="block font-medium mb-1">Max Output Tokens:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.max_output_tokens) if @<%= model_variable_name %>.max_output_tokens %></p>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<%% if @<%= model_variable_name %>.capabilities.any? %>
|
|
19
|
+
<div class="my-5">
|
|
20
|
+
<strong class="block font-medium mb-1">Capabilities:</strong>
|
|
21
|
+
<%%= @<%= model_variable_name %>.capabilities.join(", ") %>
|
|
22
|
+
</div>
|
|
23
|
+
<%% end %>
|
|
24
|
+
|
|
25
|
+
<%%= link_to "Start <%= chat_table_name.singularize.humanize.downcase %> with this model", new_<%= chat_variable_name %>_path(model: @<%= model_variable_name %>.model_id), class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
|
|
26
|
+
<%%= link_to "All <%= model_table_name.humanize.downcase %>", <%= model_table_name %>_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div id="<%%= dom_id <%= chat_model_name.demodulize.underscore %> %>">
|
|
2
|
+
<div>
|
|
3
|
+
<strong>Model:</strong>
|
|
4
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.<%= model_table_name.singularize %>&.label || default_model_display_name %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div>
|
|
8
|
+
<strong>Messages:</strong>
|
|
9
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.<%= message_table_name %>.count %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
<strong>Created:</strong>
|
|
14
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.created_at.strftime("%B %d, %Y at %I:%M %p") %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<%%= form_with(model: <%= chat_variable_name %>, url: <%= chat_table_name %>_path) do |form| %>
|
|
2
|
+
<%% if <%= chat_variable_name %>.errors.any? %>
|
|
3
|
+
<div style="color: red">
|
|
4
|
+
<h2><%%= pluralize(<%= chat_variable_name %>.errors.count, "error") %> prohibited this <%= chat_table_name.singularize.humanize.downcase %> from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul>
|
|
7
|
+
<%% <%= chat_variable_name %>.errors.each do |error| %>
|
|
8
|
+
<li><%%= error.full_message %></li>
|
|
9
|
+
<%% end %>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|
|
12
|
+
<%% end %>
|
|
13
|
+
|
|
14
|
+
<div>
|
|
15
|
+
<%%= form.label :model, "Select AI model:", style: "display: block" %>
|
|
16
|
+
<%%= form.select :model,
|
|
17
|
+
options_for_select(@chat_models.map { |model| [model.label, model.id] }.unshift([default_model_display_name, nil]), @selected_model),
|
|
18
|
+
{},
|
|
19
|
+
style: "width: 100%; max-width: 600px; padding: 5px;" %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div style="margin-top: 15px;">
|
|
23
|
+
<%%= form.text_field :prompt, style: "width: 100%; max-width: 600px;", placeholder: "What would you like to discuss?", autofocus: true %>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div>
|
|
27
|
+
<%%= form.submit "Start new <%= chat_table_name.singularize.humanize.downcase %>" %>
|
|
28
|
+
</div>
|
|
29
|
+
<%% end %>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<%% if notice.present? %>
|
|
2
|
+
<p style="color: green"><%%= notice %></p>
|
|
3
|
+
<%% end %>
|
|
4
|
+
|
|
5
|
+
<%% content_for :title, "<%= chat_model_name.pluralize %>" %>
|
|
6
|
+
|
|
7
|
+
<h1><%= chat_model_name.pluralize %></h1>
|
|
8
|
+
<p>
|
|
9
|
+
<%%= link_to "<%= model_model_name.pluralize %>", <%= model_table_name %>_path %>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<div id="<%= chat_table_name %>">
|
|
13
|
+
<%% if @<%= chat_table_name %>.any? %>
|
|
14
|
+
<%% @<%= chat_table_name %>.each do |<%= chat_variable_name %>| %>
|
|
15
|
+
<%%= render <%= chat_variable_name %> %>
|
|
16
|
+
<p>
|
|
17
|
+
<%%= link_to "Show this <%= chat_table_name.singularize.humanize.downcase %>", <%= chat_variable_name %> %>
|
|
18
|
+
</p>
|
|
19
|
+
<p>
|
|
20
|
+
<%%= link_to "Destroy this <%= chat_table_name.singularize.humanize.downcase %>", <%= chat_variable_name %>, data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>
|
|
21
|
+
</p>
|
|
22
|
+
<%% end %>
|
|
23
|
+
<%% else %>
|
|
24
|
+
<p>No <%= chat_table_name.humanize.downcase %> found.</p>
|
|
25
|
+
<%% end %>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<%%= link_to "New <%= chat_table_name.singularize.humanize.downcase %>", new_<%= chat_variable_name %>_path %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%% content_for :title, "New <%= chat_table_name.singularize.humanize.downcase %>" %>
|
|
2
|
+
|
|
3
|
+
<h1>New <%= chat_table_name.singularize.humanize.downcase %></h1>
|
|
4
|
+
|
|
5
|
+
<%%= render "form", <%= chat_variable_name %>: @<%= chat_variable_name %> %>
|
|
6
|
+
|
|
7
|
+
<br>
|
|
8
|
+
|
|
9
|
+
<div>
|
|
10
|
+
<%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path %>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<%% if notice.present? %>
|
|
2
|
+
<p style="color: green"><%%= notice %></p>
|
|
3
|
+
<%% end %>
|
|
4
|
+
|
|
5
|
+
<%%= turbo_stream_from "<%= chat_variable_name %>_#{@<%= chat_variable_name %>.id}" %>
|
|
6
|
+
|
|
7
|
+
<%% content_for :title, "<%= chat_model_name %>" %>
|
|
8
|
+
|
|
9
|
+
<h1><%= chat_model_name %> <%%= @<%= chat_variable_name %>.id %></h1>
|
|
10
|
+
|
|
11
|
+
<p>Using <strong><%%= @<%= chat_variable_name %>.<%= model_table_name.singularize %>&.label || default_model_display_name %></strong></p>
|
|
12
|
+
|
|
13
|
+
<div id="<%= message_table_name %>">
|
|
14
|
+
<%% @<%= chat_variable_name %>.<%= message_table_name %>.where.not(id: nil).each do |<%= message_variable_name %>| %>
|
|
15
|
+
<%%= render <%= message_variable_name %> %>
|
|
16
|
+
<%% end %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div style="margin-top: 30px;">
|
|
20
|
+
<%%= render "<%= message_model_name.underscore.pluralize %>/form", <%= chat_variable_name %>: @<%= chat_variable_name %>, <%= message_variable_name %>: @<%= message_variable_name %> %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div style="margin-top: 20px;">
|
|
24
|
+
<%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path %>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%% assistant ||= local_assigns[:message] %>
|
|
2
|
+
<div id="<%= message_variable_name %>_<%%= assistant.id %>" class="<%= message_variable_name %>"
|
|
3
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #28a745;">
|
|
4
|
+
<div style="font-weight: bold; margin-bottom: 5px;">Assistant</div>
|
|
5
|
+
<div id="<%= message_variable_name %>_<%%= assistant.id %>_content" style="white-space: pre-wrap;"><%%= assistant.content %></div>
|
|
6
|
+
<div style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
|
7
|
+
<%%= assistant.created_at&.strftime("%I:%M %p") %>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%%= content -%>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<div id="<%= message_variable_name %>_<%%= <%= message_model_name.demodulize.underscore %>.id %>" class="<%= message_variable_name %>"
|
|
2
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #dc2626; background: #fef2f2;">
|
|
3
|
+
<div style="font-weight: bold; margin-bottom: 5px; color: #b91c1c;"><%%= title.presence || "Error" %></div>
|
|
4
|
+
<pre style="white-space: pre-wrap; margin: 0; color: #7f1d1d;"><%%= error_message %></pre>
|
|
5
|
+
<div style="font-size: 0.85em; color: #7f1d1d; margin-top: 5px;">
|
|
6
|
+
<%%= <%= message_model_name.demodulize.underscore %>.created_at&.strftime("%I:%M %p") %>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%%= form_with(model: <%= message_variable_name %>, url: <%= chat_model_name.include?('::') ? "#{chat_model_name.split('::').first.underscore}_#{chat_model_name.demodulize.underscore}_#{message_model_name.demodulize.underscore.pluralize}_path(@#{chat_variable_name})" : "[@#{chat_variable_name}, #{message_variable_name}]" %>, id: "new_<%= message_variable_name %>") do |form| %>
|
|
2
|
+
<%% if <%= message_variable_name %>.errors.any? %>
|
|
3
|
+
<div style="color: red">
|
|
4
|
+
<h2><%%= pluralize(<%= message_variable_name %>.errors.count, "error") %> prohibited this <%= message_table_name.singularize.humanize.downcase %> from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul>
|
|
7
|
+
<%% <%= message_variable_name %>.errors.each do |error| %>
|
|
8
|
+
<li><%%= error.full_message %></li>
|
|
9
|
+
<%% end %>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|
|
12
|
+
<%% end %>
|
|
13
|
+
|
|
14
|
+
<div>
|
|
15
|
+
<%%= form.text_field :content, style: "width: 100%; max-width: 600px;", placeholder: "Message...", autofocus: true %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div>
|
|
19
|
+
<%%= form.submit "Send <%= message_table_name.singularize.humanize.downcase %>" %>
|
|
20
|
+
</div>
|
|
21
|
+
<%% end %>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<%% system ||= local_assigns[:message] %>
|
|
2
|
+
<div id="<%= message_variable_name %>_<%%= system.id %>" class="<%= message_variable_name %>"
|
|
3
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #6b7280; background: #f9fafb;">
|
|
4
|
+
<div style="font-weight: bold; margin-bottom: 5px; color: #374151;">System</div>
|
|
5
|
+
<div style="white-space: pre-wrap; color: #4b5563;"><%%= system.content %></div>
|
|
6
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%% user ||= local_assigns[:message] %>
|
|
2
|
+
<div id="<%= message_variable_name %>_<%%= user.id %>" class="<%= message_variable_name %>"
|
|
3
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #007bff;">
|
|
4
|
+
<div style="font-weight: bold; margin-bottom: 5px;">User</div>
|
|
5
|
+
<div id="<%= message_variable_name %>_<%%= user.id %>_content" style="white-space: pre-wrap;"><%%= user.content %></div>
|
|
6
|
+
<div style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
|
7
|
+
<%%= user.created_at&.strftime("%I:%M %p") %>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<%% if defined?(@<%= chat_variable_name %>) && @<%= chat_variable_name %>.present? %>
|
|
2
|
+
<%%= turbo_stream.replace "new_<%= message_variable_name %>" do %>
|
|
3
|
+
<%%= render "<%= message_model_name.underscore.pluralize %>/form",
|
|
4
|
+
<%= chat_variable_name %>: @<%= chat_variable_name %>,
|
|
5
|
+
<%= message_variable_name %>: @<%= chat_variable_name %>.<%= message_table_name %>.build %>
|
|
6
|
+
<%% end %>
|
|
7
|
+
<%% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<div id="<%= message_variable_name %>_<%%= tool_calls.id %>" class="<%= message_variable_name %>"
|
|
2
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #6b7280; background: #f9fafb;">
|
|
3
|
+
<div style="font-weight: bold; margin-bottom: 5px;">Tool Call</div>
|
|
4
|
+
<pre style="white-space: pre-wrap; margin: 0;"><%%= tool_call.name %>(<%%= tool_call.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") %>)</pre>
|
|
5
|
+
<div style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
|
6
|
+
<%%= tool_calls.created_at&.strftime("%I:%M %p") %>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%% error_message = tool.tool_error_message %>
|
|
2
|
+
<%% if error_message.present? %>
|
|
3
|
+
<%%= render "<%= message_model_name.underscore.pluralize %>/error",
|
|
4
|
+
<%= message_model_name.demodulize.underscore %>: tool,
|
|
5
|
+
title: "Tool Result Error",
|
|
6
|
+
error_message: error_message %>
|
|
7
|
+
<%% else %>
|
|
8
|
+
<div id="<%= message_variable_name %>_<%%= tool.id %>" class="<%= message_variable_name %>"
|
|
9
|
+
style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #6b7280; background: #f9fafb;">
|
|
10
|
+
<div style="font-weight: bold; margin-bottom: 5px;">Tool</div>
|
|
11
|
+
<pre style="white-space: pre-wrap; margin: 0;"><%%= tool.content.presence || "(no output)" %></pre>
|
|
12
|
+
<div style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
|
13
|
+
<%%= tool.created_at&.strftime("%I:%M %p") %>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<%% end %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%% model_id = <%= model_model_name.demodulize.underscore %>.respond_to?(:model_id) ? <%= model_model_name.demodulize.underscore %>.model_id : <%= model_model_name.demodulize.underscore %>.id %>
|
|
2
|
+
<%% row_id = [<%= model_model_name.demodulize.underscore %>.provider, model_id].join("_").parameterize(separator: "_") %>
|
|
3
|
+
<tr id="model_<%%= row_id %>">
|
|
4
|
+
<td><%%= <%= model_model_name.demodulize.underscore %>.provider_class&.name || <%= model_model_name.demodulize.underscore %>.provider %></td>
|
|
5
|
+
<td><%%= <%= model_model_name.demodulize.underscore %>.respond_to?(:display_name) ? <%= model_model_name.demodulize.underscore %>.display_name : <%= model_model_name.demodulize.underscore %>.name %></td>
|
|
6
|
+
<td><%%= number_with_delimiter(<%= model_model_name.demodulize.underscore %>.context_window) if <%= model_model_name.demodulize.underscore %>.context_window %></td>
|
|
7
|
+
<td>
|
|
8
|
+
<%% input = <%= model_model_name.demodulize.underscore %>.input_price_per_million %>
|
|
9
|
+
<%% output = <%= model_model_name.demodulize.underscore %>.output_price_per_million %>
|
|
10
|
+
<%% if input && output %>
|
|
11
|
+
$<%%= "%.2f" % input %> / $<%%= "%.2f" % output %>
|
|
12
|
+
<%% end %>
|
|
13
|
+
</td>
|
|
14
|
+
<td><%%= link_to "Start <%= chat_table_name.singularize.humanize.downcase %>", new_<%= chat_variable_name %>_path(model: model_id) %></td>
|
|
15
|
+
</tr>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<%% if notice.present? %>
|
|
2
|
+
<p style="color: green"><%%= notice %></p>
|
|
3
|
+
<%% end %>
|
|
4
|
+
|
|
5
|
+
<%% content_for :title, "<%= model_model_name.pluralize %>" %>
|
|
6
|
+
|
|
7
|
+
<h1><%= model_model_name.pluralize %></h1>
|
|
8
|
+
<p>
|
|
9
|
+
<%%= link_to "<%= chat_model_name.pluralize %>", <%= chat_table_name %>_path %>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p>
|
|
13
|
+
<%%= button_to "Refresh <%= model_model_name.pluralize %>", refresh_<%= model_table_name %>_path, method: :post %>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<div id="<%= model_table_name %>">
|
|
17
|
+
<%% if @<%= model_variable_name.pluralize %>.any? %>
|
|
18
|
+
<table>
|
|
19
|
+
<thead>
|
|
20
|
+
<tr>
|
|
21
|
+
<th>Provider</th>
|
|
22
|
+
<th>Model</th>
|
|
23
|
+
<th>Context Window</th>
|
|
24
|
+
<th>$/1M tokens (In/Out)</th>
|
|
25
|
+
<th></th>
|
|
26
|
+
</tr>
|
|
27
|
+
</thead>
|
|
28
|
+
<tbody>
|
|
29
|
+
<%% @<%= model_variable_name.pluralize %>.each do |model_info| %>
|
|
30
|
+
<%%= render "<%= model_model_name.underscore.pluralize %>/<%= model_model_name.demodulize.underscore %>",
|
|
31
|
+
<%= model_model_name.demodulize.underscore %>: model_info %>
|
|
32
|
+
<%% end %>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
<%% else %>
|
|
36
|
+
<p>No chat models found.</p>
|
|
37
|
+
<%% end %>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%% content_for :title, @<%= model_variable_name %>.name %>
|
|
2
|
+
|
|
3
|
+
<h1><%%= @<%= model_variable_name %>.name %></h1>
|
|
4
|
+
|
|
5
|
+
<p><strong>ID:</strong> <%%= @<%= model_variable_name %>.model_id %></p>
|
|
6
|
+
<p><strong>Provider:</strong> <%%= @<%= model_variable_name %>.provider_class&.name || @<%= model_variable_name %>.provider %></p>
|
|
7
|
+
<p><strong>Context Window:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.context_window) %> tokens</p>
|
|
8
|
+
<p><strong>Max Output:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.max_output_tokens) %> tokens</p>
|
|
9
|
+
|
|
10
|
+
<%% if @<%= model_variable_name %>.capabilities.any? %>
|
|
11
|
+
<p><strong>Capabilities:</strong> <%%= @<%= model_variable_name %>.capabilities.join(", ") %></p>
|
|
12
|
+
<%% end %>
|
|
13
|
+
|
|
14
|
+
<p>
|
|
15
|
+
<%%= link_to "Start chat with this model", new_<%= chat_variable_name %>_path(model: @<%= model_variable_name %>.model_id) %> |
|
|
16
|
+
<%%= link_to "All models", <%= model_table_name %>_path %>
|
|
17
|
+
</p>
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LexLLM
|
|
4
|
+
module Generators
|
|
5
|
+
# Shared helpers for LexLLM generators
|
|
6
|
+
module GeneratorHelpers
|
|
7
|
+
def parse_model_mappings
|
|
8
|
+
@model_names = {
|
|
9
|
+
chat: 'Chat',
|
|
10
|
+
message: 'Message',
|
|
11
|
+
tool_call: 'ToolCall',
|
|
12
|
+
model: 'Model'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
model_mappings.each do |mapping|
|
|
16
|
+
if mapping.include?(':')
|
|
17
|
+
key, value = mapping.split(':', 2)
|
|
18
|
+
@model_names[key.to_sym] = value.classify
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@model_names
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
%i[chat message tool_call model].each do |type|
|
|
26
|
+
define_method("#{type}_model_name") do
|
|
27
|
+
@model_names ||= parse_model_mappings
|
|
28
|
+
@model_names[type]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
define_method("#{type}_table_name") do
|
|
32
|
+
table_name_for(send("#{type}_model_name"))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
define_method("#{type}_variable_name") do
|
|
36
|
+
variable_name_for(send("#{type}_model_name"))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
define_method("#{type}_controller_class_name") do
|
|
40
|
+
controller_class_name_for(send("#{type}_model_name"))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
define_method("#{type}_job_class_name") do
|
|
44
|
+
"#{variable_name_for(send("#{type}_model_name")).camelize}ResponseJob"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
define_method("#{type}_partial") do
|
|
48
|
+
partial_path_for(send("#{type}_model_name"))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def acts_as_chat_declaration
|
|
53
|
+
params = []
|
|
54
|
+
|
|
55
|
+
add_association_params(params, :messages, message_table_name, message_model_name,
|
|
56
|
+
owner_table: chat_table_name, owner_model_name: chat_model_name, plural: true)
|
|
57
|
+
add_association_params(params, :model, model_table_name, model_model_name,
|
|
58
|
+
owner_table: chat_table_name, owner_model_name: chat_model_name)
|
|
59
|
+
|
|
60
|
+
"acts_as_chat#{" #{params.join(', ')}" if params.any?}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def acts_as_message_declaration
|
|
64
|
+
params = []
|
|
65
|
+
|
|
66
|
+
add_association_params(params, :chat, chat_table_name, chat_model_name,
|
|
67
|
+
owner_table: message_table_name, owner_model_name: message_model_name)
|
|
68
|
+
add_association_params(params, :tool_calls, tool_call_table_name, tool_call_model_name,
|
|
69
|
+
owner_table: message_table_name, owner_model_name: message_model_name, plural: true)
|
|
70
|
+
add_association_params(params, :model, model_table_name, model_model_name,
|
|
71
|
+
owner_table: message_table_name, owner_model_name: message_model_name)
|
|
72
|
+
|
|
73
|
+
"acts_as_message#{" #{params.join(', ')}" if params.any?}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def acts_as_model_declaration
|
|
77
|
+
params = []
|
|
78
|
+
|
|
79
|
+
add_association_params(params, :chats, chat_table_name, chat_model_name,
|
|
80
|
+
owner_table: model_table_name, owner_model_name: model_model_name, plural: true)
|
|
81
|
+
|
|
82
|
+
"acts_as_model#{" #{params.join(', ')}" if params.any?}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def acts_as_tool_call_declaration
|
|
86
|
+
params = []
|
|
87
|
+
|
|
88
|
+
add_association_params(params, :message, message_table_name, message_model_name,
|
|
89
|
+
owner_table: tool_call_table_name, owner_model_name: tool_call_model_name)
|
|
90
|
+
|
|
91
|
+
"acts_as_tool_call#{" #{params.join(', ')}" if params.any?}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def create_namespace_modules
|
|
95
|
+
namespaces = []
|
|
96
|
+
|
|
97
|
+
[chat_model_name, message_model_name, tool_call_model_name, model_model_name].each do |model_name|
|
|
98
|
+
if model_name.include?('::')
|
|
99
|
+
namespace = model_name.split('::').first
|
|
100
|
+
namespaces << namespace unless namespaces.include?(namespace)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
namespaces.each do |namespace|
|
|
105
|
+
module_path = "app/models/#{namespace.underscore}.rb"
|
|
106
|
+
next if File.exist?(Rails.root.join(module_path))
|
|
107
|
+
|
|
108
|
+
create_file module_path do
|
|
109
|
+
<<~RUBY
|
|
110
|
+
module #{namespace}
|
|
111
|
+
def self.table_name_prefix
|
|
112
|
+
"#{namespace.underscore}_"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
RUBY
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def migration_version
|
|
121
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def create_migration_class_name(table_name)
|
|
125
|
+
"create_#{table_name}".camelize
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def postgresql?
|
|
129
|
+
::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
|
|
130
|
+
rescue StandardError
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def mysql?
|
|
135
|
+
::ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
|
|
136
|
+
rescue StandardError
|
|
137
|
+
false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def table_exists?(table_name)
|
|
141
|
+
::ActiveRecord::Base.connection.table_exists?(table_name)
|
|
142
|
+
rescue StandardError
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
# rubocop:disable Metrics/ParameterLists
|
|
149
|
+
def add_association_params(params, default_assoc, table_name, model_name,
|
|
150
|
+
owner_table:, owner_model_name:, plural: false)
|
|
151
|
+
assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
|
|
152
|
+
collection_association = collection_association?(default_assoc, plural)
|
|
153
|
+
foreign_key = inferred_foreign_key(table_name, owner_table, collection_association)
|
|
154
|
+
default_foreign_key = default_inferred_foreign_key(assoc, owner_model_name, collection_association)
|
|
155
|
+
|
|
156
|
+
params << "#{default_assoc}: :#{assoc}" if assoc != default_assoc
|
|
157
|
+
params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify
|
|
158
|
+
params << "#{default_assoc}_foreign_key: :#{foreign_key}" if foreign_key != default_foreign_key
|
|
159
|
+
end
|
|
160
|
+
# rubocop:enable Metrics/ParameterLists
|
|
161
|
+
|
|
162
|
+
def collection_association?(default_assoc, plural)
|
|
163
|
+
plural || default_assoc.to_s.pluralize == default_assoc.to_s
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def inferred_foreign_key(table_name, owner_table, collection_association)
|
|
167
|
+
return "#{table_name.singularize}_id" unless collection_association
|
|
168
|
+
|
|
169
|
+
"#{owner_table.singularize}_id"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Rails default inference:
|
|
173
|
+
# belongs_to :assoc -> assoc_id
|
|
174
|
+
# has_many/has_one -> owner demodulized model name + _id
|
|
175
|
+
def default_inferred_foreign_key(association_name, owner_model_name, collection_association)
|
|
176
|
+
return "#{association_name}_id" unless collection_association
|
|
177
|
+
|
|
178
|
+
"#{owner_model_name.demodulize.underscore}_id"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Convert namespaced model names to proper table names
|
|
182
|
+
# e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
|
|
183
|
+
def table_name_for(model_name)
|
|
184
|
+
model_name.underscore.pluralize.tr('/', '_')
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Convert model name to instance variable name
|
|
188
|
+
# e.g., "LLM::Chat" -> "llm_chat" (not "llm/chat")
|
|
189
|
+
def variable_name_for(model_name)
|
|
190
|
+
model_name.underscore.tr('/', '_')
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Convert model name to controller class name
|
|
194
|
+
# For namespaced models, use Rails convention: "Llm::Chat" -> "Llm::ChatsController"
|
|
195
|
+
# For regular models: "Chat" -> "ChatsController"
|
|
196
|
+
def controller_class_name_for(model_name)
|
|
197
|
+
if model_name.include?('::')
|
|
198
|
+
parts = model_name.split('::')
|
|
199
|
+
namespace = parts[0..-2].join('::')
|
|
200
|
+
resource = parts.last.pluralize
|
|
201
|
+
"#{namespace}::#{resource}Controller"
|
|
202
|
+
else
|
|
203
|
+
"#{model_name.pluralize}Controller"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Convert model name to partial path
|
|
208
|
+
# e.g., "LLM::Message" -> "llm/message" (not "llm_message")
|
|
209
|
+
def partial_path_for(model_name)
|
|
210
|
+
"#{model_name.underscore.pluralize}/#{model_name.demodulize.underscore}"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|