Add tenant to URLs

This commit is contained in:
Stanko K.R.
2025-08-11 18:57:00 +02:00
parent 7603d0bbcf
commit 51f6326404
7 changed files with 80 additions and 106 deletions
+5 -1
View File
@@ -3,6 +3,10 @@ class Ai::Tool < RubyLLM::Tool
private
def default_url_options
Rails.application.default_url_options
Rails.application.default_url_options.merge(
script_name: "/#{ApplicationRecord.current_tenant}",
host: "fizzy.localhost",
port: 3006
)
end
end
+15 -3
View File
@@ -15,7 +15,7 @@ class Ai::Tool::ListCards < Ai::Tool
}
}
```
Each collection object has the following fields:
Each card object has the following fields:
- id [Integer, not null]
- title [String, not null] - The title of the card
- status [String, not null] - Enum of "creating", "draft" and "published"
@@ -69,8 +69,14 @@ class Ai::Tool::ListCards < Ai::Tool
desc: "If provided, will return only card that were last active on or before the given ISO timestamp",
required: false
attr_reader :user
def initialize(user:)
@user = user
end
def execute(**params)
cards = Card.all.includes(:stage, :creator, :assignees, :goldness)
cards = Card.where(collection: user.collections).includes(:stage, :creator, :assignees, :goldness)
cards = cards.search(params[:query]) if params[:query].present?
cards = cards.golden if params[:golden].present?
@@ -99,6 +105,12 @@ class Ai::Tool::ListCards < Ai::Tool
page = GearedPagination::Recordset.new(cards, ordered_by: { id: :desc }).page(params[:page])
puts "="*80
puts "Account: #{Account.sole.id}"
puts "Tenant: #{ApplicationRecord.current_tenant}"
puts "URL options: #{default_url_options.inspect}"
puts "="*80
{
cards: page.records.map do |card|
{
@@ -112,7 +124,7 @@ class Ai::Tool::ListCards < Ai::Tool
creator: card.creator.as_json(only: [ :id, :name ]),
assignees: card.assignees.as_json(only: [ :id, :name ]),
description: card.description.to_plain_text.truncate(1000),
url: collection_card_path(card.collection, card)
url: collection_card_url(card.collection, card)
}
end,
pagination: {
+8 -2
View File
@@ -24,8 +24,14 @@ class Ai::Tool::ListCollections < Ai::Tool
desc: "Which page to return. Leave balnk to get the first page",
required: false
attr_reader :user
def initialize(user:)
@user = user
end
def execute(**params)
scope = Collection.all
scope = user.collections
page = GearedPagination::Recordset.new(
scope,
@@ -37,7 +43,7 @@ class Ai::Tool::ListCollections < Ai::Tool
{
id: collection.id,
name: collection.name,
url: collection_path(collection)
url: collection_url(collection)
}
end,
pagination: {
+34 -6
View File
@@ -5,7 +5,7 @@ class Ai::Tool::ListComments < Ai::Tool
Responses are JSON objects that look like this:
```
{
"collections": [
"comments": [
{
"id": 3,
"card_id": 5,
@@ -21,9 +21,18 @@ class Ai::Tool::ListComments < Ai::Tool
}
}
```
Each collection object has the following fields:
Each comment object has the following fields:
- id [Integer, not null]
- name [String, not null]
- card_id [Integer, not null]
- body [String, not null]
- created_at [String, not null] ISO8601 formatted timestamp
- creator [Object, not null] the User that created the comment
- system [Boolean, not null] indicates if the comment was created by the system
- reactions [Array]
- content [String, not null]
- reacter [Object] represents a User
- id [Integer, not null]
- name [String, not null]
MD
param :page,
@@ -38,6 +47,10 @@ class Ai::Tool::ListComments < Ai::Tool
type: :integer,
desc: "If provided, will return only status changes for the specified card",
required: false
param :type,
type: :string,
desc: "If provided, returns either 'user' or 'system' comments, if ommited it returns both",
required: false
param :created_at_gte,
type: :string,
desc: "If provided, will return only comments created on or after after the given ISO timestamp",
@@ -47,12 +60,25 @@ class Ai::Tool::ListComments < Ai::Tool
desc: "If provided, will return only comments created on or before the given ISO timestamp",
required: false
attr_reader :user
def initialize(user:)
@user = user
end
def execute(**params)
scope = Comment.all.includes(:card, :creator, reactions: [ :reacter ]).where.not(creator: { role: "system" })
cards = Card.where(collection: user.collections)
scope = Comment.where(card: cards).includes(:card, :creator, reactions: [ :reacter ])
scope = scope.search(params[:query]) if params[:query].present?
scope = scope.where(card_id: params[:card_id].to_i) if params[:card_id].present?
if params[:type]&.casecmp?("system")
scope = scope.where(creator: { role: "system" })
elsif params[:type]&.casecmp?("user")
scope = scope.where.not(creator: { role: "system" })
end
if params[:created_at_gte].present?
timestamp = Time.iso8601(params[:created_at_gte])
scope = scope.where(created_at: timestamp..)
@@ -69,19 +95,21 @@ class Ai::Tool::ListComments < Ai::Tool
).page(params[:page])
{
collections: page.records.map do |comment|
comments: page.records.map do |comment|
{
id: comment.id,
card_id: comment.card_id,
body: comment.body.to_plain_text,
created_at: comment.created_at.iso8601,
creator: comment.creator.as_json(only: [ :id, :name ]),
system: comment.creator.system?,
reactions: comment.reactions.map do |reaction|
{
content: reaction.content,
reacter: reaction.reacter.as_json(only: [ :id, :name ])
}
end
end,
url: collection_card_url(comment.card.collection_id, comment.card, anchor: "comment_#{comment.id}")
}
end,
pagination: {
-84
View File
@@ -1,84 +0,0 @@
class Ai::Tool::ListStatusChanges < Ai::Tool
description <<-MD
Lists all status changes accessible by the current user.
The response is paginated so you may need to iterate through multiple pages to get the full list.
Responses are JSON objects that look like this:
```
{
"collections": [
{
"id": 3,
"card_id": 5,
"body": "Jane Doe moved this to Done",
"created_at": "2023-10-01T12:00:00Z",
"creator": { "id": 2, "name": "Jane Doe" }
}
],
"pagination": {
"next_page": "e3c2gh75e4..."
}
}
```
Each collection object has the following fields:
- id [Integer, not null]
- name [String, not null]
MD
param :page,
type: :string,
desc: "Which page to return. Leave balnk to get the first page",
required: false
param :query,
type: :string,
desc: "If provided, will perform a semantinc search by embeddings and return only matching status changes",
required: false
param :card_id,
type: :integer,
desc: "If provided, will return only status changes for the specified card",
required: false
param :created_at_gte,
type: :string,
desc: "If provided, will return only comments created on or after after the given ISO timestamp",
required: false
param :created_at_lte,
type: :string,
desc: "If provided, will return only comments created on or before the given ISO timestamp",
required: false
def execute(**params)
scope = Comment.all.includes(:card, :creator).where(creator: { role: "system" })
scope = scope.search(params[:query]) if params[:query].present?
scope = scope.where(card_id: params[:card_id].to_i) if params[:card_id].present?
if params[:created_at_gte].present?
timestamp = Time.iso8601(params[:created_at_gte])
scope = scope.where(created_at: timestamp..)
end
if params[:created_at_lte].present?
timestamp = Time.iso8601(params[:created_at_lte])
scope = scope.where(created_at: ..timestamp)
end
page = GearedPagination::Recordset.new(
scope,
ordered_by: { created_at: :asc, id: :desc }
).page(params[:page])
{
collections: page.records.map do |comment|
{
id: comment.id,
card_id: comment.card_id,
body: comment.body.to_plain_text,
created_at: comment.created_at.iso8601,
creator: comment.creator.as_json(only: [ :id, :name ])
}
end,
pagination: {
next_page: page.next_param
}
}.to_json
end
end
+13 -4
View File
@@ -5,7 +5,7 @@ class Ai::Tool::ListUsers < Ai::Tool
Responses are JSON objects that look like this:
```
{
"collections": [
"users": [
{ "id": 3, "name": "John Doe" },
{ "id": 4, "name": "Johanna Doe" }
],
@@ -14,9 +14,11 @@ class Ai::Tool::ListUsers < Ai::Tool
}
}
```
Each collection object has the following fields:
Each user object has the following fields:
- id [Integer, not null]
- name [String, not null]
- role [String, not null]
- url [String, not null]
MD
param :page,
@@ -28,6 +30,12 @@ class Ai::Tool::ListUsers < Ai::Tool
desc: "If provided, will return only the users with the given IDs (comma-separated)",
required: false
attr_reader :user
def initialize(user:)
@user = user
end
def execute(**params)
scope = User.all
@@ -39,11 +47,12 @@ class Ai::Tool::ListUsers < Ai::Tool
).page(params[:page])
{
collections: page.records.map do |user|
users: page.records.map do |user|
{
id: user.id,
name: user.name,
url: user_path(user)
role: user.role,
url: user_url(user)
}
end,
pagination: {
@@ -25,7 +25,7 @@ class Conversation::Message::ResponseGenerator
- Respond in **Markdown**
- Always include links to relevant cards, collections, comments, or users
Remember, you're here to help not to anticipate.
You're here to help not to anticipate.
PROMPT
attr_reader :message, :prompt, :llm_model
@@ -62,11 +62,10 @@ class Conversation::Message::ResponseGenerator
def llm
RubyLLM.chat(model: llm_model).tap do |chat|
chat.with_tool(Ai::Tool::ListCards.new)
chat.with_tool(Ai::Tool::ListCollections.new)
chat.with_tool(Ai::Tool::ListComments.new)
chat.with_tool(Ai::Tool::ListStatusChanges.new)
chat.with_tool(Ai::Tool::ListUsers.new)
chat.with_tool(Ai::Tool::ListCards.new(user: message.owner))
chat.with_tool(Ai::Tool::ListCollections.new(user: message.owner))
chat.with_tool(Ai::Tool::ListComments.new(user: message.owner))
chat.with_tool(Ai::Tool::ListUsers.new(user: message.owner))
chat.reset_messages!