Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create new & manage Assistants #334

Merged
merged 88 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
92729a3
Encrypt the API keys for OpenAI and Anthropic
stephan-buckmaster Apr 16, 2024
bfd663e
Review comments: explain config.active_record.encryption.support_unen…
stephan-buckmaster Apr 18, 2024
c5cc86e
Merge latest from upstream, resolve merge conflict in db/schema.rb
stephan-buckmaster Apr 18, 2024
df65e2f
Merge from upstream
stephan-buckmaster Apr 19, 2024
f91da68
Restore original config/database.yml, remove .gitignore entry, and co…
stephan-buckmaster Apr 19, 2024
92024cf
Update README with respect to credentials.yml.enc, remove the sample …
stephan-buckmaster Apr 19, 2024
01c25cc
Merge branch 'main' into main
krschacht Apr 21, 2024
7f269e6
Merge branch 'upstream-main' -- (update for Render plan)
stephan-buckmaster Apr 24, 2024
89f80f7
Have keys related to active record encryption generated on Render via…
stephan-buckmaster Apr 24, 2024
51960d0
Have keys related to active record encryption generated on Render via…
stephan-buckmaster Apr 24, 2024
746d89f
Use an config/initializer for configuring active record encryption
stephan-buckmaster Apr 24, 2024
96e3340
Merge from upstream
stephan-buckmaster Apr 25, 2024
e26a5b1
Encrypt the email field of Person records also
stephan-buckmaster Apr 26, 2024
0349a83
Merge from upstream
stephan-buckmaster Apr 26, 2024
d1bf816
Merge upstream
stephan-buckmaster Apr 29, 2024
e5a2af1
cleanup migration
krschacht Apr 29, 2024
02cbed2
ensure encryption init happens with db:prepare
krschacht Apr 29, 2024
8490064
make docker entrypoint more resilient
krschacht Apr 29, 2024
5c571b5
Update readme and startup script
krschacht Apr 29, 2024
a52a289
fix whitespace
krschacht Apr 29, 2024
d99e387
update test runner
krschacht Apr 29, 2024
776ec26
Fix running tests in docker within dev
krschacht Apr 30, 2024
438bf8c
Add generation of master.key
krschacht Apr 30, 2024
6f2d938
simplify generation of ar encryption keys
krschacht Apr 30, 2024
77032c3
Merge upstream
stephan-buckmaster May 4, 2024
bac0e0d
Merge from upstream
stephan-buckmaster May 7, 2024
a178675
Allow adding new assistants
stephan-buckmaster May 8, 2024
06de134
Load all assistants when editing, only show 5 initially
stephan-buckmaster May 10, 2024
d919d6e
Rename ai_api_models table to llms
stephan-buckmaster May 10, 2024
dad33d5
Use label Custom Instructions in the Assistant form
stephan-buckmaster May 10, 2024
a299cb1
Less clumsy way to keep additional assistants shown when editing one …
stephan-buckmaster May 10, 2024
b3edb57
Add back the New Assistant link
stephan-buckmaster May 10, 2024
f525b96
Fix the collapse mechanism
stephan-buckmaster May 10, 2024
f33f388
Refactor display of Assistant section under Settings
stephan-buckmaster May 12, 2024
808285e
Have assistant list truncated in message view also (but there is no n…
stephan-buckmaster May 12, 2024
77dcefb
Rename the LLM model (all-caps identifiers are usually constants) to …
stephan-buckmaster May 12, 2024
de6c887
Update list of language models, Assistant model belongs_to LanguageMo…
stephan-buckmaster May 13, 2024
8b98bfa
Fix tests until question of adding language_model_id to assistants ta…
stephan-buckmaster May 13, 2024
d749276
Merge from upstream
stephan-buckmaster May 14, 2024
c7c0bd3
Tests are passing, added language_models fixtures which are a bit ra…
stephan-buckmaster May 15, 2024
c3d641d
Merge upstream
stephan-buckmaster May 15, 2024
812a6c0
Merge upstream
stephan-buckmaster May 15, 2024
ffe6de5
Merge branch 'main' into 308-manage-assistants
stephan-buckmaster May 15, 2024
bb32de5
Update assistants/language_models link, add LanguageModel constants a…
stephan-buckmaster May 15, 2024
abf9cf5
Fix system tests
stephan-buckmaster May 16, 2024
668b94c
Fixed migration, also left model column in the runs table
stephan-buckmaster May 16, 2024
6f3fbd0
Populate runs.model from the name of the assistant's language_model
stephan-buckmaster May 16, 2024
3ce4368
Fix rubocop issues
stephan-buckmaster May 16, 2024
5671754
Soft-deletion of assistants
stephan-buckmaster May 16, 2024
c0ab70d
Let's have an explicit position column for ordering
krschacht May 19, 2024
58d5650
Add association + updates fixtures do ref by name
krschacht May 19, 2024
2226188
Improve styling of external assistant list
krschacht May 19, 2024
1960c30
Remove explicit ref to LLM id's to be less brittle
krschacht May 19, 2024
958bebc
Tweak styling of the assistants edit page
krschacht May 19, 2024
594bc97
Remove assistant overflow on settings since it's not easy to simplify
krschacht May 19, 2024
c5eec77
Simplify conditional logic for convo's with deleted assistants
krschacht May 19, 2024
eece44d
Remove helper methods which get are already added automatically
krschacht May 19, 2024
2da5b5a
Slightly simplify logic w/ provider_name
krschacht May 19, 2024
1db3248
Ensure all assistants (incl deleted) are destroyed w/ a user
krschacht May 19, 2024
de75b25
Merge upstream
stephan-buckmaster May 20, 2024
d8cc7e2
Merge upstream into 308-manage-assistants
stephan-buckmaster May 20, 2024
6d8a141
Merge upstream into 308-manage-assistants
stephan-buckmaster May 20, 2024
9b0a31c
Review comments, fixing code + allowing dev to choose development/tes…
stephan-buckmaster May 21, 2024
ccbc7e4
Allowing user.destroy to work as previously
stephan-buckmaster May 21, 2024
02bd81e
Test updates round 1
stephan-buckmaster May 22, 2024
6c2d447
Tests for what gets sent to the API client libraries
stephan-buckmaster May 22, 2024
19b60a6
Test for message display when assistant is deleted
stephan-buckmaster May 22, 2024
5622e3b
Merge branch 'main' into pr/stephan-buckmaster/334
krschacht May 24, 2024
b80b060
Change how test stubbing works for OpenAI to detect model
krschacht May 24, 2024
88142ad
Some additional merge cleanup
krschacht May 24, 2024
f146f35
Correct an accidental hardcoding in string
krschacht May 24, 2024
c90adcd
Fix typos in README.md
stephan-buckmaster May 25, 2024
f8b6c4d
Assistants delegate supports_images? to their language model
stephan-buckmaster May 25, 2024
e49eef9
Ensured documents have assistant populated when available
stephan-buckmaster May 26, 2024
ea07426
Make sure documents.assistant_id is consistent with messages.assistan…
stephan-buckmaster May 26, 2024
97a4a2e
Merge from upstream
stephan-buckmaster May 26, 2024
d55ad9d
Remove trailing whitespace+accidental comment
stephan-buckmaster May 26, 2024
228f5a0
Merge branch 'main' into pr/stephan-buckmaster/334
krschacht May 27, 2024
cd612b8
Readme
krschacht May 27, 2024
e03f125
Complete an initial pass of tests with some cleanup
krschacht May 27, 2024
5b1a89c
Finish reviewing tests & ensuring all pass
krschacht May 27, 2024
27ec521
Update migrations
krschacht May 27, 2024
1d9f45c
database yml
krschacht May 27, 2024
3f1595e
Revise some settings view rendering logic
krschacht May 27, 2024
efaaf3b
Remove attr
krschacht May 27, 2024
c23297d
Remove some unused code
krschacht May 28, 2024
5f7a0db
Final tweaks
krschacht May 28, 2024
7c95ee3
restore database.yml
krschacht May 28, 2024
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ If you encountered an error while waiting for the services to be deployed on Ren

# Deploy the app on Fly

Deploying to Fly.io is another great option. It's not quite one-click like Render and it's not 100% free. But we've made the configuration really easy for you and the cost should be about $2 per month, and Render costs $7 per month after 90 days of free servie so Fly is actually less expensive over the long term.
Deploying to Fly.io is another great option. It's not quite one-click like Render and it's not 100% free. But we've made the configuration really easy for you and the cost should be about $2 per month, and Render costs $7 per month after 90 days of free service so Fly is actually less expensive over the long term.

1. Click Fork > Create New Fork at the top of this repository. Pull your forked repository down to your computer (the usual git clone ...).
1. Install the Fly command-line tool [view instructions](https://fly.io/docs/hands-on/install-flyctl/)
1. In the root directory of the repoistory you pulled down, run `fly launch --build-only` and say `Yes` to copy the existing fly.toml, but note that it will generate the wrong settings.
1. In the root directory of the repository you pulled down, run `fly launch --build-only` and say `Yes` to copy the existing fly.toml, but note that it will generate the wrong settings.
1. **The settings it shows are INCORRECT** so tell it you want to make changes
1. When it opens your browser, change the Database to `Fly Postgres` with a name such as `hostedgpt-db` and you can set the configuration to `Development`.
1. Click `Confirm Settings` at the bottom of the page and close the browser.
Expand All @@ -81,7 +81,7 @@ Deploying to Fly.io is another great option. It's not quite one-click like Rende

# Deploy the app on Heroku

Heroku is a one-click option that will cost $10/monnth for the compute (dyno) and database. By default, apps use Eco dynos ($5) if you are subscribed to Eco. Otherwise, it defaults to Basic dynos ($7). The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Eco dynos "sleep" after 30 minutes of inactivity and take a few seconds to wake up. Basic dynos do not sleep.
Heroku is a one-click option that will cost $10/month for the compute (dyno) and database. By default, apps use Eco dynos ($5) if you are subscribed to Eco. Otherwise, it defaults to Basic dynos ($7). The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Eco dynos "sleep" after 30 minutes of inactivity and take a few seconds to wake up. Basic dynos do not sleep.

Eligible students can apply for Heroku platform credits through [Heroku for GitHub Students program](https://blog.heroku.com/github-student-developer-program).

Expand All @@ -94,7 +94,7 @@ Eligible students can apply for Heroku platform credits through [Heroku for GitH

# Contribute as a developer

We welcome contributors! After you get your developoment environment setup, review the list of Issues. We organize the issues into Milestones and are currently working on v0.7. [View 0.7 Milestone](https://github.com/allyourbot/hostedgpt/milestone/6). Look for any issues tagged with **Good first issue** and add a comment so we know you're working on it.
We welcome contributors! After you get your development environment setup, review the list of Issues. We organize the issues into Milestones and are currently working on v0.7. [View 0.7 Milestone](https://github.com/allyourbot/hostedgpt/milestone/6). Look for any issues tagged with **Good first issue** and add a comment so we know you're working on it.

## Setting up development

Expand Down
4 changes: 2 additions & 2 deletions app/controllers/assistants_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def update
end

def destroy
@assistant.destroy!
@assistant.soft_delete
redirect_to assistants_url, notice: "Assistant was successfully destroyed.", status: :see_other
end

Expand All @@ -46,6 +46,6 @@ def set_assistant
end

def assistant_params
params.require(:assistant).permit(:user_id, :model, :name, :description, :instructions, :tools)
params.require(:assistant).permit(:user_id, :language_model_id, :name, :description, :instructions, :tools)
end
end
4 changes: 1 addition & 3 deletions app/controllers/messages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ def create
end

def update
# Clicking edit beneath a message actually submits to create and not here. This action is only used for next/prev conversation.
# In order to force a morph we PATCH to here and redirect.
if @message.update(message_params)
redirect_to conversation_messages_path(@message.conversation, version: @version || @message.version)
else
Expand All @@ -76,7 +74,7 @@ def set_conversation
end

def set_assistant
@assistant = Current.user.assistants.find_by(id: params[:assistant_id])
@assistant = Current.user.assistants_including_deleted.find_by(id: params[:assistant_id])
@assistant ||= @conversation.latest_message_for_version(@version).assistant
end

Expand Down
11 changes: 6 additions & 5 deletions app/controllers/settings/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ class Settings::ApplicationController < ApplicationController
def set_settings_menu
# controller_name => array of items
@settings_menu = {
people: {
"Your Account": edit_settings_person_path
},

assistants: Current.user.assistants.ordered.map {
|assistant| [ assistant, edit_settings_assistant_path(assistant) ]
}.to_h.merge({
#'New Assistant': new_settings_assistant_path(assistant)
}),
"New Assistant": new_settings_assistant_path
})

people: {
'Account': edit_settings_person_path
}
}
end
end
19 changes: 15 additions & 4 deletions app/controllers/settings/assistants_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Settings::AssistantsController < Settings::ApplicationController
before_action :set_assistant, only: [:edit, :update, :destroy]
before_action :set_last_assistant, except: [:destroy]

def new
@assistant = Assistant.new
Expand Down Expand Up @@ -27,17 +28,27 @@ def update
end

def destroy
@assistant.destroy!
redirect_to new_settings_assistant_url, notice: "Deleted", status: :see_other
if @assistant.soft_delete
redirect_to new_settings_assistant_url, notice: "Deleted", status: :see_other
else
redirect_to new_settings_assistant_url, alert: "Cannot delete your last assistant", status: :see_other
end
end

private

def set_assistant
@assistant = Current.user.assistants.find(params[:id])
@assistant = Current.user.assistants.find_by(id: params[:id])
if @assistant.nil?
redirect_to new_settings_assistant_url, notice: "The assistant was deleted", status: :see_other
end
end

def set_last_assistant
@last_assistant = Current.user.assistants.count <= 1
end

def assistant_params
params.require(:assistant).permit(:name, :description, :instructions)
params.require(:assistant).permit(:name, :description, :instructions, :language_model_id)
end
end
6 changes: 1 addition & 5 deletions app/jobs/get_next_ai_message_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ class WaitForPrevious < StandardError; end
retry_on WaitForPrevious, wait: ->(run) { (2**run - 1).seconds }, attempts: 3

def ai_backend
if @assistant.model.starts_with?('gpt-')
AIBackend::OpenAI
else
AIBackend::Anthropic
end
@assistant.language_model.ai_backend
end

def perform(user_id, message_id, assistant_id, attempt = 1)
Expand Down
19 changes: 18 additions & 1 deletion app/models/assistant.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
class Assistant < ApplicationRecord
MAX_LIST_DISPLAY = 5

belongs_to :user

has_many :conversations, dependent: :destroy
has_many :documents, dependent: :destroy
has_many :runs, dependent: :destroy
has_many :steps, dependent: :destroy
has_many :messages # TODO: What should happen if an assistant is deleted?
has_many :messages, dependent: :destroy

delegate :supports_images?, to: :language_model

belongs_to :language_model

validates :tools, presence: true, allow_blank: true
validates :name, presence: true

scope :ordered, -> { order(:id) }

Expand All @@ -20,6 +27,16 @@ def initials
parts[1]&.try(:[], 0)&.capitalize.to_s
end

def soft_delete
return false if user.assistants.count <= 1
update!(deleted_at: Time.now)
return true
end

def soft_delete!
raise "Can't delete user's last assistant" if !soft_delete
end

def to_s
name
end
Expand Down
8 changes: 4 additions & 4 deletions app/models/concerns/user/registerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ module User::Registerable
private

def create_initial_assistants
assistants.create! name: "GPT-4", model: "gpt-4o-2024-05-13", images: true
assistants.create! name: "GPT-3.5", model: "gpt-3.5-turbo-0125", images: false
assistants.create! name: "Claude 3 Opus", model: "claude-3-opus-20240229", images: true
assistants.create! name: "Claude 3 Sonnet", model: "claude-3-sonnet-20240229", images: true
assistants.create! name: "GPT-4o", language_model: LanguageModel.find_by(name: 'gpt-4o')
assistants.create! name: "GPT-3.5", language_model: LanguageModel.find_by(name: 'gpt-3.5-turbo')
assistants.create! name: "Claude 3 Opus", language_model: LanguageModel.find_by(name: 'claude-3-opus-20240229')
assistants.create! name: "Claude 3 Sonnet", language_model: LanguageModel.find_by(name: 'claude-3-sonnet-20240229')
end
end
27 changes: 27 additions & 0 deletions app/models/language_model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# We don't care about large or not
class LanguageModel < ApplicationRecord
BEST_MODELS = {
'gpt-best' => 'gpt-4o-2024-05-13',
'claude-best' => 'claude-3-opus-20240229'
}

scope :ordered, -> { order(:position) }

has_many :assistants

def readonly?
!new_record?
end

def provider_name
BEST_MODELS[name] || name
end

def ai_backend
if name.starts_with?('gpt-')
AIBackend::OpenAI
else
AIBackend::Anthropic
end
end
end
7 changes: 7 additions & 0 deletions app/models/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ class Run < ApplicationRecord

enum status: %w[queued in_progress requires_action cancelling cancelled failed completed expired].index_by(&:to_sym)

before_validation :set_model, on: :create
validates :status, :expired_at, :model, :instructions, presence: true
validates :tools, :file_ids, presence: true, allow_blank: true

private

def set_model
self.model = assistant&.language_model&.name
end
end
16 changes: 15 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ class User < ApplicationRecord
validates :first_name, presence: true
validates :last_name, presence: true, on: :create

has_many :assistants, dependent: :destroy
has_many :assistants, -> { not_deleted }
has_many :assistants_including_deleted, class_name: "Assistant", dependent: :destroy
has_many :conversations, dependent: :destroy
belongs_to :last_cancelled_message, class_name: "Message", optional: true

Expand All @@ -19,4 +20,17 @@ class User < ApplicationRecord
def preferences
attributes["preferences"].with_defaults(dark_mode: "system")
end

def destroy_in_progress?
@destroy_in_progress
end

def destroy
@destroy_in_progress = true
begin
super
ensure
@destroy_in_progress = false
end
end
end
4 changes: 2 additions & 2 deletions app/services/ai_backend/anthropic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def get_next_chat_message(&chunk_received_handler)

begin
response = @client.messages(
model: @assistant.model,
model: @assistant.language_model.provider_name,
system: @assistant.instructions,
messages: preceding_messages,
parameters: {
Expand Down Expand Up @@ -76,7 +76,7 @@ def get_next_chat_message(&chunk_received_handler)

def preceding_messages
@conversation.messages.for_conversation_version(@message.version).where("messages.index < ?", @message.index).collect do |message|
if @assistant.images && message.documents.present?
if @assistant.supports_images? && message.documents.present?

content = [{ type: "text", text: message.content_text }]
content += message.documents.collect do |document|
Expand Down
4 changes: 2 additions & 2 deletions app/services/ai_backend/open_ai.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def get_next_chat_message(&chunk_handler)

begin
response = @client.chat(parameters: {
model: @assistant.model,
model: @assistant.language_model.provider_name,
messages: system_message + preceding_messages,
tools: Toolbox.tools,
stream: response_handler,
Expand Down Expand Up @@ -124,7 +124,7 @@ def system_message

def preceding_messages
@conversation.messages.for_conversation_version(@message.version).where("messages.index < ?", @message.index).collect do |message|
if @assistant.images && message.documents.present?
if @assistant.supports_images? && message.documents.present?

content_with_images = [{ type: "text", text: message.content_text }]
content_with_images += message.documents.collect do |document|
Expand Down
10 changes: 6 additions & 4 deletions app/services/test_client/anthropic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ def self.text
#
# Stub this method to respond with something more specific if needed.
def messages(**args)
model = args.dig(:model) || "no model"
system_message = args.dig(:system)
if proc = args.dig(:parameters, :stream)
proc.call({
"id"=>"msg_01LtHY4sJVd7WBdPCsYb8kHQ",
"type"=>"message",
"role"=>"assistant",
"delta"=>
{"type"=>"text",
"text"=> self.class.text || "Hello! It's nice to meet you. How can I assist you today?"},
"model"=>"claude-3-opus-20240229",
"text"=> self.class.text || "Hello this is model #{model} with instruction #{system_message.to_s.inspect}! How can I assist you today?"},
"model"=>model,
"stop_reason"=>"end_turn",
"stop_sequence"=>nil,
"usage"=>{"input_tokens"=>10, "output_tokens"=>19}
Expand All @@ -30,8 +32,8 @@ def messages(**args)
"role"=>"assistant",
"content"=>
[{"type"=>"text",
"text"=> self.class.text || "Hello! It's nice to meet you. How can I assist you today?"}],
"model"=>"claude-3-opus-20240229",
"text"=> self.class.text || "Hello this is model #{model} with instruction #{system_message.to_s.inspect}! How can I assist you today?"}],
"model"=> model,
"stop_reason"=>"end_turn",
"stop_sequence"=>nil,
"usage"=>{"input_tokens"=>10, "output_tokens"=>19}
Expand Down
10 changes: 8 additions & 2 deletions app/services/test_client/open_ai.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,22 @@ def self.text
raise "When using the OpenAI test client for api_text_response you need to stub the .text method"
end

def self.default_text
"Hello this is model #{@@model} with instruction nil! How can I assist you today?"
end

def self.api_text_response
{
"id"=> "chatcmpl-abc123abc123abc123abc123abc12",
"object"=>"chat.completion",
"created"=>1707429030,
"model"=>"gpt-3.5-turbo-0613",
"model"=> @@model,
"choices"=> [
{
"index"=>0,
"delta"=>{
"role"=>"assistant",
"content"=> text
"content"=> text || default_text
},
"logprobs"=>nil,
"finish_reason"=>"stop"
Expand Down Expand Up @@ -63,6 +67,8 @@ def self.api_function_response
end

def chat(**args)
@@model = args.dig(:parameters, :model) || "no model"

proc = args.dig(:parameters, :stream)
raise "No stream proc provided. When calling get_next_chat_message in tests be sure to include a block" if proc.nil?
proc.call(self.class.api_response)
Expand Down
5 changes: 4 additions & 1 deletion app/views/assistants/_assistant.html.erb
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
<%# locals: (assistant:, settings: true, assistant_counter: -1) %>
<% selected = assistant == @assistant %>
<% first = assistant_counter.zero? %>
<% visible = assistant_counter <= Assistant::MAX_LIST_DISPLAY-1 || selected %>

<div class="bg-gray-50 dark:bg-gray-900 <%= first && 'absolute top-[17px] left-0 pl-3 w-full z-10' %>">
<%# This extra div ^ is needed because of the absolute positioning. It doesn't lay out properly if added to the div below. %>
<div class="
flex justify-between items-center
mb-1 p-1 pl-2 pr-2 mr-5
hover:bg-gray-100 dark:hover:bg-gray-700
bg-gray-50 dark:bg-transparent
bg-gray-50 dark:bg-transparent
dark:bg-gray-700
group cursor-pointer
text-sm rounded-lg
<%= selected && 'relationship' %>
<%= !visible && 'hidden' %>
"
data-role="assistant"
data-radio-behavior-target="radio"
data-action="radio-changed@window->radio-behavior#select"
data-radio-behavior-id-param="<%= assistant.id %>"
data-transition-target="<%= !visible && 'transitionable' %>"
>
<%= link_to new_assistant_message_path(assistant), class: "flex-1 flex items-center text-gray-950 dark:text-gray-100 font-medium truncate", data: { role: "name" } do %>
<%= render partial: "layouts/assistant_avatar", locals: { assistant: assistant, size: 7, classes: "mr-2" } %>
Expand Down
Loading
Loading