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,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,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,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,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,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
|