aven 0.0.3
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/MIT-LICENSE +20 -0
- data/README.md +35 -0
- data/Rakefile +19 -0
- data/app/assets/stylesheets/aven/application.css +14 -0
- data/app/assets/stylesheets/aven/application.tailwind.css +7 -0
- data/app/assets/stylesheets/aven/tailwind.css +224 -0
- data/app/channels/aven/chat/thread_channel.rb +39 -0
- data/app/components/aven/application_view_component.rb +15 -0
- data/app/components/aven/views/admin/dashboard/index/component.html.erb +1 -0
- data/app/components/aven/views/admin/dashboard/index/component.rb +5 -0
- data/app/components/aven/views/articles/edit/component.html.erb +14 -0
- data/app/components/aven/views/articles/edit/component.rb +14 -0
- data/app/components/aven/views/articles/form/component.html.erb +45 -0
- data/app/components/aven/views/articles/form/component.rb +27 -0
- data/app/components/aven/views/articles/index/component.html.erb +93 -0
- data/app/components/aven/views/articles/index/component.rb +29 -0
- data/app/components/aven/views/articles/new/component.html.erb +13 -0
- data/app/components/aven/views/articles/new/component.rb +14 -0
- data/app/components/aven/views/articles/show/component.html.erb +110 -0
- data/app/components/aven/views/articles/show/component.rb +34 -0
- data/app/components/aven/views/oauth/error/component.html.erb +44 -0
- data/app/components/aven/views/oauth/error/component.rb +30 -0
- data/app/components/aven/views/static/index/component.html.erb +17 -0
- data/app/components/aven/views/static/index/component.rb +16 -0
- data/app/components/aven/views/static/index/controller.js +7 -0
- data/app/controllers/aven/admin/base.rb +16 -0
- data/app/controllers/aven/admin/dashboard_controller.rb +9 -0
- data/app/controllers/aven/agentic/agents_controller.rb +56 -0
- data/app/controllers/aven/agentic/documents_controller.rb +51 -0
- data/app/controllers/aven/agentic/mcp_controller.rb +124 -0
- data/app/controllers/aven/agentic/tools_controller.rb +37 -0
- data/app/controllers/aven/ai/text_controller.rb +41 -0
- data/app/controllers/aven/application_controller.rb +27 -0
- data/app/controllers/aven/articles_controller.rb +114 -0
- data/app/controllers/aven/auth_controller.rb +12 -0
- data/app/controllers/aven/chat/threads_controller.rb +67 -0
- data/app/controllers/aven/oauth/auth0_controller.rb +84 -0
- data/app/controllers/aven/oauth/base_controller.rb +183 -0
- data/app/controllers/aven/oauth/documentation/auth0.md +387 -0
- data/app/controllers/aven/oauth/documentation/entra_id.md +608 -0
- data/app/controllers/aven/oauth/documentation/github.md +329 -0
- data/app/controllers/aven/oauth/documentation/google.md +253 -0
- data/app/controllers/aven/oauth/entra_id_controller.rb +92 -0
- data/app/controllers/aven/oauth/github_controller.rb +91 -0
- data/app/controllers/aven/oauth/google_controller.rb +64 -0
- data/app/controllers/aven/static_controller.rb +7 -0
- data/app/controllers/aven/tags_controller.rb +44 -0
- data/app/controllers/aven/workspaces_controller.rb +20 -0
- data/app/controllers/concerns/aven/authentication.rb +49 -0
- data/app/controllers/concerns/aven/controller_helpers.rb +38 -0
- data/app/helpers/aven/application_helper.rb +16 -0
- data/app/javascript/aven/application.js +3 -0
- data/app/javascript/aven/controllers/application.js +5 -0
- data/app/javascript/aven/controllers/index.js +11 -0
- data/app/jobs/aven/agentic/document_embedding_job.rb +28 -0
- data/app/jobs/aven/agentic/document_ocr_job.rb +28 -0
- data/app/jobs/aven/application_job.rb +4 -0
- data/app/jobs/aven/chat/calculate_cost_job.rb +26 -0
- data/app/jobs/aven/chat/run_job.rb +27 -0
- data/app/mailers/aven/application_mailer.rb +6 -0
- data/app/models/aven/agentic/agent.rb +76 -0
- data/app/models/aven/agentic/agent_document.rb +37 -0
- data/app/models/aven/agentic/agent_tool.rb +37 -0
- data/app/models/aven/agentic/document.rb +162 -0
- data/app/models/aven/agentic/document_embedding.rb +39 -0
- data/app/models/aven/agentic/tool.rb +106 -0
- data/app/models/aven/agentic/tool_parameter.rb +56 -0
- data/app/models/aven/application_record.rb +5 -0
- data/app/models/aven/article.rb +86 -0
- data/app/models/aven/article_attachment.rb +18 -0
- data/app/models/aven/article_relationship.rb +26 -0
- data/app/models/aven/chat/message.rb +135 -0
- data/app/models/aven/chat/thread.rb +159 -0
- data/app/models/aven/import/entry.rb +45 -0
- data/app/models/aven/import/item_link.rb +36 -0
- data/app/models/aven/import/processor.rb +123 -0
- data/app/models/aven/import.rb +102 -0
- data/app/models/aven/item/embed.rb +54 -0
- data/app/models/aven/item/embeddable.rb +141 -0
- data/app/models/aven/item/linkable.rb +212 -0
- data/app/models/aven/item/schema/builder.rb +139 -0
- data/app/models/aven/item/schemaed.rb +252 -0
- data/app/models/aven/item/schemas/base.rb +108 -0
- data/app/models/aven/item.rb +128 -0
- data/app/models/aven/item_link.rb +43 -0
- data/app/models/aven/item_schema.rb +87 -0
- data/app/models/aven/log.rb +66 -0
- data/app/models/aven/loggable.rb +20 -0
- data/app/models/aven/user.rb +40 -0
- data/app/models/aven/workspace.rb +93 -0
- data/app/models/aven/workspace_role.rb +46 -0
- data/app/models/aven/workspace_user.rb +54 -0
- data/app/models/aven/workspace_user_role.rb +38 -0
- data/app/models/concerns/aven/agentic/document_embeddable.rb +58 -0
- data/app/models/concerns/aven/searchable.rb +61 -0
- data/app/services/aven/agentic/dynamic_tool_builder.rb +81 -0
- data/app/services/aven/agentic/mcp/adapter.rb +77 -0
- data/app/services/aven/agentic/mcp/result_formatter.rb +57 -0
- data/app/services/aven/agentic/mcp/server_factory.rb +43 -0
- data/app/services/aven/agentic/ocr/base_extractor.rb +39 -0
- data/app/services/aven/agentic/ocr/excel_extractor.rb +43 -0
- data/app/services/aven/agentic/ocr/image_extractor.rb +22 -0
- data/app/services/aven/agentic/ocr/pdf_extractor.rb +48 -0
- data/app/services/aven/agentic/ocr/processor.rb +36 -0
- data/app/services/aven/agentic/ocr/textract_client.rb +131 -0
- data/app/services/aven/agentic/ocr/word_extractor.rb +34 -0
- data/app/services/aven/agentic/tool_result_formatter.rb +76 -0
- data/app/services/aven/agentic/tools/base.rb +55 -0
- data/app/services/aven/agentic/tools/concerns/boolean_filtering.rb +40 -0
- data/app/services/aven/agentic/tools/concerns/enum_filtering.rb +47 -0
- data/app/services/aven/agentic/tools/concerns/geo_filtering.rb +56 -0
- data/app/services/aven/agentic/tools/concerns/range_filtering.rb +51 -0
- data/app/services/aven/chat/broadcaster.rb +59 -0
- data/app/services/aven/chat/config.rb +93 -0
- data/app/services/aven/chat/message_builder.rb +42 -0
- data/app/services/aven/chat/orchestrator.rb +69 -0
- data/app/services/aven/chat/runner.rb +105 -0
- data/app/services/aven/chat/title_generator.rb +61 -0
- data/app/services/aven/external/gmail_client.rb +173 -0
- data/app/services/aven/external/google_contacts_client.rb +95 -0
- data/app/views/layouts/aven/admin.html.erb +16 -0
- data/app/views/layouts/aven/application.html.erb +18 -0
- data/config/importmap.rb +16 -0
- data/config/routes.rb +63 -0
- data/db/migrate/20200101000001_create_aven_users.rb +19 -0
- data/db/migrate/20200101000002_create_aven_workspaces.rb +14 -0
- data/db/migrate/20200101000003_create_aven_workspace_users.rb +12 -0
- data/db/migrate/20200101000004_create_aven_workspace_roles.rb +13 -0
- data/db/migrate/20200101000005_create_aven_workspace_user_roles.rb +12 -0
- data/db/migrate/20200101000006_create_aven_logs.rb +21 -0
- data/db/migrate/20200101000009_create_aven_items.rb +17 -0
- data/db/migrate/20200101000010_create_aven_item_links.rb +17 -0
- data/db/migrate/20200101000011_create_aven_agentic_tools.rb +19 -0
- data/db/migrate/20200101000012_create_aven_agentic_tool_parameters.rb +20 -0
- data/db/migrate/20200101000013_create_aven_agentic_documents.rb +22 -0
- data/db/migrate/20200101000014_create_aven_agentic_document_embeddings.rb +18 -0
- data/db/migrate/20200101000015_create_aven_agentic_agents.rb +18 -0
- data/db/migrate/20200101000016_create_aven_agentic_agent_tools.rb +13 -0
- data/db/migrate/20200101000017_create_aven_agentic_agent_documents.rb +13 -0
- data/db/migrate/20200101000018_create_aven_chat_threads.rb +19 -0
- data/db/migrate/20200101000019_create_aven_chat_messages.rb +26 -0
- data/db/migrate/20200101000020_add_pg_search_support.rb +21 -0
- data/db/migrate/20200101000021_create_aven_item_schemas.rb +18 -0
- data/db/migrate/20200101000022_create_aven_imports.rb +23 -0
- data/db/migrate/20200101000023_create_aven_import_entries.rb +13 -0
- data/db/migrate/20200101000024_create_aven_import_item_links.rb +13 -0
- data/db/migrate/20200101000025_create_aven_articles.rb +19 -0
- data/db/migrate/20200101000026_create_aven_article_attachments.rb +13 -0
- data/db/migrate/20200101000027_create_aven_article_relationships.rb +15 -0
- data/lib/aven/configuration.rb +87 -0
- data/lib/aven/engine.rb +43 -0
- data/lib/aven/model/tenant_model.rb +91 -0
- data/lib/aven/model.rb +6 -0
- data/lib/aven/version.rb +3 -0
- data/lib/aven.rb +8 -0
- data/lib/tasks/annotate_rb.rake +10 -0
- data/lib/tasks/aven_tasks.rake +21 -0
- metadata +426 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_agentic_documents
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# byte_size :bigint not null
|
|
9
|
+
# content_type :string not null
|
|
10
|
+
# embedding_status :string default("pending"), not null
|
|
11
|
+
# filename :string not null
|
|
12
|
+
# metadata :jsonb
|
|
13
|
+
# ocr_content :text
|
|
14
|
+
# ocr_status :string default("pending"), not null
|
|
15
|
+
# processed_at :datetime
|
|
16
|
+
# created_at :datetime not null
|
|
17
|
+
# updated_at :datetime not null
|
|
18
|
+
# workspace_id :bigint not null
|
|
19
|
+
#
|
|
20
|
+
# Indexes
|
|
21
|
+
#
|
|
22
|
+
# index_aven_agentic_documents_on_content_type (content_type)
|
|
23
|
+
# index_aven_agentic_documents_on_embedding_status (embedding_status)
|
|
24
|
+
# index_aven_agentic_documents_on_ocr_status (ocr_status)
|
|
25
|
+
# index_aven_agentic_documents_on_workspace_id (workspace_id)
|
|
26
|
+
#
|
|
27
|
+
# Foreign Keys
|
|
28
|
+
#
|
|
29
|
+
# fk_rails_... (workspace_id => aven_workspaces.id)
|
|
30
|
+
#
|
|
31
|
+
module Aven
|
|
32
|
+
module Agentic
|
|
33
|
+
class Document < Aven::ApplicationRecord
|
|
34
|
+
self.table_name = "aven_agentic_documents"
|
|
35
|
+
|
|
36
|
+
include Aven::Model::TenantModel
|
|
37
|
+
include Aven::Agentic::DocumentEmbeddable
|
|
38
|
+
|
|
39
|
+
has_one_attached :file
|
|
40
|
+
|
|
41
|
+
has_many :embeddings,
|
|
42
|
+
class_name: "Aven::Agentic::DocumentEmbedding",
|
|
43
|
+
foreign_key: :document_id,
|
|
44
|
+
dependent: :destroy
|
|
45
|
+
|
|
46
|
+
has_many :agent_documents,
|
|
47
|
+
class_name: "Aven::Agentic::AgentDocument",
|
|
48
|
+
foreign_key: :document_id,
|
|
49
|
+
dependent: :destroy,
|
|
50
|
+
inverse_of: :document
|
|
51
|
+
|
|
52
|
+
has_many :agents, through: :agent_documents
|
|
53
|
+
|
|
54
|
+
# Statuses
|
|
55
|
+
OCR_STATUSES = %w[pending processing completed failed skipped].freeze
|
|
56
|
+
EMBEDDING_STATUSES = %w[pending processing completed failed].freeze
|
|
57
|
+
|
|
58
|
+
# Allowed file types
|
|
59
|
+
ALLOWED_CONTENT_TYPES = [
|
|
60
|
+
"application/pdf",
|
|
61
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
62
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
63
|
+
"image/jpeg",
|
|
64
|
+
"image/png",
|
|
65
|
+
"image/gif",
|
|
66
|
+
"image/webp"
|
|
67
|
+
].freeze
|
|
68
|
+
|
|
69
|
+
MAX_FILE_SIZE = 30.megabytes
|
|
70
|
+
|
|
71
|
+
validates :filename, presence: true
|
|
72
|
+
validates :content_type, presence: true, inclusion: { in: ALLOWED_CONTENT_TYPES }
|
|
73
|
+
validates :byte_size, presence: true, numericality: { less_than_or_equal_to: MAX_FILE_SIZE }
|
|
74
|
+
validates :ocr_status, inclusion: { in: OCR_STATUSES }
|
|
75
|
+
validates :embedding_status, inclusion: { in: EMBEDDING_STATUSES }
|
|
76
|
+
validate :file_attached
|
|
77
|
+
|
|
78
|
+
after_create_commit :enqueue_ocr_job
|
|
79
|
+
|
|
80
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
81
|
+
scope :by_type, ->(type) { where("content_type LIKE ?", "#{type}%") }
|
|
82
|
+
scope :images, -> { where("content_type LIKE ?", "image/%") }
|
|
83
|
+
scope :pdfs, -> { where(content_type: "application/pdf") }
|
|
84
|
+
scope :with_ocr, -> { where(ocr_status: "completed").where.not(ocr_content: nil) }
|
|
85
|
+
scope :pending_ocr, -> { where(ocr_status: "pending") }
|
|
86
|
+
scope :pending_embedding, -> { where(embedding_status: "pending", ocr_status: "completed") }
|
|
87
|
+
|
|
88
|
+
def image?
|
|
89
|
+
content_type.start_with?("image/")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def pdf?
|
|
93
|
+
content_type == "application/pdf"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def word_doc?
|
|
97
|
+
content_type.include?("wordprocessingml")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def excel?
|
|
101
|
+
content_type.include?("spreadsheetml")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def ocr_required?
|
|
105
|
+
image? || pdf?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def mark_ocr_processing!
|
|
109
|
+
update!(ocr_status: "processing")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def mark_ocr_completed!(content)
|
|
113
|
+
update!(
|
|
114
|
+
ocr_status: "completed",
|
|
115
|
+
ocr_content: content,
|
|
116
|
+
processed_at: Time.current
|
|
117
|
+
)
|
|
118
|
+
enqueue_embedding_job if content.present?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def mark_ocr_failed!(error = nil)
|
|
122
|
+
update!(
|
|
123
|
+
ocr_status: "failed",
|
|
124
|
+
metadata: metadata.merge("ocr_error" => error)
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def mark_ocr_skipped!
|
|
129
|
+
update!(ocr_status: "skipped")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def mark_embedding_processing!
|
|
133
|
+
update!(embedding_status: "processing")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def mark_embedding_completed!
|
|
137
|
+
update!(embedding_status: "completed")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def mark_embedding_failed!(error = nil)
|
|
141
|
+
update!(
|
|
142
|
+
embedding_status: "failed",
|
|
143
|
+
metadata: metadata.merge("embedding_error" => error)
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def file_attached
|
|
150
|
+
errors.add(:file, "must be attached") unless file.attached?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def enqueue_ocr_job
|
|
154
|
+
Aven::Agentic::DocumentOcrJob.perform_later(id) if ocr_required?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def enqueue_embedding_job
|
|
158
|
+
Aven::Agentic::DocumentEmbeddingJob.perform_later(id)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_agentic_document_embeddings
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# chunk_index :integer not null
|
|
9
|
+
# content :text not null
|
|
10
|
+
# embedding :vector(1536)
|
|
11
|
+
# created_at :datetime not null
|
|
12
|
+
# updated_at :datetime not null
|
|
13
|
+
# document_id :bigint not null
|
|
14
|
+
#
|
|
15
|
+
# Indexes
|
|
16
|
+
#
|
|
17
|
+
# idx_on_document_id_chunk_index_5fe199c056 (document_id,chunk_index) UNIQUE
|
|
18
|
+
# index_aven_agentic_document_embeddings_on_document_id (document_id)
|
|
19
|
+
#
|
|
20
|
+
# Foreign Keys
|
|
21
|
+
#
|
|
22
|
+
# fk_rails_... (document_id => aven_agentic_documents.id)
|
|
23
|
+
#
|
|
24
|
+
module Aven
|
|
25
|
+
module Agentic
|
|
26
|
+
class DocumentEmbedding < Aven::ApplicationRecord
|
|
27
|
+
self.table_name = "aven_agentic_document_embeddings"
|
|
28
|
+
|
|
29
|
+
belongs_to :document, class_name: "Aven::Agentic::Document"
|
|
30
|
+
|
|
31
|
+
validates :chunk_index, presence: true, uniqueness: { scope: :document_id }
|
|
32
|
+
validates :content, presence: true
|
|
33
|
+
|
|
34
|
+
scope :ordered, -> { order(:chunk_index) }
|
|
35
|
+
|
|
36
|
+
delegate :workspace, :workspace_id, to: :document
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_agentic_tools
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# class_name :string not null
|
|
9
|
+
# default_description :text
|
|
10
|
+
# description :text
|
|
11
|
+
# enabled :boolean default(TRUE), not null
|
|
12
|
+
# name :string not null
|
|
13
|
+
# created_at :datetime not null
|
|
14
|
+
# updated_at :datetime not null
|
|
15
|
+
# workspace_id :bigint
|
|
16
|
+
#
|
|
17
|
+
# Indexes
|
|
18
|
+
#
|
|
19
|
+
# index_aven_agentic_tools_on_enabled (enabled)
|
|
20
|
+
# index_aven_agentic_tools_on_workspace_id (workspace_id)
|
|
21
|
+
# index_aven_agentic_tools_on_workspace_id_and_class_name (workspace_id,class_name) UNIQUE
|
|
22
|
+
# index_aven_agentic_tools_on_workspace_id_and_name (workspace_id,name) UNIQUE
|
|
23
|
+
#
|
|
24
|
+
# Foreign Keys
|
|
25
|
+
#
|
|
26
|
+
# fk_rails_... (workspace_id => aven_workspaces.id)
|
|
27
|
+
#
|
|
28
|
+
module Aven
|
|
29
|
+
module Agentic
|
|
30
|
+
class Tool < Aven::ApplicationRecord
|
|
31
|
+
self.table_name = "aven_agentic_tools"
|
|
32
|
+
|
|
33
|
+
belongs_to :workspace, class_name: "Aven::Workspace", optional: true
|
|
34
|
+
|
|
35
|
+
has_many :parameters,
|
|
36
|
+
class_name: "Aven::Agentic::ToolParameter",
|
|
37
|
+
foreign_key: :tool_id,
|
|
38
|
+
dependent: :destroy,
|
|
39
|
+
inverse_of: :tool
|
|
40
|
+
|
|
41
|
+
has_many :agent_tools,
|
|
42
|
+
class_name: "Aven::Agentic::AgentTool",
|
|
43
|
+
foreign_key: :tool_id,
|
|
44
|
+
dependent: :destroy,
|
|
45
|
+
inverse_of: :tool
|
|
46
|
+
|
|
47
|
+
has_many :agents, through: :agent_tools
|
|
48
|
+
|
|
49
|
+
accepts_nested_attributes_for :parameters, allow_destroy: true
|
|
50
|
+
|
|
51
|
+
validates :name, presence: true, uniqueness: { scope: :workspace_id }
|
|
52
|
+
validates :class_name, presence: true, uniqueness: { scope: :workspace_id }
|
|
53
|
+
|
|
54
|
+
scope :enabled, -> { where(enabled: true) }
|
|
55
|
+
scope :global, -> { where(workspace_id: nil) }
|
|
56
|
+
scope :for_workspace, ->(workspace) {
|
|
57
|
+
where(workspace_id: [nil, workspace.id])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Get the actual tool class
|
|
61
|
+
def tool_class
|
|
62
|
+
class_name.constantize
|
|
63
|
+
rescue NameError
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if tool class exists and is valid
|
|
68
|
+
def valid_class?
|
|
69
|
+
klass = tool_class
|
|
70
|
+
klass.present? && klass < Aven::Agentic::Tools::Base
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Get effective description (user-enriched or default)
|
|
74
|
+
def effective_description
|
|
75
|
+
description.presence || default_description
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Sync from code definition
|
|
79
|
+
def sync_from_code!
|
|
80
|
+
klass = tool_class
|
|
81
|
+
return false unless klass
|
|
82
|
+
|
|
83
|
+
self.default_description = klass.default_description
|
|
84
|
+
|
|
85
|
+
code_params = klass.parameters
|
|
86
|
+
code_names = code_params.map { |p| p.name.to_s }
|
|
87
|
+
|
|
88
|
+
# Remove deleted parameters
|
|
89
|
+
parameters.where.not(name: code_names).destroy_all
|
|
90
|
+
|
|
91
|
+
# Create or update parameters
|
|
92
|
+
code_params.each_with_index do |param_def, index|
|
|
93
|
+
param = parameters.find_or_initialize_by(name: param_def.name.to_s)
|
|
94
|
+
param.param_type = param_def.type.to_s
|
|
95
|
+
param.default_description = param_def.description
|
|
96
|
+
param.required = param_def.required || false
|
|
97
|
+
param.constraints = param_def.constraints || {}
|
|
98
|
+
param.position = index
|
|
99
|
+
param.save!
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
save!
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_agentic_tool_parameters
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# constraints :jsonb
|
|
9
|
+
# default_description :text
|
|
10
|
+
# description :text
|
|
11
|
+
# name :string not null
|
|
12
|
+
# param_type :string not null
|
|
13
|
+
# position :integer default(0), not null
|
|
14
|
+
# required :boolean default(FALSE), not null
|
|
15
|
+
# created_at :datetime not null
|
|
16
|
+
# updated_at :datetime not null
|
|
17
|
+
# tool_id :bigint not null
|
|
18
|
+
#
|
|
19
|
+
# Indexes
|
|
20
|
+
#
|
|
21
|
+
# index_aven_agentic_tool_parameters_on_tool_id (tool_id)
|
|
22
|
+
# index_aven_agentic_tool_parameters_on_tool_id_and_name (tool_id,name) UNIQUE
|
|
23
|
+
# index_aven_agentic_tool_parameters_on_tool_id_and_position (tool_id,position)
|
|
24
|
+
#
|
|
25
|
+
# Foreign Keys
|
|
26
|
+
#
|
|
27
|
+
# fk_rails_... (tool_id => aven_agentic_tools.id)
|
|
28
|
+
#
|
|
29
|
+
module Aven
|
|
30
|
+
module Agentic
|
|
31
|
+
class ToolParameter < Aven::ApplicationRecord
|
|
32
|
+
self.table_name = "aven_agentic_tool_parameters"
|
|
33
|
+
|
|
34
|
+
belongs_to :tool, class_name: "Aven::Agentic::Tool", inverse_of: :parameters
|
|
35
|
+
|
|
36
|
+
validates :name, presence: true, uniqueness: { scope: :tool_id }
|
|
37
|
+
validates :param_type, presence: true
|
|
38
|
+
|
|
39
|
+
default_scope { order(:position) }
|
|
40
|
+
|
|
41
|
+
PARAM_TYPES = %w[string integer float boolean array object].freeze
|
|
42
|
+
|
|
43
|
+
validates :param_type, inclusion: { in: PARAM_TYPES }
|
|
44
|
+
|
|
45
|
+
# Get effective description (user-enriched or default)
|
|
46
|
+
def effective_description
|
|
47
|
+
description.presence || default_description
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if parameter is required
|
|
51
|
+
def required?
|
|
52
|
+
required
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
class Article < ApplicationRecord
|
|
5
|
+
self.table_name = "aven_articles"
|
|
6
|
+
|
|
7
|
+
include Aven::Model::TenantModel
|
|
8
|
+
|
|
9
|
+
extend FriendlyId
|
|
10
|
+
friendly_id :title, use: [:slugged, :scoped], scope: :workspace
|
|
11
|
+
|
|
12
|
+
acts_as_taggable_on :tags
|
|
13
|
+
|
|
14
|
+
# Associations
|
|
15
|
+
belongs_to :author, class_name: "Aven::User", optional: true
|
|
16
|
+
|
|
17
|
+
has_one_attached :main_visual
|
|
18
|
+
|
|
19
|
+
has_many :article_attachments,
|
|
20
|
+
class_name: "Aven::ArticleAttachment",
|
|
21
|
+
foreign_key: :article_id,
|
|
22
|
+
dependent: :destroy,
|
|
23
|
+
inverse_of: :article
|
|
24
|
+
|
|
25
|
+
has_many :article_relationships,
|
|
26
|
+
-> { order(position: :asc) },
|
|
27
|
+
class_name: "Aven::ArticleRelationship",
|
|
28
|
+
foreign_key: :article_id,
|
|
29
|
+
dependent: :destroy,
|
|
30
|
+
inverse_of: :article
|
|
31
|
+
|
|
32
|
+
has_many :related_articles,
|
|
33
|
+
through: :article_relationships,
|
|
34
|
+
source: :related_article
|
|
35
|
+
|
|
36
|
+
has_many :inverse_article_relationships,
|
|
37
|
+
class_name: "Aven::ArticleRelationship",
|
|
38
|
+
foreign_key: :related_article_id,
|
|
39
|
+
dependent: :destroy
|
|
40
|
+
|
|
41
|
+
has_many :inverse_related_articles,
|
|
42
|
+
through: :inverse_article_relationships,
|
|
43
|
+
source: :article
|
|
44
|
+
|
|
45
|
+
# Nested attributes
|
|
46
|
+
accepts_nested_attributes_for :article_attachments, allow_destroy: true
|
|
47
|
+
accepts_nested_attributes_for :article_relationships, allow_destroy: true
|
|
48
|
+
|
|
49
|
+
# Validations
|
|
50
|
+
validates :title, presence: true
|
|
51
|
+
validates :slug, uniqueness: { scope: :workspace_id }, allow_blank: true
|
|
52
|
+
|
|
53
|
+
# Scopes
|
|
54
|
+
scope :published, -> { where.not(published_at: nil).where("published_at <= ?", Time.current) }
|
|
55
|
+
scope :draft, -> { where(published_at: nil) }
|
|
56
|
+
scope :scheduled, -> { where.not(published_at: nil).where("published_at > ?", Time.current) }
|
|
57
|
+
scope :recent, -> { order(Arel.sql("published_at DESC NULLS LAST, created_at DESC")) }
|
|
58
|
+
scope :by_author, ->(user) { where(author: user) }
|
|
59
|
+
|
|
60
|
+
# Instance methods
|
|
61
|
+
def published?
|
|
62
|
+
published_at.present? && published_at <= Time.current
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def draft?
|
|
66
|
+
published_at.nil?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def scheduled?
|
|
70
|
+
published_at.present? && published_at > Time.current
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def publish!
|
|
74
|
+
update!(published_at: Time.current)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def unpublish!
|
|
78
|
+
update!(published_at: nil)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns sorted attachments
|
|
82
|
+
def sorted_attachments
|
|
83
|
+
article_attachments.order(position: :asc)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
class ArticleAttachment < ApplicationRecord
|
|
5
|
+
self.table_name = "aven_article_attachments"
|
|
6
|
+
|
|
7
|
+
belongs_to :article, class_name: "Aven::Article", inverse_of: :article_attachments
|
|
8
|
+
|
|
9
|
+
has_one_attached :file
|
|
10
|
+
|
|
11
|
+
validates :position, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
|
12
|
+
|
|
13
|
+
scope :ordered, -> { order(position: :asc) }
|
|
14
|
+
|
|
15
|
+
# Delegate workspace access
|
|
16
|
+
delegate :workspace, :workspace_id, to: :article
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
class ArticleRelationship < ApplicationRecord
|
|
5
|
+
self.table_name = "aven_article_relationships"
|
|
6
|
+
|
|
7
|
+
belongs_to :article, class_name: "Aven::Article", inverse_of: :article_relationships
|
|
8
|
+
belongs_to :related_article, class_name: "Aven::Article"
|
|
9
|
+
|
|
10
|
+
validates :related_article_id, uniqueness: { scope: :article_id }
|
|
11
|
+
validate :not_self_referential
|
|
12
|
+
|
|
13
|
+
scope :ordered, -> { order(position: :asc) }
|
|
14
|
+
|
|
15
|
+
# Delegate workspace access
|
|
16
|
+
delegate :workspace, :workspace_id, to: :article
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def not_self_referential
|
|
21
|
+
if article_id == related_article_id
|
|
22
|
+
errors.add(:related_article, "cannot be the same as the article")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_chat_messages
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# completed_at :datetime
|
|
9
|
+
# content :text
|
|
10
|
+
# cost_usd :decimal(10, 6) default(0.0)
|
|
11
|
+
# input_tokens :integer default(0)
|
|
12
|
+
# model :string
|
|
13
|
+
# output_tokens :integer default(0)
|
|
14
|
+
# role :string not null
|
|
15
|
+
# started_at :datetime
|
|
16
|
+
# status :string default("pending")
|
|
17
|
+
# tool_call :jsonb
|
|
18
|
+
# total_tokens :integer default(0)
|
|
19
|
+
# created_at :datetime not null
|
|
20
|
+
# updated_at :datetime not null
|
|
21
|
+
# parent_id :bigint
|
|
22
|
+
# thread_id :bigint not null
|
|
23
|
+
#
|
|
24
|
+
# Indexes
|
|
25
|
+
#
|
|
26
|
+
# index_aven_chat_messages_on_parent_id (parent_id)
|
|
27
|
+
# index_aven_chat_messages_on_role (role)
|
|
28
|
+
# index_aven_chat_messages_on_status (status)
|
|
29
|
+
# index_aven_chat_messages_on_thread_id (thread_id)
|
|
30
|
+
# index_aven_chat_messages_on_thread_id_and_created_at (thread_id,created_at)
|
|
31
|
+
#
|
|
32
|
+
# Foreign Keys
|
|
33
|
+
#
|
|
34
|
+
# fk_rails_... (parent_id => aven_chat_messages.id)
|
|
35
|
+
# fk_rails_... (thread_id => aven_chat_threads.id)
|
|
36
|
+
#
|
|
37
|
+
module Aven
|
|
38
|
+
module Chat
|
|
39
|
+
class Message < Aven::ApplicationRecord
|
|
40
|
+
self.table_name = "aven_chat_messages"
|
|
41
|
+
|
|
42
|
+
belongs_to :thread, class_name: "Aven::Chat::Thread"
|
|
43
|
+
belongs_to :parent, class_name: "Aven::Chat::Message", optional: true
|
|
44
|
+
|
|
45
|
+
has_many :replies,
|
|
46
|
+
class_name: "Aven::Chat::Message",
|
|
47
|
+
foreign_key: :parent_id,
|
|
48
|
+
dependent: :nullify
|
|
49
|
+
|
|
50
|
+
enum :role, {
|
|
51
|
+
user: "user",
|
|
52
|
+
assistant: "assistant",
|
|
53
|
+
tool: "tool",
|
|
54
|
+
system: "system"
|
|
55
|
+
}, prefix: true
|
|
56
|
+
|
|
57
|
+
enum :status, {
|
|
58
|
+
pending: "pending",
|
|
59
|
+
streaming: "streaming",
|
|
60
|
+
success: "success",
|
|
61
|
+
error: "error"
|
|
62
|
+
}, prefix: true, default: :pending
|
|
63
|
+
|
|
64
|
+
validates :thread, :role, presence: true
|
|
65
|
+
validates :content, presence: true, unless: -> { role_assistant? && (status_pending? || status_streaming?) }
|
|
66
|
+
|
|
67
|
+
scope :by_tool_call_id, ->(id) { where("tool_call->>'id' = ?", id) }
|
|
68
|
+
scope :chronological, -> { order(:created_at) }
|
|
69
|
+
|
|
70
|
+
after_create_commit :broadcast_created
|
|
71
|
+
after_update_commit :broadcast_updated
|
|
72
|
+
|
|
73
|
+
# Calculate duration if timing is available
|
|
74
|
+
def duration
|
|
75
|
+
return nil unless started_at && completed_at
|
|
76
|
+
|
|
77
|
+
completed_at - started_at
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def mark_started!
|
|
81
|
+
update!(started_at: Time.current, status: :streaming)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def mark_completed!(content: nil, model: nil, tokens: {})
|
|
85
|
+
update!(
|
|
86
|
+
completed_at: Time.current,
|
|
87
|
+
status: :success,
|
|
88
|
+
content:,
|
|
89
|
+
model:,
|
|
90
|
+
input_tokens: tokens[:input] || 0,
|
|
91
|
+
output_tokens: tokens[:output] || 0,
|
|
92
|
+
total_tokens: tokens[:total] || 0
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def mark_failed!(error_message)
|
|
97
|
+
update!(
|
|
98
|
+
completed_at: Time.current,
|
|
99
|
+
status: :error,
|
|
100
|
+
content: error_message
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Append content during streaming
|
|
105
|
+
def append_content!(chunk)
|
|
106
|
+
new_content = (content || "") + chunk
|
|
107
|
+
update_column(:content, new_content)
|
|
108
|
+
broadcast_streaming(new_content)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def broadcast_created
|
|
114
|
+
Aven::Chat::ThreadChannel.broadcast_to(thread, {
|
|
115
|
+
type: "message_created",
|
|
116
|
+
message: as_json
|
|
117
|
+
})
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def broadcast_updated
|
|
121
|
+
Aven::Chat::ThreadChannel.broadcast_to(thread, {
|
|
122
|
+
type: "message_updated",
|
|
123
|
+
message: as_json
|
|
124
|
+
})
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def broadcast_streaming(content)
|
|
128
|
+
Aven::Chat::ThreadChannel.broadcast_to(thread, {
|
|
129
|
+
type: "message_streaming",
|
|
130
|
+
message: { id:, content: }
|
|
131
|
+
})
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|