layered-assistant-rails 0.1.0
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/AGENTS.md +94 -0
- data/LICENSE +201 -0
- data/README.md +176 -0
- data/Rakefile +15 -0
- data/app/assets/tailwind/layered/assistant/styles.css +13 -0
- data/app/controllers/concerns/layered/assistant/message_creation.rb +49 -0
- data/app/controllers/concerns/layered/assistant/public/session_conversations.rb +50 -0
- data/app/controllers/layered/assistant/application_controller.rb +25 -0
- data/app/controllers/layered/assistant/assistants_controller.rb +59 -0
- data/app/controllers/layered/assistant/conversations_controller.rb +77 -0
- data/app/controllers/layered/assistant/messages_controller.rb +57 -0
- data/app/controllers/layered/assistant/models_controller.rb +61 -0
- data/app/controllers/layered/assistant/panel/conversations_controller.rb +63 -0
- data/app/controllers/layered/assistant/panel/messages_controller.rb +44 -0
- data/app/controllers/layered/assistant/providers_controller.rb +55 -0
- data/app/controllers/layered/assistant/public/application_controller.rb +16 -0
- data/app/controllers/layered/assistant/public/assistants_controller.rb +16 -0
- data/app/controllers/layered/assistant/public/conversations_controller.rb +33 -0
- data/app/controllers/layered/assistant/public/messages_controller.rb +42 -0
- data/app/controllers/layered/assistant/public/panel/conversations_controller.rb +62 -0
- data/app/controllers/layered/assistant/public/panel/messages_controller.rb +50 -0
- data/app/controllers/layered/assistant/setup_controller.rb +9 -0
- data/app/helpers/layered/assistant/access_helper.rb +45 -0
- data/app/helpers/layered/assistant/messages_helper.rb +41 -0
- data/app/helpers/layered/assistant/panel_helper.rb +38 -0
- data/app/javascript/layered_assistant/composer_controller.js +30 -0
- data/app/javascript/layered_assistant/index.js +14 -0
- data/app/javascript/layered_assistant/message_streaming.js +124 -0
- data/app/javascript/layered_assistant/messages_controller.js +62 -0
- data/app/javascript/layered_assistant/panel_controller.js +36 -0
- data/app/javascript/layered_assistant/panel_nav_controller.js +16 -0
- data/app/javascript/layered_assistant/provider_template_controller.js +45 -0
- data/app/javascript/layered_assistant/vendor/marked.esm.js +72 -0
- data/app/jobs/layered/assistant/application_job.rb +6 -0
- data/app/jobs/layered/assistant/messages/response_job.rb +36 -0
- data/app/models/layered/assistant/application_record.rb +7 -0
- data/app/models/layered/assistant/assistant.rb +22 -0
- data/app/models/layered/assistant/conversation.rb +39 -0
- data/app/models/layered/assistant/message.rb +56 -0
- data/app/models/layered/assistant/model.rb +21 -0
- data/app/models/layered/assistant/provider.rb +49 -0
- data/app/services/layered/assistant/chunk_service.rb +80 -0
- data/app/services/layered/assistant/client_service.rb +18 -0
- data/app/services/layered/assistant/clients/anthropic.rb +24 -0
- data/app/services/layered/assistant/clients/base.rb +29 -0
- data/app/services/layered/assistant/clients/openai.rb +33 -0
- data/app/services/layered/assistant/messages_service.rb +58 -0
- data/app/services/layered/assistant/models/create_service.rb +50 -0
- data/app/services/layered/assistant/token_estimator.rb +11 -0
- data/app/views/layered/assistant/assistants/_form.html.erb +42 -0
- data/app/views/layered/assistant/assistants/edit.html.erb +6 -0
- data/app/views/layered/assistant/assistants/index.html.erb +45 -0
- data/app/views/layered/assistant/assistants/new.html.erb +6 -0
- data/app/views/layered/assistant/conversations/_form.html.erb +29 -0
- data/app/views/layered/assistant/conversations/edit.html.erb +6 -0
- data/app/views/layered/assistant/conversations/index.html.erb +63 -0
- data/app/views/layered/assistant/conversations/new.html.erb +6 -0
- data/app/views/layered/assistant/conversations/show.html.erb +25 -0
- data/app/views/layered/assistant/messages/_composer.html.erb +15 -0
- data/app/views/layered/assistant/messages/_message.html.erb +25 -0
- data/app/views/layered/assistant/messages/_system_prompt.html.erb +10 -0
- data/app/views/layered/assistant/messages/create.turbo_stream.erb +9 -0
- data/app/views/layered/assistant/messages/index.html.erb +46 -0
- data/app/views/layered/assistant/models/_form.html.erb +30 -0
- data/app/views/layered/assistant/models/edit.html.erb +6 -0
- data/app/views/layered/assistant/models/index.html.erb +54 -0
- data/app/views/layered/assistant/models/new.html.erb +6 -0
- data/app/views/layered/assistant/panel/conversations/_header.html.erb +23 -0
- data/app/views/layered/assistant/panel/conversations/index.html.erb +46 -0
- data/app/views/layered/assistant/panel/conversations/new.html.erb +23 -0
- data/app/views/layered/assistant/panel/conversations/show.html.erb +24 -0
- data/app/views/layered/assistant/panel/messages/_composer.html.erb +15 -0
- data/app/views/layered/assistant/panel/messages/create.turbo_stream.erb +9 -0
- data/app/views/layered/assistant/providers/_form.html.erb +81 -0
- data/app/views/layered/assistant/providers/edit.html.erb +6 -0
- data/app/views/layered/assistant/providers/index.html.erb +47 -0
- data/app/views/layered/assistant/providers/new.html.erb +6 -0
- data/app/views/layered/assistant/public/assistants/index.html.erb +34 -0
- data/app/views/layered/assistant/public/assistants/show.html.erb +23 -0
- data/app/views/layered/assistant/public/conversations/show.html.erb +24 -0
- data/app/views/layered/assistant/public/messages/_composer.html.erb +7 -0
- data/app/views/layered/assistant/public/messages/create.turbo_stream.erb +9 -0
- data/app/views/layered/assistant/public/panel/conversations/_header.html.erb +16 -0
- data/app/views/layered/assistant/public/panel/conversations/index.html.erb +48 -0
- data/app/views/layered/assistant/public/panel/conversations/new.html.erb +17 -0
- data/app/views/layered/assistant/public/panel/conversations/show.html.erb +23 -0
- data/app/views/layered/assistant/public/panel/messages/_composer.html.erb +7 -0
- data/app/views/layered/assistant/public/panel/messages/create.turbo_stream.erb +9 -0
- data/app/views/layered/assistant/setup/_setup.html.erb +121 -0
- data/app/views/layered/assistant/setup/index.html.erb +2 -0
- data/app/views/layouts/layered/assistant/_host_navigation.html.erb +0 -0
- data/app/views/layouts/layered/assistant/application.html.erb +32 -0
- data/config/importmap.rb +8 -0
- data/config/routes.rb +31 -0
- data/data/models.json +42 -0
- data/db/migrate/20260312000000_create_layered_assistant_tables.rb +63 -0
- data/lib/generators/layered/assistant/install_generator.rb +113 -0
- data/lib/generators/layered/assistant/migrations_generator.rb +47 -0
- data/lib/generators/layered/assistant/templates/initializer.rb +26 -0
- data/lib/layered/assistant/engine.rb +29 -0
- data/lib/layered/assistant/version.rb +5 -0
- data/lib/layered/assistant.rb +19 -0
- data/lib/layered-assistant-rails.rb +1 -0
- metadata +449 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Assistant
|
|
3
|
+
module Generators
|
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
|
5
|
+
desc "Install layered-ui-assistant into the host application"
|
|
6
|
+
|
|
7
|
+
def self.source_root
|
|
8
|
+
Layered::Assistant::Engine.root
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def verify_layered_ui_dependency
|
|
12
|
+
application_css = "app/assets/tailwind/application.css"
|
|
13
|
+
application_js = "app/javascript/application.js"
|
|
14
|
+
|
|
15
|
+
css_ok = File.exist?(application_css) && File.read(application_css).include?('@import "./layered_ui"')
|
|
16
|
+
js_ok = File.exist?(application_js) && File.read(application_js).include?('import "layered_ui"')
|
|
17
|
+
|
|
18
|
+
unless css_ok && js_ok
|
|
19
|
+
say "layered-ui-rails is not installed yet - installing now...", :yellow
|
|
20
|
+
invoke "layered:ui:install"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def copy_css
|
|
25
|
+
source_path = File.join(self.class.source_root, "app/assets/tailwind/layered/assistant/styles.css")
|
|
26
|
+
source_content = File.read(source_path)
|
|
27
|
+
|
|
28
|
+
header = <<~CSS
|
|
29
|
+
/*
|
|
30
|
+
* layered-assistant-rails v#{Layered::Assistant::VERSION}
|
|
31
|
+
*
|
|
32
|
+
* This file was automatically generated by the layered:assistant:install generator.
|
|
33
|
+
* Do not modify directly. To update, re-run: bin/rails generate layered:assistant:install
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
CSS
|
|
37
|
+
|
|
38
|
+
create_file "app/assets/tailwind/layered_assistant.css", header + source_content, force: true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def add_css_import
|
|
42
|
+
application_css = "app/assets/tailwind/application.css"
|
|
43
|
+
|
|
44
|
+
return unless File.exist?(application_css)
|
|
45
|
+
|
|
46
|
+
content = File.read(application_css)
|
|
47
|
+
import_line = '@import "./layered_assistant";'
|
|
48
|
+
|
|
49
|
+
return if content.include?(import_line)
|
|
50
|
+
|
|
51
|
+
# Insert after the layered_ui import (which must already be present)
|
|
52
|
+
inject_into_file application_css, "\n#{import_line}", after: '@import "./layered_ui";'
|
|
53
|
+
say "Added import to #{application_css}", :green
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_js_import
|
|
57
|
+
application_js = "app/javascript/application.js"
|
|
58
|
+
|
|
59
|
+
return unless File.exist?(application_js)
|
|
60
|
+
|
|
61
|
+
content = File.read(application_js)
|
|
62
|
+
import_line = 'import "layered_assistant"'
|
|
63
|
+
|
|
64
|
+
return if content.include?(import_line)
|
|
65
|
+
|
|
66
|
+
# Insert after the layered_ui import (which must already be present)
|
|
67
|
+
inject_into_file application_js, "\n#{import_line}", after: 'import "layered_ui"'
|
|
68
|
+
say "Added import to #{application_js}", :green
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def copy_initializer
|
|
72
|
+
initializer_path = "config/initializers/layered_assistant.rb"
|
|
73
|
+
|
|
74
|
+
return if File.exist?(initializer_path)
|
|
75
|
+
|
|
76
|
+
template_path = File.expand_path("templates/initializer.rb", __dir__)
|
|
77
|
+
create_file initializer_path, File.read(template_path)
|
|
78
|
+
say "Created #{initializer_path}", :green
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def mount_engine
|
|
82
|
+
routes_file = "config/routes.rb"
|
|
83
|
+
|
|
84
|
+
return unless File.exist?(routes_file)
|
|
85
|
+
|
|
86
|
+
content = File.read(routes_file)
|
|
87
|
+
mount_line = 'mount Layered::Assistant::Engine => "/layered/assistant"'
|
|
88
|
+
|
|
89
|
+
return if content.include?("Layered::Assistant::Engine")
|
|
90
|
+
|
|
91
|
+
inject_into_file routes_file, " #{mount_line}\n\n", after: "Rails.application.routes.draw do\n"
|
|
92
|
+
say "Mounted engine in #{routes_file}", :green
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def copy_migrations
|
|
96
|
+
invoke "layered:assistant:migrations"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def show_instructions
|
|
100
|
+
say ""
|
|
101
|
+
say "layered-assistant-rails installed successfully", :green
|
|
102
|
+
say ""
|
|
103
|
+
say "Next steps:", :yellow
|
|
104
|
+
say " 1. Run bin/rails db:migrate"
|
|
105
|
+
say " 2. Configure authorization in config/initializers/layered_assistant.rb"
|
|
106
|
+
say " 3. Routes return 403 until you uncomment an authorize block"
|
|
107
|
+
say " 4. Visit /layered/assistant to get started"
|
|
108
|
+
say ""
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Assistant
|
|
3
|
+
module Generators
|
|
4
|
+
class MigrationsGenerator < Rails::Generators::Base
|
|
5
|
+
desc "Copy layered-assistant-rails migrations to the host application"
|
|
6
|
+
|
|
7
|
+
def copy_migrations
|
|
8
|
+
engine_migrations_path = Layered::Assistant::Engine.root.join("db/migrate")
|
|
9
|
+
app_migrations_path = Rails.root.join("db/migrate")
|
|
10
|
+
|
|
11
|
+
unless engine_migrations_path.exist?
|
|
12
|
+
say "No migrations found in layered-assistant-rails.", :yellow
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
existing_migrations = Dir[app_migrations_path.join("*.rb")].map { |f| migration_name(File.basename(f)) }
|
|
17
|
+
|
|
18
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
|
19
|
+
|
|
20
|
+
Dir[engine_migrations_path.join("*.rb")].sort.each do |source|
|
|
21
|
+
basename = File.basename(source)
|
|
22
|
+
name = migration_name(basename)
|
|
23
|
+
|
|
24
|
+
if existing_migrations.include?(name)
|
|
25
|
+
say " skip #{name} (already exists)", :yellow
|
|
26
|
+
next
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
destination = app_migrations_path.join("#{timestamp}_#{name}.layered_assistant.rb")
|
|
30
|
+
content = "# This migration comes from layered_assistant (originally #{basename.split("_").first})\n" + File.read(source)
|
|
31
|
+
|
|
32
|
+
create_file destination, content
|
|
33
|
+
timestamp += 1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# Extract migration name without timestamp prefix
|
|
40
|
+
# e.g. "20260202074215_create_layered_assistant_providers.rb" => "create_layered_assistant_providers"
|
|
41
|
+
def migration_name(filename)
|
|
42
|
+
filename.sub(/^\d+_/, "").sub(/\.layered_assistant\.rb$/, "").sub(/\.rb$/, "")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Configure authorization for layered-assistant-rails.
|
|
2
|
+
# All non-public engine routes return 403 Forbidden until this block is configured.
|
|
3
|
+
# Uncomment one of the examples below, or write your own.
|
|
4
|
+
#
|
|
5
|
+
# The block runs in controller context, so you have access to request,
|
|
6
|
+
# current_user, redirect_to, head, main_app, and all other controller methods.
|
|
7
|
+
#
|
|
8
|
+
# Once configured, visit /layered/assistant (or your mount path) to get started.
|
|
9
|
+
|
|
10
|
+
# Allow all requests (no authorization):
|
|
11
|
+
#
|
|
12
|
+
# Layered::Assistant.authorize do
|
|
13
|
+
# # No-op: all requests permitted
|
|
14
|
+
# end
|
|
15
|
+
|
|
16
|
+
# Require sign-in (Devise):
|
|
17
|
+
#
|
|
18
|
+
# Layered::Assistant.authorize do
|
|
19
|
+
# redirect_to main_app.new_user_session_path unless user_signed_in?
|
|
20
|
+
# end
|
|
21
|
+
|
|
22
|
+
# Restrict to admins:
|
|
23
|
+
#
|
|
24
|
+
# Layered::Assistant.authorize do
|
|
25
|
+
# head :forbidden unless current_user&.admin?
|
|
26
|
+
# end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Assistant
|
|
3
|
+
class Engine < ::Rails::Engine
|
|
4
|
+
isolate_namespace Layered::Assistant
|
|
5
|
+
|
|
6
|
+
initializer "layered-assistant-rails.inflections", before: :set_autoload_paths do
|
|
7
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
|
8
|
+
inflect.acronym "OpenAI"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
initializer "layered-assistant-rails.importmap", before: "importmap" do |app|
|
|
13
|
+
app.config.importmap.paths << Engine.root.join("config/importmap.rb")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer "layered-assistant-rails.assets" do |app|
|
|
17
|
+
app.config.assets.paths << Engine.root.join("app/javascript")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
initializer "layered-assistant-rails.helpers" do
|
|
21
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
22
|
+
helper Layered::Assistant::AccessHelper
|
|
23
|
+
helper Layered::Assistant::MessagesHelper
|
|
24
|
+
helper Layered::Assistant::PanelHelper
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "positioning"
|
|
2
|
+
require "pagy"
|
|
3
|
+
require "anthropic"
|
|
4
|
+
require "openai"
|
|
5
|
+
require "kramdown"
|
|
6
|
+
require "kramdown-parser-gfm"
|
|
7
|
+
require "layered-ui-rails"
|
|
8
|
+
require "layered/assistant/version"
|
|
9
|
+
require "layered/assistant/engine"
|
|
10
|
+
|
|
11
|
+
module Layered
|
|
12
|
+
module Assistant
|
|
13
|
+
mattr_reader :authorize_block
|
|
14
|
+
|
|
15
|
+
def self.authorize(&block)
|
|
16
|
+
@@authorize_block = block
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "layered/assistant"
|