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,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_items
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# data :jsonb not null
|
|
9
|
+
# deleted_at :datetime
|
|
10
|
+
# schema_slug :string not null
|
|
11
|
+
# created_at :datetime not null
|
|
12
|
+
# updated_at :datetime not null
|
|
13
|
+
# workspace_id :bigint not null
|
|
14
|
+
#
|
|
15
|
+
# Indexes
|
|
16
|
+
#
|
|
17
|
+
# index_aven_items_on_data (data) USING gin
|
|
18
|
+
# index_aven_items_on_deleted_at (deleted_at)
|
|
19
|
+
# index_aven_items_on_schema_slug (schema_slug)
|
|
20
|
+
# index_aven_items_on_workspace_id (workspace_id)
|
|
21
|
+
#
|
|
22
|
+
# Foreign Keys
|
|
23
|
+
#
|
|
24
|
+
# fk_rails_... (workspace_id => aven_workspaces.id)
|
|
25
|
+
#
|
|
26
|
+
module Aven
|
|
27
|
+
class Item < ApplicationRecord
|
|
28
|
+
include Aven::Model::TenantModel
|
|
29
|
+
include Aven::Loggable
|
|
30
|
+
include Item::Schemaed
|
|
31
|
+
include Item::Embeddable
|
|
32
|
+
include Item::Linkable
|
|
33
|
+
|
|
34
|
+
self.table_name = "aven_items"
|
|
35
|
+
|
|
36
|
+
validates :schema_slug, presence: true
|
|
37
|
+
validates :data, presence: true
|
|
38
|
+
validate :validate_data_against_schema
|
|
39
|
+
|
|
40
|
+
scope :active, -> { where(deleted_at: nil) }
|
|
41
|
+
scope :deleted, -> { where.not(deleted_at: nil) }
|
|
42
|
+
scope :by_schema, ->(slug) { where(schema_slug: slug) }
|
|
43
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
44
|
+
|
|
45
|
+
def soft_delete!
|
|
46
|
+
update!(deleted_at: Time.current)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def restore!
|
|
50
|
+
update!(deleted_at: nil)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def deleted?
|
|
54
|
+
deleted_at.present?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Schema resolution: code class first, then DB, then raise
|
|
58
|
+
class << self
|
|
59
|
+
def schema_class_for(slug)
|
|
60
|
+
"Aven::Item::Schemas::#{slug.to_s.camelize}".constantize
|
|
61
|
+
rescue NameError
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def schema_for(slug, workspace: nil)
|
|
66
|
+
# 1. Try code-defined class
|
|
67
|
+
schema_class_for(slug) ||
|
|
68
|
+
# 2. Try DB (requires workspace)
|
|
69
|
+
(workspace && Aven::ItemSchema.find_by!(workspace:, slug:))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def schema_class
|
|
74
|
+
self.class.schema_class_for(schema_slug)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns the schema source (code class or DB record)
|
|
78
|
+
def resolved_schema
|
|
79
|
+
@_resolved_schema ||= begin
|
|
80
|
+
# 1. Code class first
|
|
81
|
+
code_schema = self.class.schema_class_for(schema_slug)
|
|
82
|
+
return code_schema if code_schema
|
|
83
|
+
|
|
84
|
+
# 2. DB lookup (raises if not found)
|
|
85
|
+
Aven::ItemSchema.find_by!(workspace:, slug: schema_slug)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def schema_builder
|
|
90
|
+
resolved_schema&.builder
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def validate_data_against_schema
|
|
96
|
+
return if schema_slug.blank? || data.blank?
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
json_schema = resolved_schema&.to_json_schema
|
|
100
|
+
return if json_schema.blank?
|
|
101
|
+
|
|
102
|
+
registry = JSONSkooma.create_registry("2020-12", assert_formats: true)
|
|
103
|
+
schema_with_meta = json_schema.dup
|
|
104
|
+
schema_with_meta["$schema"] ||= "https://json-schema.org/draft/2020-12/schema"
|
|
105
|
+
validator = JSONSkooma::JSONSchema.new(schema_with_meta, registry:)
|
|
106
|
+
result = validator.evaluate(data)
|
|
107
|
+
|
|
108
|
+
unless result.valid?
|
|
109
|
+
error_output = result.output(:basic)
|
|
110
|
+
if error_output["errors"]
|
|
111
|
+
error_messages = error_output["errors"].map do |err|
|
|
112
|
+
location = err["instanceLocation"] || "data"
|
|
113
|
+
message = err["error"] || "validation failed"
|
|
114
|
+
"#{location}: #{message}"
|
|
115
|
+
end
|
|
116
|
+
errors.add(:data, "schema validation failed: #{error_messages.join('; ')}")
|
|
117
|
+
else
|
|
118
|
+
errors.add(:data, "does not conform to schema")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
rescue ActiveRecord::RecordNotFound
|
|
122
|
+
errors.add(:schema_slug, "schema '#{schema_slug}' not found")
|
|
123
|
+
rescue => e
|
|
124
|
+
errors.add(:data, "schema validation error: #{e.message}")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_item_links
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# position :integer default(0)
|
|
9
|
+
# relation :string not null
|
|
10
|
+
# created_at :datetime not null
|
|
11
|
+
# updated_at :datetime not null
|
|
12
|
+
# source_id :bigint not null
|
|
13
|
+
# target_id :bigint not null
|
|
14
|
+
#
|
|
15
|
+
# Indexes
|
|
16
|
+
#
|
|
17
|
+
# index_aven_item_links_on_source_id (source_id)
|
|
18
|
+
# index_aven_item_links_on_source_id_and_relation (source_id,relation)
|
|
19
|
+
# index_aven_item_links_on_source_id_and_target_id_and_relation (source_id,target_id,relation) UNIQUE
|
|
20
|
+
# index_aven_item_links_on_target_id (target_id)
|
|
21
|
+
# index_aven_item_links_on_target_id_and_relation (target_id,relation)
|
|
22
|
+
#
|
|
23
|
+
# Foreign Keys
|
|
24
|
+
#
|
|
25
|
+
# fk_rails_... (source_id => aven_items.id)
|
|
26
|
+
# fk_rails_... (target_id => aven_items.id)
|
|
27
|
+
#
|
|
28
|
+
module Aven
|
|
29
|
+
class ItemLink < ApplicationRecord
|
|
30
|
+
self.table_name = "aven_item_links"
|
|
31
|
+
|
|
32
|
+
belongs_to :source, class_name: "Aven::Item"
|
|
33
|
+
belongs_to :target, class_name: "Aven::Item"
|
|
34
|
+
|
|
35
|
+
validates :relation, presence: true
|
|
36
|
+
validates :target_id, uniqueness: { scope: [:source_id, :relation] }
|
|
37
|
+
|
|
38
|
+
scope :for_relation, ->(rel) { where(relation: rel.to_s) }
|
|
39
|
+
scope :ordered, -> { order(position: :asc) }
|
|
40
|
+
|
|
41
|
+
delegate :workspace, :workspace_id, to: :source
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# == Schema Information
|
|
4
|
+
#
|
|
5
|
+
# Table name: aven_item_schemas
|
|
6
|
+
#
|
|
7
|
+
# id :bigint not null, primary key
|
|
8
|
+
# embeds :jsonb not null
|
|
9
|
+
# fields :jsonb not null
|
|
10
|
+
# links :jsonb not null
|
|
11
|
+
# schema :jsonb not null
|
|
12
|
+
# slug :string not null
|
|
13
|
+
# created_at :datetime not null
|
|
14
|
+
# updated_at :datetime not null
|
|
15
|
+
# workspace_id :bigint not null
|
|
16
|
+
#
|
|
17
|
+
# Indexes
|
|
18
|
+
#
|
|
19
|
+
# index_aven_item_schemas_on_slug (slug)
|
|
20
|
+
# index_aven_item_schemas_on_workspace_id (workspace_id)
|
|
21
|
+
# index_aven_item_schemas_on_workspace_id_and_slug (workspace_id,slug) UNIQUE
|
|
22
|
+
#
|
|
23
|
+
# Foreign Keys
|
|
24
|
+
#
|
|
25
|
+
# fk_rails_... (workspace_id => aven_workspaces.id)
|
|
26
|
+
#
|
|
27
|
+
module Aven
|
|
28
|
+
class ItemSchema < ApplicationRecord
|
|
29
|
+
self.table_name = "aven_item_schemas"
|
|
30
|
+
|
|
31
|
+
include Aven::Model::TenantModel
|
|
32
|
+
include Aven::Loggable
|
|
33
|
+
|
|
34
|
+
has_many :items, ->(schema) { where(schema_slug: schema.slug) },
|
|
35
|
+
class_name: "Aven::Item",
|
|
36
|
+
foreign_key: false,
|
|
37
|
+
inverse_of: false
|
|
38
|
+
|
|
39
|
+
validates :slug, presence: true,
|
|
40
|
+
uniqueness: { scope: :workspace_id },
|
|
41
|
+
format: { with: /\A[a-z][a-z0-9_]*\z/, message: "must be lowercase, start with letter, contain only letters/numbers/underscores" }
|
|
42
|
+
validates :schema, presence: true
|
|
43
|
+
validate :validate_schema_format
|
|
44
|
+
|
|
45
|
+
# Mimic the interface of code-defined schemas (Item::Schemas::Base)
|
|
46
|
+
def builder
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_json_schema
|
|
51
|
+
schema
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Accessors that match Item::Schemas::Base interface
|
|
55
|
+
def fields_config
|
|
56
|
+
(fields || {}).deep_symbolize_keys
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def embeds_config
|
|
60
|
+
(embeds || {}).deep_symbolize_keys
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def links_config
|
|
64
|
+
(links || {}).deep_symbolize_keys
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Alias methods to match Schemas::Base
|
|
68
|
+
alias_method :schema_fields, :fields_config
|
|
69
|
+
alias_method :schema_embeds, :embeds_config
|
|
70
|
+
alias_method :schema_links, :links_config
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def validate_schema_format
|
|
75
|
+
return if schema.blank?
|
|
76
|
+
|
|
77
|
+
unless schema.is_a?(Hash)
|
|
78
|
+
errors.add(:schema, "must be a valid JSON object")
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
unless schema["type"].present?
|
|
83
|
+
errors.add(:schema, "must include a 'type' property")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: aven_logs
|
|
4
|
+
#
|
|
5
|
+
# id :bigint not null, primary key
|
|
6
|
+
# level :string default("info"), not null
|
|
7
|
+
# loggable_type :string not null
|
|
8
|
+
# message :text not null
|
|
9
|
+
# metadata :jsonb
|
|
10
|
+
# state :string
|
|
11
|
+
# state_machine :string
|
|
12
|
+
# created_at :datetime not null
|
|
13
|
+
# updated_at :datetime not null
|
|
14
|
+
# loggable_id :bigint not null
|
|
15
|
+
# run_id :string
|
|
16
|
+
# workspace_id :bigint not null
|
|
17
|
+
#
|
|
18
|
+
# Indexes
|
|
19
|
+
#
|
|
20
|
+
# idx_aven_logs_on_loggable_run_state_created_at (loggable_type,loggable_id,run_id,state,created_at)
|
|
21
|
+
# index_aven_logs_on_created_at (created_at)
|
|
22
|
+
# index_aven_logs_on_level (level)
|
|
23
|
+
# index_aven_logs_on_loggable (loggable_type,loggable_id)
|
|
24
|
+
# index_aven_logs_on_workspace_id (workspace_id)
|
|
25
|
+
#
|
|
26
|
+
# Foreign Keys
|
|
27
|
+
#
|
|
28
|
+
# fk_rails_... (workspace_id => aven_workspaces.id)
|
|
29
|
+
#
|
|
30
|
+
module Aven
|
|
31
|
+
class Log < ApplicationRecord
|
|
32
|
+
self.table_name = "aven_logs"
|
|
33
|
+
|
|
34
|
+
LEVELS = %w[debug info warn error fatal].freeze
|
|
35
|
+
|
|
36
|
+
belongs_to :loggable, polymorphic: true
|
|
37
|
+
belongs_to :workspace, class_name: "Aven::Workspace"
|
|
38
|
+
|
|
39
|
+
validates :message, presence: true
|
|
40
|
+
validates :level, inclusion: { in: LEVELS }
|
|
41
|
+
|
|
42
|
+
scope :by_level, ->(level) { where(level:) }
|
|
43
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
44
|
+
|
|
45
|
+
before_validation :apply_loggable_context
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def apply_loggable_context
|
|
50
|
+
owner = loggable
|
|
51
|
+
return unless owner
|
|
52
|
+
|
|
53
|
+
if respond_to?(:workspace_id) && workspace_id.blank?
|
|
54
|
+
if owner.is_a?(Aven::Workspace)
|
|
55
|
+
self.workspace = owner
|
|
56
|
+
elsif owner.respond_to?(:workspace)
|
|
57
|
+
self.workspace = owner.workspace
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if respond_to?(:run_id) && run_id.blank? && owner.respond_to?(:_log_run_id)
|
|
62
|
+
self.run_id = owner._log_run_id
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Aven
|
|
2
|
+
module Loggable
|
|
3
|
+
def log!(message:, level: "info", metadata: nil, **extra)
|
|
4
|
+
attrs = {
|
|
5
|
+
message:,
|
|
6
|
+
level:,
|
|
7
|
+
metadata: metadata || {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
ws = is_a?(Aven::Workspace) ? self : workspace
|
|
11
|
+
attrs[:workspace] = ws
|
|
12
|
+
|
|
13
|
+
attrs[:run_id] = extra[:run_id] if extra.key?(:run_id)
|
|
14
|
+
attrs[:state] = extra[:state] if extra.key?(:state)
|
|
15
|
+
attrs[:state_machine] = extra[:state_machine] if extra.key?(:state_machine)
|
|
16
|
+
|
|
17
|
+
logs.create!(attrs)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: aven_users
|
|
4
|
+
#
|
|
5
|
+
# id :bigint not null, primary key
|
|
6
|
+
# access_token :string
|
|
7
|
+
# admin :boolean default(FALSE), not null
|
|
8
|
+
# auth_tenant :string
|
|
9
|
+
# email :string default(""), not null
|
|
10
|
+
# encrypted_password :string default(""), not null
|
|
11
|
+
# remember_created_at :datetime
|
|
12
|
+
# reset_password_sent_at :datetime
|
|
13
|
+
# reset_password_token :string
|
|
14
|
+
# created_at :datetime not null
|
|
15
|
+
# updated_at :datetime not null
|
|
16
|
+
# remote_id :string
|
|
17
|
+
#
|
|
18
|
+
# Indexes
|
|
19
|
+
#
|
|
20
|
+
# index_aven_users_on_email_and_auth_tenant (email,auth_tenant) UNIQUE
|
|
21
|
+
# index_aven_users_on_reset_password_token (reset_password_token) UNIQUE
|
|
22
|
+
#
|
|
23
|
+
module Aven
|
|
24
|
+
class User < ApplicationRecord
|
|
25
|
+
has_many :workspace_users, dependent: :destroy
|
|
26
|
+
has_many :workspaces, through: :workspace_users
|
|
27
|
+
has_many :workspace_user_roles, through: :workspace_users
|
|
28
|
+
has_many :workspace_roles, through: :workspace_user_roles
|
|
29
|
+
|
|
30
|
+
# has_many(:repos, class_name: UserRepo.name)
|
|
31
|
+
# has_many(:deployments)
|
|
32
|
+
# has_many(:strategies, foreign_key: :author_id, inverse_of: :author)
|
|
33
|
+
# has_many(:credentials, class_name: ProviderCredential.name)
|
|
34
|
+
|
|
35
|
+
validates :email, presence: true, uniqueness: { scope: :auth_tenant, case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
36
|
+
validates :remote_id, uniqueness: { scope: :auth_tenant, case_sensitive: false }, allow_blank: true
|
|
37
|
+
|
|
38
|
+
encrypts(:access_token)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: aven_workspaces
|
|
4
|
+
#
|
|
5
|
+
# id :bigint not null, primary key
|
|
6
|
+
# description :text
|
|
7
|
+
# domain :string
|
|
8
|
+
# label :string
|
|
9
|
+
# slug :string
|
|
10
|
+
# created_at :datetime not null
|
|
11
|
+
# updated_at :datetime not null
|
|
12
|
+
#
|
|
13
|
+
# Indexes
|
|
14
|
+
#
|
|
15
|
+
# index_aven_workspaces_on_slug (slug) UNIQUE
|
|
16
|
+
#
|
|
17
|
+
module Aven
|
|
18
|
+
class Workspace < ApplicationRecord
|
|
19
|
+
extend FriendlyId
|
|
20
|
+
friendly_id :label, use: :slugged
|
|
21
|
+
|
|
22
|
+
self.table_name = "aven_workspaces"
|
|
23
|
+
|
|
24
|
+
has_many :workspace_users, class_name: "Aven::WorkspaceUser", dependent: :destroy
|
|
25
|
+
has_many :users, through: :workspace_users, class_name: "Aven::User"
|
|
26
|
+
has_many :workspace_roles, class_name: "Aven::WorkspaceRole", dependent: :destroy
|
|
27
|
+
has_many :workspace_user_roles, through: :workspace_roles, class_name: "Aven::WorkspaceUserRole"
|
|
28
|
+
|
|
29
|
+
# Agentic associations
|
|
30
|
+
has_many :aven_agentic_agents, class_name: "Aven::Agentic::Agent", dependent: :destroy
|
|
31
|
+
has_many :aven_agentic_documents, class_name: "Aven::Agentic::Document", dependent: :destroy
|
|
32
|
+
|
|
33
|
+
# Chat associations
|
|
34
|
+
has_many :aven_chat_threads, class_name: "Aven::Chat::Thread", dependent: :destroy
|
|
35
|
+
|
|
36
|
+
# Article associations
|
|
37
|
+
has_many :aven_articles, class_name: "Aven::Article", dependent: :destroy
|
|
38
|
+
|
|
39
|
+
validates :slug, uniqueness: true, allow_blank: true
|
|
40
|
+
validates :label, length: { maximum: 255 }, allow_blank: true
|
|
41
|
+
validates :description, length: { maximum: 1000 }, allow_blank: true
|
|
42
|
+
|
|
43
|
+
# Tenant model registry (inspired by Flipper's group registry pattern)
|
|
44
|
+
class << self
|
|
45
|
+
# Returns array of all registered tenant model classes
|
|
46
|
+
def tenant_models
|
|
47
|
+
@tenant_models ||= []
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Register a model class as workspace-scoped
|
|
51
|
+
# Called automatically when a model includes Aven::TenantModel
|
|
52
|
+
def register_tenant_model(model_class)
|
|
53
|
+
return if tenant_models.include?(model_class)
|
|
54
|
+
|
|
55
|
+
tenant_models << model_class
|
|
56
|
+
define_tenant_association(model_class)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get all registered tenant model class names
|
|
60
|
+
def tenant_model_names
|
|
61
|
+
tenant_models.map(&:name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Define association method for a tenant model
|
|
67
|
+
# Creates query method that returns ActiveRecord::Relation
|
|
68
|
+
def define_tenant_association(model_class)
|
|
69
|
+
association_name = model_class.workspace_association_name
|
|
70
|
+
|
|
71
|
+
# Define instance method for querying tenant records
|
|
72
|
+
define_method(association_name) do
|
|
73
|
+
model_class.where(workspace_id: id)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Find a tenant record by type and ID
|
|
79
|
+
def find_tenant_record(model_name, record_id)
|
|
80
|
+
model_class = self.class.tenant_models.find { |m| m.name == model_name }
|
|
81
|
+
return nil unless model_class
|
|
82
|
+
|
|
83
|
+
model_class.where(workspace_id: id).find(record_id)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Destroy all tenant data for this workspace
|
|
87
|
+
def destroy_tenant_data
|
|
88
|
+
self.class.tenant_models.each do |model_class|
|
|
89
|
+
model_class.where(workspace_id: id).destroy_all
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: aven_workspace_roles
|
|
4
|
+
#
|
|
5
|
+
# id :bigint not null, primary key
|
|
6
|
+
# description :string
|
|
7
|
+
# label :string not null
|
|
8
|
+
# created_at :datetime not null
|
|
9
|
+
# updated_at :datetime not null
|
|
10
|
+
# workspace_id :bigint
|
|
11
|
+
#
|
|
12
|
+
# Indexes
|
|
13
|
+
#
|
|
14
|
+
# idx_aven_workspace_roles_on_ws_label (workspace_id,label) UNIQUE
|
|
15
|
+
# index_aven_workspace_roles_on_workspace_id (workspace_id)
|
|
16
|
+
#
|
|
17
|
+
# Foreign Keys
|
|
18
|
+
#
|
|
19
|
+
# fk_rails_... (workspace_id => aven_workspaces.id)
|
|
20
|
+
#
|
|
21
|
+
module Aven
|
|
22
|
+
class WorkspaceRole < ApplicationRecord
|
|
23
|
+
self.table_name = "aven_workspace_roles"
|
|
24
|
+
|
|
25
|
+
belongs_to :workspace, class_name: "Aven::Workspace"
|
|
26
|
+
has_many :workspace_user_roles, class_name: "Aven::WorkspaceUserRole", dependent: :destroy
|
|
27
|
+
has_many :workspace_users, through: :workspace_user_roles, class_name: "Aven::WorkspaceUser"
|
|
28
|
+
has_many :users, through: :workspace_users, class_name: "Aven::User"
|
|
29
|
+
|
|
30
|
+
validates :label, presence: true
|
|
31
|
+
validates :label, uniqueness: { scope: :workspace_id }
|
|
32
|
+
|
|
33
|
+
PREDEFINED_ROLES = %w[owner admin member viewer].freeze
|
|
34
|
+
|
|
35
|
+
scope :predefined, -> { where(label: PREDEFINED_ROLES) }
|
|
36
|
+
scope :custom, -> { where.not(label: PREDEFINED_ROLES) }
|
|
37
|
+
|
|
38
|
+
def predefined?
|
|
39
|
+
PREDEFINED_ROLES.include?(label)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def custom?
|
|
43
|
+
!predefined?
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: aven_workspace_users
|
|
4
|
+
#
|
|
5
|
+
# id :bigint not null, primary key
|
|
6
|
+
# created_at :datetime not null
|
|
7
|
+
# updated_at :datetime not null
|
|
8
|
+
# user_id :bigint not null
|
|
9
|
+
# workspace_id :bigint not null
|
|
10
|
+
#
|
|
11
|
+
# Indexes
|
|
12
|
+
#
|
|
13
|
+
# idx_aven_workspace_users_on_user_workspace (user_id,workspace_id) UNIQUE
|
|
14
|
+
# index_aven_workspace_users_on_user_id (user_id)
|
|
15
|
+
# index_aven_workspace_users_on_workspace_id (workspace_id)
|
|
16
|
+
#
|
|
17
|
+
# Foreign Keys
|
|
18
|
+
#
|
|
19
|
+
# fk_rails_... (user_id => aven_users.id)
|
|
20
|
+
# fk_rails_... (workspace_id => aven_workspaces.id)
|
|
21
|
+
#
|
|
22
|
+
module Aven
|
|
23
|
+
class WorkspaceUser < ApplicationRecord
|
|
24
|
+
self.table_name = "aven_workspace_users"
|
|
25
|
+
|
|
26
|
+
belongs_to :user, class_name: "Aven::User"
|
|
27
|
+
belongs_to :workspace, class_name: "Aven::Workspace"
|
|
28
|
+
|
|
29
|
+
has_many :workspace_user_roles, class_name: "Aven::WorkspaceUserRole", dependent: :destroy
|
|
30
|
+
has_many :workspace_roles, through: :workspace_user_roles, class_name: "Aven::WorkspaceRole"
|
|
31
|
+
|
|
32
|
+
validates :user_id, uniqueness: { scope: :workspace_id }
|
|
33
|
+
|
|
34
|
+
def roles
|
|
35
|
+
workspace_roles.pluck(:label)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def has_role?(role_label)
|
|
39
|
+
workspace_roles.exists?(label: role_label)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_role(role_label)
|
|
43
|
+
role = workspace.workspace_roles.find_or_create_by!(label: role_label)
|
|
44
|
+
workspace_user_roles.find_or_create_by!(workspace_role: role)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def remove_role(role_label)
|
|
48
|
+
role = workspace.workspace_roles.find_by(label: role_label)
|
|
49
|
+
return unless role
|
|
50
|
+
|
|
51
|
+
workspace_user_roles.where(workspace_role: role).destroy_all
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: aven_workspace_user_roles
|
|
4
|
+
#
|
|
5
|
+
# id :bigint not null, primary key
|
|
6
|
+
# created_at :datetime not null
|
|
7
|
+
# updated_at :datetime not null
|
|
8
|
+
# workspace_role_id :bigint
|
|
9
|
+
# workspace_user_id :bigint
|
|
10
|
+
#
|
|
11
|
+
# Indexes
|
|
12
|
+
#
|
|
13
|
+
# idx_aven_ws_user_roles_on_role_user (workspace_role_id,workspace_user_id) UNIQUE
|
|
14
|
+
# index_aven_workspace_user_roles_on_workspace_role_id (workspace_role_id)
|
|
15
|
+
# index_aven_workspace_user_roles_on_workspace_user_id (workspace_user_id)
|
|
16
|
+
#
|
|
17
|
+
# Foreign Keys
|
|
18
|
+
#
|
|
19
|
+
# fk_rails_... (workspace_role_id => aven_workspace_roles.id)
|
|
20
|
+
# fk_rails_... (workspace_user_id => aven_workspace_users.id)
|
|
21
|
+
#
|
|
22
|
+
module Aven
|
|
23
|
+
class WorkspaceUserRole < ApplicationRecord
|
|
24
|
+
self.table_name = "aven_workspace_user_roles"
|
|
25
|
+
|
|
26
|
+
belongs_to :workspace_user, class_name: "Aven::WorkspaceUser"
|
|
27
|
+
belongs_to :workspace_role, class_name: "Aven::WorkspaceRole"
|
|
28
|
+
|
|
29
|
+
validates :workspace_user_id, uniqueness: { scope: :workspace_role_id }
|
|
30
|
+
|
|
31
|
+
delegate :workspace, :label, :description, to: :workspace_role
|
|
32
|
+
delegate :user, to: :workspace_user
|
|
33
|
+
delegate :email, :username, to: :user
|
|
34
|
+
|
|
35
|
+
scope :for_workspace, ->(workspace) { joins(:workspace_role).where(aven_workspace_roles: { workspace_id: workspace.id }) }
|
|
36
|
+
scope :with_role, ->(role_label) { joins(:workspace_role).where(aven_workspace_roles: { label: role_label }) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module Agentic
|
|
5
|
+
module DocumentEmbeddable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
# Override in subclasses to customize embedding behavior
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Split content into chunks for embedding
|
|
13
|
+
def chunk_content(content, chunk_size: 1000, overlap: 200)
|
|
14
|
+
return [] if content.blank?
|
|
15
|
+
|
|
16
|
+
chunks = []
|
|
17
|
+
position = 0
|
|
18
|
+
|
|
19
|
+
while position < content.length
|
|
20
|
+
chunk_end = [position + chunk_size, content.length].min
|
|
21
|
+
chunks << content[position...chunk_end]
|
|
22
|
+
position += (chunk_size - overlap)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
chunks
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Generate embeddings for all chunks
|
|
29
|
+
def generate_embeddings!
|
|
30
|
+
return unless ocr_content.present?
|
|
31
|
+
|
|
32
|
+
chunks = chunk_content(ocr_content)
|
|
33
|
+
|
|
34
|
+
transaction do
|
|
35
|
+
embeddings.destroy_all
|
|
36
|
+
|
|
37
|
+
chunks.each_with_index do |chunk, index|
|
|
38
|
+
embeddings.create!(
|
|
39
|
+
chunk_index: index,
|
|
40
|
+
content: chunk,
|
|
41
|
+
embedding: nil # Will be filled by embedding service
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Search similar chunks using vector similarity
|
|
48
|
+
def self.search_similar(query_embedding, workspace:, limit: 10)
|
|
49
|
+
Aven::Agentic::DocumentEmbedding
|
|
50
|
+
.joins(:document)
|
|
51
|
+
.where(aven_agentic_documents: { workspace_id: workspace.id })
|
|
52
|
+
.where.not(embedding: nil)
|
|
53
|
+
.order(Arel.sql("embedding <-> '#{query_embedding}'"))
|
|
54
|
+
.limit(limit)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|