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.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +35 -0
  4. data/Rakefile +19 -0
  5. data/app/assets/stylesheets/aven/application.css +14 -0
  6. data/app/assets/stylesheets/aven/application.tailwind.css +7 -0
  7. data/app/assets/stylesheets/aven/tailwind.css +224 -0
  8. data/app/channels/aven/chat/thread_channel.rb +39 -0
  9. data/app/components/aven/application_view_component.rb +15 -0
  10. data/app/components/aven/views/admin/dashboard/index/component.html.erb +1 -0
  11. data/app/components/aven/views/admin/dashboard/index/component.rb +5 -0
  12. data/app/components/aven/views/articles/edit/component.html.erb +14 -0
  13. data/app/components/aven/views/articles/edit/component.rb +14 -0
  14. data/app/components/aven/views/articles/form/component.html.erb +45 -0
  15. data/app/components/aven/views/articles/form/component.rb +27 -0
  16. data/app/components/aven/views/articles/index/component.html.erb +93 -0
  17. data/app/components/aven/views/articles/index/component.rb +29 -0
  18. data/app/components/aven/views/articles/new/component.html.erb +13 -0
  19. data/app/components/aven/views/articles/new/component.rb +14 -0
  20. data/app/components/aven/views/articles/show/component.html.erb +110 -0
  21. data/app/components/aven/views/articles/show/component.rb +34 -0
  22. data/app/components/aven/views/oauth/error/component.html.erb +44 -0
  23. data/app/components/aven/views/oauth/error/component.rb +30 -0
  24. data/app/components/aven/views/static/index/component.html.erb +17 -0
  25. data/app/components/aven/views/static/index/component.rb +16 -0
  26. data/app/components/aven/views/static/index/controller.js +7 -0
  27. data/app/controllers/aven/admin/base.rb +16 -0
  28. data/app/controllers/aven/admin/dashboard_controller.rb +9 -0
  29. data/app/controllers/aven/agentic/agents_controller.rb +56 -0
  30. data/app/controllers/aven/agentic/documents_controller.rb +51 -0
  31. data/app/controllers/aven/agentic/mcp_controller.rb +124 -0
  32. data/app/controllers/aven/agentic/tools_controller.rb +37 -0
  33. data/app/controllers/aven/ai/text_controller.rb +41 -0
  34. data/app/controllers/aven/application_controller.rb +27 -0
  35. data/app/controllers/aven/articles_controller.rb +114 -0
  36. data/app/controllers/aven/auth_controller.rb +12 -0
  37. data/app/controllers/aven/chat/threads_controller.rb +67 -0
  38. data/app/controllers/aven/oauth/auth0_controller.rb +84 -0
  39. data/app/controllers/aven/oauth/base_controller.rb +183 -0
  40. data/app/controllers/aven/oauth/documentation/auth0.md +387 -0
  41. data/app/controllers/aven/oauth/documentation/entra_id.md +608 -0
  42. data/app/controllers/aven/oauth/documentation/github.md +329 -0
  43. data/app/controllers/aven/oauth/documentation/google.md +253 -0
  44. data/app/controllers/aven/oauth/entra_id_controller.rb +92 -0
  45. data/app/controllers/aven/oauth/github_controller.rb +91 -0
  46. data/app/controllers/aven/oauth/google_controller.rb +64 -0
  47. data/app/controllers/aven/static_controller.rb +7 -0
  48. data/app/controllers/aven/tags_controller.rb +44 -0
  49. data/app/controllers/aven/workspaces_controller.rb +20 -0
  50. data/app/controllers/concerns/aven/authentication.rb +49 -0
  51. data/app/controllers/concerns/aven/controller_helpers.rb +38 -0
  52. data/app/helpers/aven/application_helper.rb +16 -0
  53. data/app/javascript/aven/application.js +3 -0
  54. data/app/javascript/aven/controllers/application.js +5 -0
  55. data/app/javascript/aven/controllers/index.js +11 -0
  56. data/app/jobs/aven/agentic/document_embedding_job.rb +28 -0
  57. data/app/jobs/aven/agentic/document_ocr_job.rb +28 -0
  58. data/app/jobs/aven/application_job.rb +4 -0
  59. data/app/jobs/aven/chat/calculate_cost_job.rb +26 -0
  60. data/app/jobs/aven/chat/run_job.rb +27 -0
  61. data/app/mailers/aven/application_mailer.rb +6 -0
  62. data/app/models/aven/agentic/agent.rb +76 -0
  63. data/app/models/aven/agentic/agent_document.rb +37 -0
  64. data/app/models/aven/agentic/agent_tool.rb +37 -0
  65. data/app/models/aven/agentic/document.rb +162 -0
  66. data/app/models/aven/agentic/document_embedding.rb +39 -0
  67. data/app/models/aven/agentic/tool.rb +106 -0
  68. data/app/models/aven/agentic/tool_parameter.rb +56 -0
  69. data/app/models/aven/application_record.rb +5 -0
  70. data/app/models/aven/article.rb +86 -0
  71. data/app/models/aven/article_attachment.rb +18 -0
  72. data/app/models/aven/article_relationship.rb +26 -0
  73. data/app/models/aven/chat/message.rb +135 -0
  74. data/app/models/aven/chat/thread.rb +159 -0
  75. data/app/models/aven/import/entry.rb +45 -0
  76. data/app/models/aven/import/item_link.rb +36 -0
  77. data/app/models/aven/import/processor.rb +123 -0
  78. data/app/models/aven/import.rb +102 -0
  79. data/app/models/aven/item/embed.rb +54 -0
  80. data/app/models/aven/item/embeddable.rb +141 -0
  81. data/app/models/aven/item/linkable.rb +212 -0
  82. data/app/models/aven/item/schema/builder.rb +139 -0
  83. data/app/models/aven/item/schemaed.rb +252 -0
  84. data/app/models/aven/item/schemas/base.rb +108 -0
  85. data/app/models/aven/item.rb +128 -0
  86. data/app/models/aven/item_link.rb +43 -0
  87. data/app/models/aven/item_schema.rb +87 -0
  88. data/app/models/aven/log.rb +66 -0
  89. data/app/models/aven/loggable.rb +20 -0
  90. data/app/models/aven/user.rb +40 -0
  91. data/app/models/aven/workspace.rb +93 -0
  92. data/app/models/aven/workspace_role.rb +46 -0
  93. data/app/models/aven/workspace_user.rb +54 -0
  94. data/app/models/aven/workspace_user_role.rb +38 -0
  95. data/app/models/concerns/aven/agentic/document_embeddable.rb +58 -0
  96. data/app/models/concerns/aven/searchable.rb +61 -0
  97. data/app/services/aven/agentic/dynamic_tool_builder.rb +81 -0
  98. data/app/services/aven/agentic/mcp/adapter.rb +77 -0
  99. data/app/services/aven/agentic/mcp/result_formatter.rb +57 -0
  100. data/app/services/aven/agentic/mcp/server_factory.rb +43 -0
  101. data/app/services/aven/agentic/ocr/base_extractor.rb +39 -0
  102. data/app/services/aven/agentic/ocr/excel_extractor.rb +43 -0
  103. data/app/services/aven/agentic/ocr/image_extractor.rb +22 -0
  104. data/app/services/aven/agentic/ocr/pdf_extractor.rb +48 -0
  105. data/app/services/aven/agentic/ocr/processor.rb +36 -0
  106. data/app/services/aven/agentic/ocr/textract_client.rb +131 -0
  107. data/app/services/aven/agentic/ocr/word_extractor.rb +34 -0
  108. data/app/services/aven/agentic/tool_result_formatter.rb +76 -0
  109. data/app/services/aven/agentic/tools/base.rb +55 -0
  110. data/app/services/aven/agentic/tools/concerns/boolean_filtering.rb +40 -0
  111. data/app/services/aven/agentic/tools/concerns/enum_filtering.rb +47 -0
  112. data/app/services/aven/agentic/tools/concerns/geo_filtering.rb +56 -0
  113. data/app/services/aven/agentic/tools/concerns/range_filtering.rb +51 -0
  114. data/app/services/aven/chat/broadcaster.rb +59 -0
  115. data/app/services/aven/chat/config.rb +93 -0
  116. data/app/services/aven/chat/message_builder.rb +42 -0
  117. data/app/services/aven/chat/orchestrator.rb +69 -0
  118. data/app/services/aven/chat/runner.rb +105 -0
  119. data/app/services/aven/chat/title_generator.rb +61 -0
  120. data/app/services/aven/external/gmail_client.rb +173 -0
  121. data/app/services/aven/external/google_contacts_client.rb +95 -0
  122. data/app/views/layouts/aven/admin.html.erb +16 -0
  123. data/app/views/layouts/aven/application.html.erb +18 -0
  124. data/config/importmap.rb +16 -0
  125. data/config/routes.rb +63 -0
  126. data/db/migrate/20200101000001_create_aven_users.rb +19 -0
  127. data/db/migrate/20200101000002_create_aven_workspaces.rb +14 -0
  128. data/db/migrate/20200101000003_create_aven_workspace_users.rb +12 -0
  129. data/db/migrate/20200101000004_create_aven_workspace_roles.rb +13 -0
  130. data/db/migrate/20200101000005_create_aven_workspace_user_roles.rb +12 -0
  131. data/db/migrate/20200101000006_create_aven_logs.rb +21 -0
  132. data/db/migrate/20200101000009_create_aven_items.rb +17 -0
  133. data/db/migrate/20200101000010_create_aven_item_links.rb +17 -0
  134. data/db/migrate/20200101000011_create_aven_agentic_tools.rb +19 -0
  135. data/db/migrate/20200101000012_create_aven_agentic_tool_parameters.rb +20 -0
  136. data/db/migrate/20200101000013_create_aven_agentic_documents.rb +22 -0
  137. data/db/migrate/20200101000014_create_aven_agentic_document_embeddings.rb +18 -0
  138. data/db/migrate/20200101000015_create_aven_agentic_agents.rb +18 -0
  139. data/db/migrate/20200101000016_create_aven_agentic_agent_tools.rb +13 -0
  140. data/db/migrate/20200101000017_create_aven_agentic_agent_documents.rb +13 -0
  141. data/db/migrate/20200101000018_create_aven_chat_threads.rb +19 -0
  142. data/db/migrate/20200101000019_create_aven_chat_messages.rb +26 -0
  143. data/db/migrate/20200101000020_add_pg_search_support.rb +21 -0
  144. data/db/migrate/20200101000021_create_aven_item_schemas.rb +18 -0
  145. data/db/migrate/20200101000022_create_aven_imports.rb +23 -0
  146. data/db/migrate/20200101000023_create_aven_import_entries.rb +13 -0
  147. data/db/migrate/20200101000024_create_aven_import_item_links.rb +13 -0
  148. data/db/migrate/20200101000025_create_aven_articles.rb +19 -0
  149. data/db/migrate/20200101000026_create_aven_article_attachments.rb +13 -0
  150. data/db/migrate/20200101000027_create_aven_article_relationships.rb +15 -0
  151. data/lib/aven/configuration.rb +87 -0
  152. data/lib/aven/engine.rb +43 -0
  153. data/lib/aven/model/tenant_model.rb +91 -0
  154. data/lib/aven/model.rb +6 -0
  155. data/lib/aven/version.rb +3 -0
  156. data/lib/aven.rb +8 -0
  157. data/lib/tasks/annotate_rb.rake +10 -0
  158. data/lib/tasks/aven_tasks.rake +21 -0
  159. metadata +426 -0
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ module Aven
7
+ module Oauth
8
+ class GithubController < BaseController
9
+ AUTHORIZATION_URL = "https://github.com/login/oauth/authorize"
10
+ TOKEN_URL = "https://github.com/login/oauth/access_token"
11
+ USER_INFO_URL = "https://api.github.com/user"
12
+ USER_EMAIL_URL = "https://api.github.com/user/emails"
13
+ DEFAULT_SCOPE = "user:email"
14
+
15
+ protected
16
+
17
+ def authorization_url(state)
18
+ params = {
19
+ client_id: oauth_config[:client_id],
20
+ redirect_uri: callback_url,
21
+ scope: oauth_config[:scope] || DEFAULT_SCOPE,
22
+ state:
23
+ }
24
+
25
+ "#{AUTHORIZATION_URL}?#{params.to_query}"
26
+ end
27
+
28
+ def exchange_code_for_token(code)
29
+ params = {
30
+ client_id: oauth_config[:client_id],
31
+ client_secret: oauth_config[:client_secret],
32
+ code:,
33
+ redirect_uri: callback_url
34
+ }
35
+
36
+ headers = { "Accept" => "application/json" }
37
+ oauth_request(URI(TOKEN_URL), params, headers)
38
+ end
39
+
40
+ def fetch_user_info(access_token)
41
+ # Fetch user profile
42
+ user_data = github_api_request(USER_INFO_URL, access_token)
43
+
44
+ # Fetch primary email if not public
45
+ email = user_data[:email]
46
+ if email.blank?
47
+ emails_data = github_api_request(USER_EMAIL_URL, access_token)
48
+ primary_email = emails_data.find { |e| e[:primary] && e[:verified] }
49
+ email = primary_email[:email] if primary_email
50
+ end
51
+
52
+ {
53
+ id: user_data[:id],
54
+ email:,
55
+ name: user_data[:name] || user_data[:login],
56
+ avatar_url: user_data[:avatar_url],
57
+ login: user_data[:login]
58
+ }
59
+ end
60
+
61
+ private
62
+
63
+ def callback_url
64
+ aven.oauth_github_callback_url(host: request.host, protocol: request.protocol)
65
+ end
66
+
67
+ def oauth_config
68
+ @oauth_config ||= Aven.configuration.oauth_providers[:github] || raise("GitHub OAuth not configured")
69
+ end
70
+
71
+ def github_api_request(url, access_token)
72
+ uri = URI(url)
73
+ http = Net::HTTP.new(uri.host, uri.port)
74
+ http.use_ssl = true
75
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if Rails.env.development?
76
+
77
+ request = Net::HTTP::Get.new(uri)
78
+ request["Authorization"] = "Bearer #{access_token}"
79
+ request["Accept"] = "application/vnd.github.v3+json"
80
+
81
+ response = http.request(request)
82
+
83
+ unless response.is_a?(Net::HTTPSuccess)
84
+ raise StandardError, "GitHub API request failed: #{response.body}"
85
+ end
86
+
87
+ JSON.parse(response.body, symbolize_names: true)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ module Aven
7
+ module Oauth
8
+ class GoogleController < BaseController
9
+ AUTHORIZATION_URL = "https://accounts.google.com/o/oauth2/v2/auth"
10
+ TOKEN_URL = "https://www.googleapis.com/oauth2/v4/token"
11
+ USER_INFO_URL = "https://www.googleapis.com/oauth2/v3/userinfo"
12
+ DEFAULT_SCOPE = "openid email profile"
13
+
14
+ protected
15
+
16
+ def authorization_url(state)
17
+ params = {
18
+ client_id: oauth_config[:client_id],
19
+ redirect_uri: callback_url,
20
+ response_type: "code",
21
+ scope: oauth_config[:scope] || DEFAULT_SCOPE,
22
+ state:,
23
+ access_type: oauth_config[:access_type] || "offline",
24
+ prompt: oauth_config[:prompt] || "select_account"
25
+ }
26
+
27
+ "#{AUTHORIZATION_URL}?#{params.to_query}"
28
+ end
29
+
30
+ def exchange_code_for_token(code)
31
+ params = {
32
+ code:,
33
+ client_id: oauth_config[:client_id],
34
+ client_secret: oauth_config[:client_secret],
35
+ redirect_uri: callback_url,
36
+ grant_type: "authorization_code"
37
+ }
38
+
39
+ oauth_request(URI(TOKEN_URL), params)
40
+ end
41
+
42
+ def fetch_user_info(access_token)
43
+ response = oauth_get_request(URI(USER_INFO_URL), access_token)
44
+
45
+ {
46
+ id: response[:sub],
47
+ email: response[:email],
48
+ name: response[:name],
49
+ picture: response[:picture]
50
+ }
51
+ end
52
+
53
+ private
54
+
55
+ def callback_url
56
+ aven.oauth_google_callback_url(host: request.host, protocol: request.protocol)
57
+ end
58
+
59
+ def oauth_config
60
+ @oauth_config ||= Aven.configuration.oauth_providers[:google] || raise("Google OAuth not configured")
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ module Aven
2
+ class StaticController < ApplicationController
3
+ def index
4
+ view_component("static/index", current_user:)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ class TagsController < Aven::ApplicationController
5
+ include Aven::Authentication
6
+
7
+ before_action :authenticate_user!
8
+
9
+ # GET /tags or /tags.json
10
+ # Supports search via ?q= parameter
11
+ def index
12
+ tags = ActsAsTaggableOn::Tag.all
13
+
14
+ if params[:q].present?
15
+ tags = tags.where("name ILIKE ?", "%#{params[:q]}%")
16
+ end
17
+
18
+ tags = tags.order(:name).limit(params[:limit] || 50)
19
+
20
+ respond_to do |format|
21
+ format.html { render plain: tags.pluck(:name).join(", ") }
22
+ format.json { render json: tags.pluck(:name) }
23
+ end
24
+ end
25
+
26
+ # POST /tags or /tags.json
27
+ # Creates a new tag
28
+ def create
29
+ tag_name = params.dig(:tag, :name) || params[:name]
30
+
31
+ if tag_name.blank?
32
+ render json: { error: "Name is required" }, status: :unprocessable_entity
33
+ return
34
+ end
35
+
36
+ tag = ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name.strip)
37
+
38
+ respond_to do |format|
39
+ format.html { redirect_to tags_path, notice: "Tag created" }
40
+ format.json { render json: { id: tag.id, name: tag.name }, status: :created }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ class WorkspacesController < ApplicationController
5
+ before_action :authenticate_user!
6
+
7
+ # POST /workspaces/:id/switch
8
+ def switch
9
+ workspace = current_user.workspaces.friendly.find(params[:id])
10
+ self.current_workspace = workspace
11
+ redirect_to after_switch_workspace_path, notice: "Switched to #{workspace.label}"
12
+ end
13
+
14
+ private
15
+
16
+ def after_switch_workspace_path
17
+ Aven.configuration.resolve_authenticated_root_path || root_path
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ module Authentication
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ helper_method :current_user if respond_to?(:helper_method)
9
+ end
10
+
11
+ private
12
+ # Returns the currently signed-in user, if any
13
+ def current_user
14
+ @current_user ||= Aven::User.find_by(id: session[:user_id]) if session[:user_id]
15
+ end
16
+
17
+ # Signs in the given user by setting the session
18
+ def sign_in(user)
19
+ reset_session
20
+ session[:user_id] = user.id
21
+ @current_user = user
22
+ end
23
+
24
+ # Signs out the current user by clearing the session
25
+ def sign_out
26
+ reset_session
27
+ @current_user = nil
28
+ end
29
+
30
+ # Stores the current location to redirect back after authentication
31
+ def store_location
32
+ session[:return_to_after_authentication] = request.url if request.get?
33
+ end
34
+
35
+ # Returns and clears the stored location for redirect after authentication
36
+ # This method accepts a resource parameter for API compatibility with Devise
37
+ def stored_location_for(_resource = nil)
38
+ session.delete(:return_to_after_authentication)
39
+ end
40
+
41
+ # Requires user to be authenticated, redirects to root if not
42
+ def authenticate_user!
43
+ unless current_user
44
+ store_location
45
+ redirect_to main_app.root_path, alert: "You must be signed in to access this page."
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ module Aven
2
+ module ControllerHelpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :current_workspace if respond_to?(:helper_method)
7
+ end
8
+
9
+ # Get the current workspace from session
10
+ def current_workspace
11
+ return @current_workspace if defined?(@current_workspace)
12
+
13
+ @current_workspace = if session[:workspace_id].present? && current_user
14
+ current_user.workspaces.find_by(id: session[:workspace_id])
15
+ elsif current_user
16
+ # Auto-select first workspace if none selected
17
+ workspace = current_user.workspaces.first
18
+ session[:workspace_id] = workspace&.id
19
+ workspace
20
+ end
21
+ end
22
+
23
+ # Set the current workspace
24
+ def current_workspace=(workspace)
25
+ @current_workspace = workspace
26
+ session[:workspace_id] = workspace&.id
27
+ end
28
+
29
+ # Verify user has access to current workspace (similar to Devise's authenticate_user!)
30
+ def verify_workspace!
31
+ return unless current_user.present? && current_workspace.present?
32
+
33
+ unless current_user.workspaces.exists?(id: current_workspace.id)
34
+ render file: Rails.public_path.join("404.html"), status: :not_found, layout: false
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module Aven
2
+ module ApplicationHelper
3
+ def aven_importmap_tags(entry_point = "application", shim: true)
4
+ safe_join [
5
+ javascript_inline_importmap_tag(Aven.importmap.to_json(resolver: self)),
6
+ javascript_importmap_module_preload_tags(Aven.importmap),
7
+ javascript_import_module_tag(entry_point)
8
+ ].compact, "\n"
9
+ end
10
+
11
+ def view_component(name, *args, **kwargs, &block)
12
+ component = "Aven::Views::#{name.split("/").map(&:camelize).join("::")}::Component".constantize
13
+ render(component.new(*args, **kwargs), &block)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails";
3
+ import "aven/controllers";
@@ -0,0 +1,5 @@
1
+ import { Application } from "@hotwired/stimulus";
2
+ const application = Application.start();
3
+ application.debug = false;
4
+ window.Stimulus = application;
5
+ export { application };
@@ -0,0 +1,11 @@
1
+ import { application } from "aven/controllers/application";
2
+ import { eagerLoadEngineControllersFrom } from "aeros/controllers/loader";
3
+
4
+ // Load controllers from the standard controllers directory
5
+ eagerLoadEngineControllersFrom("aven/controllers", application);
6
+
7
+ // Load component controllers
8
+ eagerLoadEngineControllersFrom("aven/components", application);
9
+
10
+ // Load UI gem component controllers
11
+ eagerLoadEngineControllersFrom("aeros/components", application);
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ module Agentic
5
+ class DocumentEmbeddingJob < Aven::ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(document_id)
9
+ document = Aven::Agentic::Document.find_by(id: document_id)
10
+ return unless document
11
+ return unless document.embedding_status == "pending"
12
+ return unless document.ocr_content.present?
13
+
14
+ document.mark_embedding_processing!
15
+
16
+ # Generate embeddings for document chunks
17
+ document.generate_embeddings!
18
+
19
+ # TODO: Call embedding API to fill in vector embeddings
20
+ # For now, just mark as completed without actual embeddings
21
+ document.mark_embedding_completed!
22
+ rescue => e
23
+ Rails.logger.error("[Aven::Embedding] Job failed for document #{document_id}: #{e.message}")
24
+ document&.mark_embedding_failed!(e.message)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ module Agentic
5
+ class DocumentOcrJob < Aven::ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(document_id)
9
+ document = Aven::Agentic::Document.find_by(id: document_id)
10
+ return unless document
11
+ return unless document.ocr_status == "pending"
12
+
13
+ document.mark_ocr_processing!
14
+
15
+ content = Aven::Agentic::Ocr::Processor.process(document)
16
+
17
+ if content.present?
18
+ document.mark_ocr_completed!(content)
19
+ else
20
+ document.mark_ocr_skipped!
21
+ end
22
+ rescue => e
23
+ Rails.logger.error("[Aven::OCR] Job failed for document #{document_id}: #{e.message}")
24
+ document&.mark_ocr_failed!(e.message)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,4 @@
1
+ module Aven
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ module Chat
5
+ class CalculateCostJob < Aven::ApplicationJob
6
+ queue_as :low
7
+
8
+ def perform(message_id)
9
+ message = Aven::Chat::Message.find_by(id: message_id)
10
+ return unless message
11
+ return unless message.model.present?
12
+ return if message.cost_usd.to_f > 0
13
+
14
+ cost = Config.calculate_cost(
15
+ input_tokens: message.input_tokens,
16
+ output_tokens: message.output_tokens,
17
+ model_id: message.model
18
+ )
19
+
20
+ message.update!(cost_usd: cost) if cost
21
+ rescue => e
22
+ Rails.logger.warn("[Aven::Chat] Cost calculation failed for message #{message_id}: #{e.message}")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aven
4
+ module Chat
5
+ class RunJob < Aven::ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(thread_id, user_message_id = nil)
9
+ thread = Aven::Chat::Thread.find_by(id: thread_id)
10
+ return unless thread
11
+
12
+ user_message = if user_message_id
13
+ thread.messages.find_by(id: user_message_id)
14
+ else
15
+ thread.messages.where(role: :user).order(:created_at).last
16
+ end
17
+
18
+ return unless user_message
19
+
20
+ Aven::Chat::Orchestrator.new(thread).run(user_message)
21
+ rescue => e
22
+ Rails.logger.error("[Aven::Chat] RunJob failed for thread #{thread_id}: #{e.message}")
23
+ Rails.logger.error(e.backtrace.first(10).join("\n"))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module Aven
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: aven_agentic_agents
6
+ #
7
+ # id :bigint not null, primary key
8
+ # enabled :boolean default(TRUE), not null
9
+ # label :string not null
10
+ # slug :string
11
+ # system_prompt :text
12
+ # user_facing_question :text
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_agentic_agents_on_enabled (enabled)
20
+ # index_aven_agentic_agents_on_workspace_id (workspace_id)
21
+ # index_aven_agentic_agents_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
+ module Agentic
29
+ class Agent < Aven::ApplicationRecord
30
+ self.table_name = "aven_agentic_agents"
31
+
32
+ include Aven::Model::TenantModel
33
+
34
+ extend FriendlyId
35
+ friendly_id :label, use: [:slugged, :scoped], scope: :workspace
36
+
37
+ has_many :agent_tools,
38
+ class_name: "Aven::Agentic::AgentTool",
39
+ foreign_key: :agent_id,
40
+ dependent: :destroy,
41
+ inverse_of: :agent
42
+
43
+ has_many :tools, through: :agent_tools
44
+
45
+ has_many :agent_documents,
46
+ class_name: "Aven::Agentic::AgentDocument",
47
+ foreign_key: :agent_id,
48
+ dependent: :destroy,
49
+ inverse_of: :agent
50
+
51
+ has_many :documents, through: :agent_documents
52
+
53
+ has_many :threads,
54
+ class_name: "Aven::Chat::Thread",
55
+ foreign_key: :agent_id,
56
+ dependent: :nullify
57
+
58
+ accepts_nested_attributes_for :agent_tools, allow_destroy: true
59
+ accepts_nested_attributes_for :agent_documents, allow_destroy: true
60
+
61
+ validates :label, presence: true
62
+
63
+ scope :enabled, -> { where(enabled: true) }
64
+
65
+ # Returns tool names for this agent (used when locking thread tools)
66
+ def tool_names
67
+ tools.pluck(:name)
68
+ end
69
+
70
+ # Returns document IDs for this agent (used when locking thread documents)
71
+ def document_ids
72
+ documents.pluck(:id)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: aven_agentic_agent_documents
6
+ #
7
+ # id :bigint not null, primary key
8
+ # created_at :datetime not null
9
+ # updated_at :datetime not null
10
+ # agent_id :bigint not null
11
+ # document_id :bigint not null
12
+ #
13
+ # Indexes
14
+ #
15
+ # index_aven_agentic_agent_documents_on_agent_id (agent_id)
16
+ # index_aven_agentic_agent_documents_on_agent_id_and_document_id (agent_id,document_id) UNIQUE
17
+ # index_aven_agentic_agent_documents_on_document_id (document_id)
18
+ #
19
+ # Foreign Keys
20
+ #
21
+ # fk_rails_... (agent_id => aven_agentic_agents.id)
22
+ # fk_rails_... (document_id => aven_agentic_documents.id)
23
+ #
24
+ module Aven
25
+ module Agentic
26
+ class AgentDocument < Aven::ApplicationRecord
27
+ self.table_name = "aven_agentic_agent_documents"
28
+
29
+ belongs_to :agent, class_name: "Aven::Agentic::Agent", inverse_of: :agent_documents
30
+ belongs_to :document, class_name: "Aven::Agentic::Document", inverse_of: :agent_documents
31
+
32
+ validates :document_id, uniqueness: { scope: :agent_id }
33
+
34
+ delegate :workspace, :workspace_id, to: :agent
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == Schema Information
4
+ #
5
+ # Table name: aven_agentic_agent_tools
6
+ #
7
+ # id :bigint not null, primary key
8
+ # created_at :datetime not null
9
+ # updated_at :datetime not null
10
+ # agent_id :bigint not null
11
+ # tool_id :bigint not null
12
+ #
13
+ # Indexes
14
+ #
15
+ # index_aven_agentic_agent_tools_on_agent_id (agent_id)
16
+ # index_aven_agentic_agent_tools_on_agent_id_and_tool_id (agent_id,tool_id) UNIQUE
17
+ # index_aven_agentic_agent_tools_on_tool_id (tool_id)
18
+ #
19
+ # Foreign Keys
20
+ #
21
+ # fk_rails_... (agent_id => aven_agentic_agents.id)
22
+ # fk_rails_... (tool_id => aven_agentic_tools.id)
23
+ #
24
+ module Aven
25
+ module Agentic
26
+ class AgentTool < Aven::ApplicationRecord
27
+ self.table_name = "aven_agentic_agent_tools"
28
+
29
+ belongs_to :agent, class_name: "Aven::Agentic::Agent", inverse_of: :agent_tools
30
+ belongs_to :tool, class_name: "Aven::Agentic::Tool", inverse_of: :agent_tools
31
+
32
+ validates :tool_id, uniqueness: { scope: :agent_id }
33
+
34
+ delegate :workspace, :workspace_id, to: :agent
35
+ end
36
+ end
37
+ end