layered-assistant-rails 0.2.2 → 0.3.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 +4 -4
- data/AGENTS.md +4 -0
- data/README.md +1 -7
- data/app/controllers/layered/assistant/assistants_controller.rb +13 -3
- data/app/controllers/layered/assistant/personas_controller.rb +60 -0
- data/app/models/layered/assistant/assistant.rb +1 -0
- data/app/models/layered/assistant/conversation.rb +10 -0
- data/app/models/layered/assistant/persona.rb +20 -0
- data/app/services/layered/assistant/client_service.rb +1 -3
- data/app/services/layered/assistant/clients/anthropic.rb +2 -2
- data/app/services/layered/assistant/clients/base.rb +1 -1
- data/app/services/layered/assistant/clients/openai.rb +2 -2
- data/app/services/layered/assistant/messages_service.rb +5 -7
- data/app/services/layered/assistant/system_prompt_service.rb +17 -0
- data/app/views/layered/assistant/assistants/_form.html.erb +9 -3
- data/app/views/layered/assistant/conversations/show.html.erb +0 -1
- data/app/views/layered/assistant/panel/conversations/show.html.erb +0 -1
- data/app/views/layered/assistant/personas/_form.html.erb +31 -0
- data/app/views/layered/assistant/personas/edit.html.erb +2 -0
- data/app/views/layered/assistant/personas/index.html.erb +41 -0
- data/app/views/layered/assistant/personas/new.html.erb +2 -0
- data/app/views/layouts/layered/assistant/application.html.erb +1 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20260403000000_create_layered_assistant_personas.rb +13 -0
- data/db/migrate/20260403000001_add_persona_to_layered_assistant_assistants.rb +5 -0
- data/db/migrate/20260406000000_rename_system_prompt_to_instructions.rb +5 -0
- data/lib/generators/layered/assistant/templates/initializer.rb +2 -4
- data/lib/layered/assistant/version.rb +1 -1
- metadata +11 -2
- data/app/views/layered/assistant/messages/_system_prompt.html.erb +0 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c4d53502a7e91e56f50420d3d16830d3441a49a3a6a5a399be405d10e0c05b4
|
|
4
|
+
data.tar.gz: 6155fe4297f73702449ac22e099692cc17d6582c697f68c0e7b9df8f0f3bc96f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8131ee6e51b4f9417501de38b88af330e1dbd6b0603e4284e56900e37b41c549265d56821b6518846e1d1c95382d4fe186fca8c2d5a450c7c4a17f2a5ac79266
|
|
7
|
+
data.tar.gz: 9ff9c8b830ee1cabaeed1da03332e57f5622ce79104f3d0f2e760be19d2dc503adcc0195bb3eb0d877b9b4a656a8556e6596301b087eb9fb805d619291978e18
|
data/AGENTS.md
CHANGED
|
@@ -39,6 +39,10 @@ You can run the tests with: `bundle exec rake test`
|
|
|
39
39
|
- Tables prefixed `layered_assistant_`
|
|
40
40
|
- Models inherit from `Layered::Assistant::ApplicationRecord`
|
|
41
41
|
|
|
42
|
+
### Ownership and scoping
|
|
43
|
+
|
|
44
|
+
Ownership is enforced at the controller layer via `scoped()`, not with model validations. When a controller action accepts a foreign key for a scoped model (e.g. `persona_id`, `assistant_id`), look up the record through `scoped(Model).find(params[:id])` so that out-of-scope IDs return 404. Do not add model-level validations that duplicate this scoping logic.
|
|
45
|
+
|
|
42
46
|
### Generators
|
|
43
47
|
|
|
44
48
|
Verify dependencies before modifying host app files. Use `inject_into_file` for safe insertion. Check for existing imports to ensure idempotency.
|
data/README.md
CHANGED
|
@@ -106,13 +106,7 @@ The `l_assistant_accessible?` helper evaluates the authorize block without side
|
|
|
106
106
|
|
|
107
107
|
By default, all records are visible to any authorised user. If your application is multi-tenant or you need to restrict which records a user can see, configure a `scope` block in the initialiser.
|
|
108
108
|
|
|
109
|
-
The block receives the model class, runs in controller context, and must return an `ActiveRecord::Relation`.
|
|
110
|
-
|
|
111
|
-
| Model | Description |
|
|
112
|
-
|---|---|
|
|
113
|
-
| `Layered::Assistant::Conversation` | User conversations (has polymorphic `owner`) |
|
|
114
|
-
| `Layered::Assistant::Assistant` | Assistant configurations (has polymorphic `owner`) |
|
|
115
|
-
| `Layered::Assistant::Provider` | API provider credentials (has polymorphic `owner`) |
|
|
109
|
+
The block receives the model class, runs in controller context, and must return an `ActiveRecord::Relation`. All engine models with a polymorphic `owner` association are passed through the scope block.
|
|
116
110
|
|
|
117
111
|
### Scope all owned resources to the current user
|
|
118
112
|
|
|
@@ -3,6 +3,7 @@ module Layered
|
|
|
3
3
|
class AssistantsController < ApplicationController
|
|
4
4
|
before_action :set_assistant, only: [:edit, :update, :destroy]
|
|
5
5
|
before_action :set_models, only: [:new, :create, :edit, :update]
|
|
6
|
+
before_action :set_personas, only: [:new, :create, :edit, :update]
|
|
6
7
|
|
|
7
8
|
def index
|
|
8
9
|
@page_title = "Assistants"
|
|
@@ -15,8 +16,9 @@ module Layered
|
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def create
|
|
18
|
-
@assistant = Assistant.new(assistant_params)
|
|
19
|
+
@assistant = Assistant.new(assistant_params.except(:persona_id))
|
|
19
20
|
@assistant.owner = l_ui_current_user
|
|
21
|
+
@assistant.persona = scoped(Persona).find(assistant_params[:persona_id]) if assistant_params[:persona_id].present?
|
|
20
22
|
|
|
21
23
|
if @assistant.save
|
|
22
24
|
redirect_to layered_assistant.assistants_path, notice: "Assistant was successfully created."
|
|
@@ -30,7 +32,11 @@ module Layered
|
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def update
|
|
33
|
-
if
|
|
35
|
+
if assistant_params.key?(:persona_id)
|
|
36
|
+
@assistant.persona = assistant_params[:persona_id].present? ? scoped(Persona).find(assistant_params[:persona_id]) : nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if @assistant.update(assistant_params.except(:persona_id))
|
|
34
40
|
redirect_to layered_assistant.assistants_path, notice: "Assistant was successfully updated."
|
|
35
41
|
else
|
|
36
42
|
render :edit, status: :unprocessable_entity
|
|
@@ -52,8 +58,12 @@ module Layered
|
|
|
52
58
|
@models = Model.available
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
def set_personas
|
|
62
|
+
@personas = scoped(Persona).by_name
|
|
63
|
+
end
|
|
64
|
+
|
|
55
65
|
def assistant_params
|
|
56
|
-
params.require(:assistant).permit(:name, :description, :
|
|
66
|
+
params.require(:assistant).permit(:name, :description, :instructions, :default_model_id, :persona_id, :public)
|
|
57
67
|
end
|
|
58
68
|
end
|
|
59
69
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Assistant
|
|
3
|
+
class PersonasController < ApplicationController
|
|
4
|
+
before_action :set_persona, only: [:edit, :update, :destroy]
|
|
5
|
+
|
|
6
|
+
def index
|
|
7
|
+
@page_title = "Personas"
|
|
8
|
+
@pagy, @personas = pagy(scoped(Persona).by_name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def new
|
|
12
|
+
@page_title = "New persona"
|
|
13
|
+
@persona = Persona.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
@persona = Persona.new(persona_params)
|
|
18
|
+
@persona.owner = l_ui_current_user
|
|
19
|
+
|
|
20
|
+
if @persona.save
|
|
21
|
+
redirect_to layered_assistant.personas_path, notice: "Persona was successfully created."
|
|
22
|
+
else
|
|
23
|
+
render :new, status: :unprocessable_entity
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def edit
|
|
28
|
+
@page_title = "Edit persona"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update
|
|
32
|
+
if @persona.update(persona_params)
|
|
33
|
+
redirect_to layered_assistant.personas_path, notice: "Persona was successfully updated."
|
|
34
|
+
else
|
|
35
|
+
render :edit, status: :unprocessable_entity
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def destroy
|
|
40
|
+
if @persona.destroy
|
|
41
|
+
redirect_to layered_assistant.personas_path, notice: "Persona was successfully deleted."
|
|
42
|
+
else
|
|
43
|
+
redirect_to layered_assistant.personas_path, alert: "Persona could not be deleted: #{@persona.errors.full_messages.to_sentence}."
|
|
44
|
+
end
|
|
45
|
+
rescue ActiveRecord::InvalidForeignKey
|
|
46
|
+
redirect_to layered_assistant.personas_path, alert: "Persona could not be deleted because it is assigned to assistants."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def set_persona
|
|
52
|
+
@persona = scoped(Persona).find(params[:id])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def persona_params
|
|
56
|
+
params.require(:persona).permit(:name, :description, :instructions)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -7,6 +7,7 @@ module Layered
|
|
|
7
7
|
# Associations
|
|
8
8
|
belongs_to :owner, polymorphic: true, optional: true
|
|
9
9
|
belongs_to :default_model, class_name: "Layered::Assistant::Model", optional: true, counter_cache: :assistants_count
|
|
10
|
+
belongs_to :persona, optional: true, counter_cache: :assistants_count
|
|
10
11
|
has_many :conversations, dependent: :destroy
|
|
11
12
|
|
|
12
13
|
# Validations
|
|
@@ -10,6 +10,9 @@ module Layered
|
|
|
10
10
|
belongs_to :subject, polymorphic: true, optional: true
|
|
11
11
|
has_many :messages, dependent: :destroy
|
|
12
12
|
|
|
13
|
+
# Callbacks
|
|
14
|
+
after_create :create_system_message
|
|
15
|
+
|
|
13
16
|
# Validations
|
|
14
17
|
validates :name, presence: true
|
|
15
18
|
|
|
@@ -73,6 +76,13 @@ module Layered
|
|
|
73
76
|
|
|
74
77
|
private
|
|
75
78
|
|
|
79
|
+
def create_system_message
|
|
80
|
+
prompt = SystemPromptService.new.call(assistant: assistant)
|
|
81
|
+
return if prompt.blank?
|
|
82
|
+
|
|
83
|
+
messages.create!(role: :system, content: prompt)
|
|
84
|
+
end
|
|
85
|
+
|
|
76
86
|
def broadcast_name_updated(old_name)
|
|
77
87
|
css_class = "#{ActionView::RecordIdentifier.dom_id(self)}_name"
|
|
78
88
|
Turbo::StreamsChannel.broadcast_action_to(
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Assistant
|
|
3
|
+
class Persona < ApplicationRecord
|
|
4
|
+
# UID
|
|
5
|
+
has_secure_token :uid
|
|
6
|
+
|
|
7
|
+
# Associations
|
|
8
|
+
belongs_to :owner, polymorphic: true, optional: true
|
|
9
|
+
has_many :assistants, dependent: :restrict_with_error
|
|
10
|
+
|
|
11
|
+
# Validations
|
|
12
|
+
validates :name, presence: true
|
|
13
|
+
validates :instructions, presence: true
|
|
14
|
+
|
|
15
|
+
# Scopes
|
|
16
|
+
scope :by_name, -> { order(name: :asc, created_at: :desc) }
|
|
17
|
+
scope :by_created_at, -> { order(created_at: :desc) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -4,13 +4,11 @@ module Layered
|
|
|
4
4
|
def call(message:, stream_proc:)
|
|
5
5
|
provider = message.model.provider
|
|
6
6
|
client = Clients::Base.for(provider)
|
|
7
|
-
system_prompt = message.conversation.assistant.system_prompt
|
|
8
7
|
|
|
9
8
|
client.chat(
|
|
10
9
|
messages: message.conversation.messages,
|
|
11
10
|
model: message.model.identifier,
|
|
12
|
-
stream_proc: stream_proc
|
|
13
|
-
system_prompt: system_prompt
|
|
11
|
+
stream_proc: stream_proc
|
|
14
12
|
)
|
|
15
13
|
end
|
|
16
14
|
end
|
|
@@ -2,8 +2,8 @@ module Layered
|
|
|
2
2
|
module Assistant
|
|
3
3
|
module Clients
|
|
4
4
|
class Anthropic < Base
|
|
5
|
-
def chat(messages:, model:, stream_proc
|
|
6
|
-
formatted = MessagesService.new.format(messages, provider: @provider
|
|
5
|
+
def chat(messages:, model:, stream_proc:)
|
|
6
|
+
formatted = MessagesService.new.format(messages, provider: @provider)
|
|
7
7
|
|
|
8
8
|
parameters = {
|
|
9
9
|
model: model,
|
|
@@ -9,7 +9,7 @@ module Layered
|
|
|
9
9
|
raise StandardError, "API key is not set for provider #{provider.name}" if @api_key.blank?
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def chat(messages:, model:, stream_proc
|
|
12
|
+
def chat(messages:, model:, stream_proc:)
|
|
13
13
|
raise NotImplementedError
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -2,8 +2,8 @@ module Layered
|
|
|
2
2
|
module Assistant
|
|
3
3
|
module Clients
|
|
4
4
|
class OpenAI < Base
|
|
5
|
-
def chat(messages:, model:, stream_proc
|
|
6
|
-
formatted = MessagesService.new.format(messages, provider: @provider
|
|
5
|
+
def chat(messages:, model:, stream_proc:)
|
|
6
|
+
formatted = MessagesService.new.format(messages, provider: @provider)
|
|
7
7
|
|
|
8
8
|
client_options = {
|
|
9
9
|
access_token: @api_key,
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
module Layered
|
|
2
2
|
module Assistant
|
|
3
3
|
class MessagesService
|
|
4
|
-
def format(messages, provider: nil
|
|
4
|
+
def format(messages, provider: nil)
|
|
5
5
|
protocol = provider&.protocol
|
|
6
6
|
|
|
7
7
|
if protocol == "openai"
|
|
8
|
-
format_openai(messages
|
|
8
|
+
format_openai(messages)
|
|
9
9
|
else
|
|
10
|
-
format_anthropic(messages
|
|
10
|
+
format_anthropic(messages)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
private
|
|
15
15
|
|
|
16
|
-
def format_anthropic(messages
|
|
16
|
+
def format_anthropic(messages)
|
|
17
17
|
system_messages = []
|
|
18
|
-
system_messages << system_prompt if system_prompt.present?
|
|
19
18
|
regular_messages = []
|
|
20
19
|
|
|
21
20
|
messages.by_created_at.each do |message|
|
|
@@ -35,9 +34,8 @@ module Layered
|
|
|
35
34
|
result
|
|
36
35
|
end
|
|
37
36
|
|
|
38
|
-
def format_openai(messages
|
|
37
|
+
def format_openai(messages)
|
|
39
38
|
formatted = []
|
|
40
|
-
formatted << { role: "system", content: system_prompt } if system_prompt.present?
|
|
41
39
|
|
|
42
40
|
messages.by_created_at.each do |message|
|
|
43
41
|
case message.role
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Layered
|
|
2
|
+
module Assistant
|
|
3
|
+
class SystemPromptService
|
|
4
|
+
def call(assistant:)
|
|
5
|
+
parts = []
|
|
6
|
+
|
|
7
|
+
if assistant.persona&.instructions.present?
|
|
8
|
+
parts << "**Persona**\n\n#{assistant.persona.instructions}"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
parts << assistant.instructions if assistant.instructions.present?
|
|
12
|
+
|
|
13
|
+
parts.join("\n\n").presence
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -14,9 +14,15 @@
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
16
|
<div class="l-ui-form__group">
|
|
17
|
-
<%= render "layered_ui/shared/label", form: f, field: :
|
|
18
|
-
<%= f.text_area :
|
|
19
|
-
<%= render "layered_ui/shared/field_error", object: assistant, field: :
|
|
17
|
+
<%= render "layered_ui/shared/label", form: f, field: :instructions, required: false %>
|
|
18
|
+
<%= f.text_area :instructions, class: "l-ui-form__field", rows: 5 %>
|
|
19
|
+
<%= render "layered_ui/shared/field_error", object: assistant, field: :instructions %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="l-ui-form__group">
|
|
23
|
+
<%= render "layered_ui/shared/label", form: f, field: :persona_id, name: "Persona", required: false %>
|
|
24
|
+
<%= f.select :persona_id, @personas.map { |p| [p.name, p.id] }, { include_blank: "None" }, class: "l-ui-select" %>
|
|
25
|
+
<%= render "layered_ui/shared/field_error", object: assistant, field: :persona_id %>
|
|
20
26
|
</div>
|
|
21
27
|
|
|
22
28
|
<div class="l-ui-form__group">
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
<div class="l-ui-conversation__messages" data-controller="messages">
|
|
10
10
|
<div id="<%= dom_id(@conversation) %>_messages" class="<%= dom_id(@conversation) %>_messages l-ui-conversation" aria-live="polite" data-messages-target="list">
|
|
11
|
-
<%= render "layered/assistant/messages/system_prompt", conversation: @conversation %>
|
|
12
11
|
<%= render partial: "layered/assistant/messages/message", collection: @messages, as: :message %>
|
|
13
12
|
</div>
|
|
14
13
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
<div class="l-ui-conversation__messages" data-controller="messages">
|
|
8
8
|
<div id="panel_<%= dom_id(@conversation) %>_messages" class="<%= dom_id(@conversation) %>_messages l-ui-conversation" aria-live="polite" data-messages-target="list">
|
|
9
|
-
<%= render "layered/assistant/messages/system_prompt", conversation: @conversation %>
|
|
10
9
|
<%= render partial: "layered/assistant/messages/message", collection: @messages, as: :message %>
|
|
11
10
|
</div>
|
|
12
11
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%= form_with(model: persona, url: url, html: { class: "l-ui-form" }) do |f| %>
|
|
2
|
+
<%= render "layered_ui/shared/form_errors", item: persona %>
|
|
3
|
+
|
|
4
|
+
<div class="l-ui-form__group">
|
|
5
|
+
<%= render "layered_ui/shared/label", form: f, field: :name, required: true %>
|
|
6
|
+
<%= f.text_field :name, class: "l-ui-form__field" %>
|
|
7
|
+
<%= render "layered_ui/shared/field_error", object: persona, field: :name %>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="l-ui-form__group">
|
|
11
|
+
<%= render "layered_ui/shared/label", form: f, field: :description, required: false %>
|
|
12
|
+
<%= f.text_area :description, class: "l-ui-form__field", rows: 3 %>
|
|
13
|
+
<%= render "layered_ui/shared/field_error", object: persona, field: :description %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="l-ui-form__group">
|
|
17
|
+
<%= render "layered_ui/shared/label", form: f, field: :instructions, required: true %>
|
|
18
|
+
<%= f.text_area :instructions, class: "l-ui-form__field", rows: 5 %>
|
|
19
|
+
<p class="l-ui-form__hint">Shape the personality and tone of conversations, used alongside the assistant's own instructions. e.g. "Reply in British English with a formal tone"</p>
|
|
20
|
+
<%= render "layered_ui/shared/field_error", object: persona, field: :instructions %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="l-ui-container--spread l-ui-utility--mt-2xl">
|
|
24
|
+
<% if persona.persisted? %>
|
|
25
|
+
<%= link_to "Delete", layered_assistant.persona_path(persona), class: "l-ui-button--outline-danger", data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>
|
|
26
|
+
<% else %>
|
|
27
|
+
<span></span>
|
|
28
|
+
<% end %>
|
|
29
|
+
<%= f.submit persona.new_record? ? "Create" : "Update", class: "l-ui-button--primary" %>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<div class="l-ui-container--spread">
|
|
2
|
+
<h1>Personas</h1>
|
|
3
|
+
<%= link_to "New", layered_assistant.new_persona_path, class: "l-ui-button--primary" %>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="l-ui-container--table l-ui-utility--mt-lg">
|
|
7
|
+
<table class="l-ui-table">
|
|
8
|
+
<caption class="l-ui-sr-only">Personas</caption>
|
|
9
|
+
<thead class="l-ui-table__header">
|
|
10
|
+
<tr>
|
|
11
|
+
<th scope="col" class="l-ui-table__header-cell">Name</th>
|
|
12
|
+
<th scope="col" class="l-ui-table__header-cell">Description</th>
|
|
13
|
+
<th scope="col" class="l-ui-table__header-cell">Assistants</th>
|
|
14
|
+
<th scope="col" class="l-ui-table__header-cell--action">Actions</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
|
|
18
|
+
<tbody class="l-ui-table__body">
|
|
19
|
+
<% @personas.each do |persona| %>
|
|
20
|
+
<tr>
|
|
21
|
+
<th scope="row" class="l-ui-table__cell--primary">
|
|
22
|
+
<%= persona.name %>
|
|
23
|
+
</th>
|
|
24
|
+
<td class="l-ui-table__cell">
|
|
25
|
+
<%= truncate(persona.description, length: 60) %>
|
|
26
|
+
</td>
|
|
27
|
+
<td class="l-ui-table__cell">
|
|
28
|
+
<%= persona.assistants_count %>
|
|
29
|
+
</td>
|
|
30
|
+
<td class="l-ui-table__cell--action">
|
|
31
|
+
<%= link_to "Edit", layered_assistant.edit_persona_path(persona) %>
|
|
32
|
+
</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<% end %>
|
|
35
|
+
</tbody>
|
|
36
|
+
</table>
|
|
37
|
+
|
|
38
|
+
<div class="l-ui-utility--mt-lg">
|
|
39
|
+
<%= l_ui_pagy(@pagy) %>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<% if l_assistant_accessible? %>
|
|
10
10
|
<%= l_ui_navigation_item "Setup", layered_assistant.root_path %>
|
|
11
11
|
<%= l_ui_navigation_item "Providers", layered_assistant.providers_path %>
|
|
12
|
+
<%= l_ui_navigation_item "Personas", layered_assistant.personas_path %>
|
|
12
13
|
<%= l_ui_navigation_item "Assistants", layered_assistant.assistants_path %>
|
|
13
14
|
<%= l_ui_navigation_item "Conversations", layered_assistant.conversations_path %>
|
|
14
15
|
<% else %>
|
data/config/routes.rb
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreateLayeredAssistantPersonas < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :layered_assistant_personas, if_not_exists: true do |t|
|
|
4
|
+
t.string :uid, null: false, index: { unique: true }
|
|
5
|
+
t.references :owner, polymorphic: true
|
|
6
|
+
t.string :name, null: false
|
|
7
|
+
t.text :description
|
|
8
|
+
t.text :instructions
|
|
9
|
+
t.bigint :assistants_count, default: 0, null: false
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -33,10 +33,8 @@
|
|
|
33
33
|
# have access to current_user and other helpers. Return an ActiveRecord
|
|
34
34
|
# relation (e.g. model_class.where(...) or model_class.all).
|
|
35
35
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# - Layered::Assistant::Assistant (has polymorphic owner)
|
|
39
|
-
# - Layered::Assistant::Provider (has polymorphic owner)
|
|
36
|
+
# All engine models with a polymorphic owner association are passed through
|
|
37
|
+
# the scope block.
|
|
40
38
|
#
|
|
41
39
|
# Scope all owned resources to the current user:
|
|
42
40
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: layered-assistant-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- layered.ai
|
|
@@ -313,6 +313,7 @@ files:
|
|
|
313
313
|
- app/controllers/layered/assistant/models_controller.rb
|
|
314
314
|
- app/controllers/layered/assistant/panel/conversations_controller.rb
|
|
315
315
|
- app/controllers/layered/assistant/panel/messages_controller.rb
|
|
316
|
+
- app/controllers/layered/assistant/personas_controller.rb
|
|
316
317
|
- app/controllers/layered/assistant/providers_controller.rb
|
|
317
318
|
- app/controllers/layered/assistant/public/application_controller.rb
|
|
318
319
|
- app/controllers/layered/assistant/public/assistants_controller.rb
|
|
@@ -339,6 +340,7 @@ files:
|
|
|
339
340
|
- app/models/layered/assistant/conversation.rb
|
|
340
341
|
- app/models/layered/assistant/message.rb
|
|
341
342
|
- app/models/layered/assistant/model.rb
|
|
343
|
+
- app/models/layered/assistant/persona.rb
|
|
342
344
|
- app/models/layered/assistant/provider.rb
|
|
343
345
|
- app/services/layered/assistant/chunk_parser.rb
|
|
344
346
|
- app/services/layered/assistant/chunk_service.rb
|
|
@@ -349,6 +351,7 @@ files:
|
|
|
349
351
|
- app/services/layered/assistant/messages_service.rb
|
|
350
352
|
- app/services/layered/assistant/models/create_service.rb
|
|
351
353
|
- app/services/layered/assistant/response_timer.rb
|
|
354
|
+
- app/services/layered/assistant/system_prompt_service.rb
|
|
352
355
|
- app/services/layered/assistant/token_estimator.rb
|
|
353
356
|
- app/views/layered/assistant/assistants/_form.html.erb
|
|
354
357
|
- app/views/layered/assistant/assistants/edit.html.erb
|
|
@@ -362,7 +365,6 @@ files:
|
|
|
362
365
|
- app/views/layered/assistant/messages/_composer.html.erb
|
|
363
366
|
- app/views/layered/assistant/messages/_composer_fields.html.erb
|
|
364
367
|
- app/views/layered/assistant/messages/_message.html.erb
|
|
365
|
-
- app/views/layered/assistant/messages/_system_prompt.html.erb
|
|
366
368
|
- app/views/layered/assistant/messages/create.turbo_stream.erb
|
|
367
369
|
- app/views/layered/assistant/messages/index.html.erb
|
|
368
370
|
- app/views/layered/assistant/models/_form.html.erb
|
|
@@ -375,6 +377,10 @@ files:
|
|
|
375
377
|
- app/views/layered/assistant/panel/conversations/show.html.erb
|
|
376
378
|
- app/views/layered/assistant/panel/messages/_composer.html.erb
|
|
377
379
|
- app/views/layered/assistant/panel/messages/create.turbo_stream.erb
|
|
380
|
+
- app/views/layered/assistant/personas/_form.html.erb
|
|
381
|
+
- app/views/layered/assistant/personas/edit.html.erb
|
|
382
|
+
- app/views/layered/assistant/personas/index.html.erb
|
|
383
|
+
- app/views/layered/assistant/personas/new.html.erb
|
|
378
384
|
- app/views/layered/assistant/providers/_form.html.erb
|
|
379
385
|
- app/views/layered/assistant/providers/edit.html.erb
|
|
380
386
|
- app/views/layered/assistant/providers/index.html.erb
|
|
@@ -401,6 +407,9 @@ files:
|
|
|
401
407
|
- db/migrate/20260315000000_add_stopped_to_layered_assistant_messages.rb
|
|
402
408
|
- db/migrate/20260315100000_add_response_timing_to_layered_assistant_messages.rb
|
|
403
409
|
- db/migrate/20260317000000_normalise_provider_protocol_values.rb
|
|
410
|
+
- db/migrate/20260403000000_create_layered_assistant_personas.rb
|
|
411
|
+
- db/migrate/20260403000001_add_persona_to_layered_assistant_assistants.rb
|
|
412
|
+
- db/migrate/20260406000000_rename_system_prompt_to_instructions.rb
|
|
404
413
|
- lib/generators/layered/assistant/install_generator.rb
|
|
405
414
|
- lib/generators/layered/assistant/migrations_generator.rb
|
|
406
415
|
- lib/generators/layered/assistant/templates/initializer.rb
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
<% if conversation.assistant.system_prompt.present? %>
|
|
2
|
-
<div class="l-ui-message">
|
|
3
|
-
<div class="l-ui-message__bubble">
|
|
4
|
-
<div class="l-ui-message__author">System</div>
|
|
5
|
-
<div class="l-ui-message__body">
|
|
6
|
-
<%= conversation.assistant.system_prompt %>
|
|
7
|
-
</div>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
<% end %>
|