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,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module Chat
|
|
5
|
+
class TitleGenerator
|
|
6
|
+
MAX_TITLE_LENGTH = 100
|
|
7
|
+
|
|
8
|
+
def initialize(thread, first_message)
|
|
9
|
+
@thread = thread
|
|
10
|
+
@first_message = first_message
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Generate a title for the thread based on first message
|
|
14
|
+
def call
|
|
15
|
+
return if @thread.title.present?
|
|
16
|
+
|
|
17
|
+
title = generate_title
|
|
18
|
+
@thread.update!(title:) if title.present?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def generate_title
|
|
24
|
+
content = @first_message.content.to_s.strip
|
|
25
|
+
return nil if content.blank?
|
|
26
|
+
|
|
27
|
+
# Try LLM-based title generation if available
|
|
28
|
+
llm_title = generate_with_llm(content)
|
|
29
|
+
return llm_title if llm_title.present?
|
|
30
|
+
|
|
31
|
+
# Fallback to simple truncation
|
|
32
|
+
truncate_content(content)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def generate_with_llm(content)
|
|
36
|
+
return nil unless defined?(RubyLLM)
|
|
37
|
+
|
|
38
|
+
response = RubyLLM.chat(model: "claude-haiku-3-5-20241022")
|
|
39
|
+
.with_instructions("Generate a short, descriptive title (max 50 chars) for a conversation that starts with this message. Return only the title, nothing else.")
|
|
40
|
+
.ask(content)
|
|
41
|
+
|
|
42
|
+
title = response.content.to_s.strip.gsub(/^["']|["']$/, "")
|
|
43
|
+
title.presence
|
|
44
|
+
rescue => e
|
|
45
|
+
Rails.logger.warn("[Aven::Chat] LLM title generation failed: #{e.message}")
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def truncate_content(content)
|
|
50
|
+
# Take first sentence or first N characters
|
|
51
|
+
first_sentence = content.split(/[.!?]/).first.to_s.strip
|
|
52
|
+
|
|
53
|
+
if first_sentence.length <= MAX_TITLE_LENGTH
|
|
54
|
+
first_sentence
|
|
55
|
+
else
|
|
56
|
+
content.truncate(MAX_TITLE_LENGTH, separator: " ")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module External
|
|
5
|
+
class GmailClient
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1"
|
|
9
|
+
PAGE_SIZE = 100
|
|
10
|
+
|
|
11
|
+
IGNORED_PATTERNS = %w[
|
|
12
|
+
noreply no-reply notifications notification
|
|
13
|
+
mailer-daemon postmaster bounce
|
|
14
|
+
newsletter news updates update
|
|
15
|
+
support help info contact
|
|
16
|
+
donotreply do-not-reply
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
def initialize(access_token, exclude_emails: [])
|
|
20
|
+
@access_token = access_token
|
|
21
|
+
@exclude_emails = Array(exclude_emails).map(&:downcase)
|
|
22
|
+
@connection = build_connection
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Scan emails and extract unique addresses
|
|
26
|
+
# Returns { emails_scanned: N, addresses: [{ email:, name:, domain: }, ...] }
|
|
27
|
+
def extract_email_addresses(max_emails: 2000, &progress_callback)
|
|
28
|
+
addresses = {}
|
|
29
|
+
emails_scanned = 0
|
|
30
|
+
page_token = nil
|
|
31
|
+
|
|
32
|
+
loop do
|
|
33
|
+
break if emails_scanned >= max_emails
|
|
34
|
+
|
|
35
|
+
messages_response = fetch_messages(
|
|
36
|
+
page_token:,
|
|
37
|
+
max_results: [PAGE_SIZE, max_emails - emails_scanned].min
|
|
38
|
+
)
|
|
39
|
+
messages = messages_response["messages"] || []
|
|
40
|
+
|
|
41
|
+
break if messages.empty?
|
|
42
|
+
|
|
43
|
+
messages.each do |msg|
|
|
44
|
+
break if emails_scanned >= max_emails
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
message = fetch_message(msg["id"])
|
|
48
|
+
extract_addresses_from_message(message, addresses)
|
|
49
|
+
emails_scanned += 1
|
|
50
|
+
progress_callback&.call(emails_scanned)
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
Rails.logger.warn("Failed to fetch message #{msg['id']}: #{e.message}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
page_token = messages_response["nextPageToken"]
|
|
57
|
+
break unless page_token
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
emails_scanned:,
|
|
62
|
+
addresses: addresses.values
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Extract addresses and create import entries
|
|
67
|
+
def fetch_into_import(import, max_emails: 2000, &progress_callback)
|
|
68
|
+
result = extract_email_addresses(max_emails:, &progress_callback)
|
|
69
|
+
|
|
70
|
+
result[:addresses].each do |address_data|
|
|
71
|
+
import.entries.create!(data: address_data)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
result
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def build_connection
|
|
80
|
+
Faraday.new(GMAIL_API_BASE) do |f|
|
|
81
|
+
f.request :json
|
|
82
|
+
f.response :json
|
|
83
|
+
f.adapter Faraday.default_adapter
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def fetch_messages(page_token: nil, max_results: PAGE_SIZE)
|
|
88
|
+
params = { maxResults: max_results }
|
|
89
|
+
params[:pageToken] = page_token if page_token
|
|
90
|
+
|
|
91
|
+
response = @connection.get("users/me/messages", params) do |req|
|
|
92
|
+
req.headers["Authorization"] = "Bearer #{@access_token}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
raise Error, "Gmail API error: #{response.body}" unless response.success?
|
|
96
|
+
|
|
97
|
+
response.body
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def fetch_message(message_id)
|
|
101
|
+
response = @connection.get("users/me/messages/#{message_id}", { format: "metadata" }) do |req|
|
|
102
|
+
req.headers["Authorization"] = "Bearer #{@access_token}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
raise Error, "Gmail API error: #{response.body}" unless response.success?
|
|
106
|
+
|
|
107
|
+
response.body
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def extract_addresses_from_message(message, addresses)
|
|
111
|
+
headers = message.dig("payload", "headers") || []
|
|
112
|
+
|
|
113
|
+
%w[From To Cc].each do |header_name|
|
|
114
|
+
header = headers.find { |h| h["name"] == header_name }
|
|
115
|
+
next unless header
|
|
116
|
+
|
|
117
|
+
parse_email_header(header["value"]).each do |parsed|
|
|
118
|
+
next if ignored_address?(parsed[:email])
|
|
119
|
+
next if addresses.key?(parsed[:email].downcase)
|
|
120
|
+
|
|
121
|
+
addresses[parsed[:email].downcase] = {
|
|
122
|
+
"email" => parsed[:email],
|
|
123
|
+
"name" => parsed[:name],
|
|
124
|
+
"domain" => parsed[:email].split("@").last
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_email_header(header_value)
|
|
131
|
+
return [] if header_value.blank?
|
|
132
|
+
|
|
133
|
+
results = []
|
|
134
|
+
parts = header_value.scan(/(?:"[^"]*"|[^,])+/).map(&:strip).reject(&:blank?)
|
|
135
|
+
|
|
136
|
+
parts.each do |part|
|
|
137
|
+
if part =~ /^(?:"?(.+?)"?\s+)?<([^>]+@[^>]+)>$/
|
|
138
|
+
name = ::Regexp.last_match(1)&.strip
|
|
139
|
+
email = ::Regexp.last_match(2)&.strip&.downcase
|
|
140
|
+
elsif part =~ /^([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})$/
|
|
141
|
+
name = nil
|
|
142
|
+
email = ::Regexp.last_match(1)&.strip&.downcase
|
|
143
|
+
else
|
|
144
|
+
next
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
next unless email
|
|
148
|
+
next unless email.include?("@")
|
|
149
|
+
next if email.length < 5
|
|
150
|
+
|
|
151
|
+
results << { name: name.presence, email: }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
results
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def ignored_address?(email)
|
|
158
|
+
return true if email.blank?
|
|
159
|
+
|
|
160
|
+
local_part = email.split("@").first&.downcase || ""
|
|
161
|
+
domain = email.split("@").last&.downcase || ""
|
|
162
|
+
|
|
163
|
+
return true if @exclude_emails.include?(email.downcase)
|
|
164
|
+
return true if local_part.match?(/^(no[-_]?reply|noreply|do[-_]?not[-_]?reply|mailer[-_]?daemon|postmaster|bounce|daemon)/i)
|
|
165
|
+
return true if IGNORED_PATTERNS.any? { |keyword| local_part.include?(keyword) }
|
|
166
|
+
return true if domain == "noreply.github.com"
|
|
167
|
+
return true if domain.match?(/(^|\.)(substack\.com|beehiiv\.com|medium\.com)$/i)
|
|
168
|
+
|
|
169
|
+
false
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "google/apis/people_v1"
|
|
4
|
+
|
|
5
|
+
module Aven
|
|
6
|
+
module External
|
|
7
|
+
class GoogleContactsClient
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
PERSON_FIELDS = %w[
|
|
11
|
+
names
|
|
12
|
+
emailAddresses
|
|
13
|
+
phoneNumbers
|
|
14
|
+
organizations
|
|
15
|
+
photos
|
|
16
|
+
].join(",").freeze
|
|
17
|
+
|
|
18
|
+
PAGE_SIZE = 100
|
|
19
|
+
|
|
20
|
+
def initialize(access_token)
|
|
21
|
+
@service = Google::Apis::PeopleV1::PeopleServiceService.new
|
|
22
|
+
@service.authorization = access_token
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def contacts_count
|
|
26
|
+
response = @service.list_person_connections(
|
|
27
|
+
"people/me",
|
|
28
|
+
person_fields: "metadata",
|
|
29
|
+
page_size: 1
|
|
30
|
+
)
|
|
31
|
+
response.total_items || 0
|
|
32
|
+
rescue Google::Apis::Error => e
|
|
33
|
+
raise Error, "Google API error: #{e.message}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def each_contact(&block)
|
|
37
|
+
page_token = nil
|
|
38
|
+
|
|
39
|
+
loop do
|
|
40
|
+
response = @service.list_person_connections(
|
|
41
|
+
"people/me",
|
|
42
|
+
person_fields: PERSON_FIELDS,
|
|
43
|
+
page_size: PAGE_SIZE,
|
|
44
|
+
page_token:,
|
|
45
|
+
sort_order: "LAST_MODIFIED_DESCENDING"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
(response.connections || []).each do |person|
|
|
49
|
+
yield normalize_contact(person)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
page_token = response.next_page_token
|
|
53
|
+
break unless page_token
|
|
54
|
+
end
|
|
55
|
+
rescue Google::Apis::Error => e
|
|
56
|
+
raise Error, "Google API error: #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def fetch_all
|
|
60
|
+
contacts = []
|
|
61
|
+
each_contact { |c| contacts << c }
|
|
62
|
+
contacts
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Fetch contacts and create import entries
|
|
66
|
+
def fetch_into_import(import, &progress_callback)
|
|
67
|
+
count = 0
|
|
68
|
+
each_contact do |contact_data|
|
|
69
|
+
import.entries.create!(data: contact_data)
|
|
70
|
+
count += 1
|
|
71
|
+
progress_callback&.call(count)
|
|
72
|
+
end
|
|
73
|
+
count
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def normalize_contact(person)
|
|
79
|
+
name = person.names&.first
|
|
80
|
+
emails = person.email_addresses || []
|
|
81
|
+
phones = person.phone_numbers || []
|
|
82
|
+
org = person.organizations&.first
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
"google_resource_name" => person.resource_name,
|
|
86
|
+
"first_name" => name&.given_name,
|
|
87
|
+
"last_name" => name&.family_name,
|
|
88
|
+
"email" => emails.first&.value,
|
|
89
|
+
"phone" => phones.first&.value,
|
|
90
|
+
"company" => org&.name
|
|
91
|
+
}.compact
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html class='bg-slate-50'>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Aven</title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
<%= yield :head %>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
|
|
9
|
+
<%= stylesheet_link_tag "aven/application", media: "all" %>
|
|
10
|
+
<%= stylesheet_link_tag "aven/tailwind", media: "all" %>
|
|
11
|
+
<%= javascript_importmap_tags "aven/application", importmap: Aven.importmap %>
|
|
12
|
+
</head>
|
|
13
|
+
<body class='bg-slate-100'>
|
|
14
|
+
<%= yield %>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Aven</title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
<%= yield :head %>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
|
|
9
|
+
<%= stylesheet_link_tag "aeros/application", media: "all" %>
|
|
10
|
+
<%= stylesheet_link_tag "aeros/tailwind", media: "all" %>
|
|
11
|
+
<%= stylesheet_link_tag "aven/application", media: "all" %>
|
|
12
|
+
<%= stylesheet_link_tag "aven/tailwind", media: "all" %>
|
|
13
|
+
<%= javascript_importmap_tags "aven/application", importmap: Aven.importmap %>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<%= yield %>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Engine's own importmap configuration
|
|
2
|
+
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
|
3
|
+
pin "aven/application"
|
|
4
|
+
pin "@hotwired/stimulus", to: "stimulus.min.js"
|
|
5
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
|
6
|
+
|
|
7
|
+
pin_all_from(
|
|
8
|
+
Aven::Engine.root.join("app/javascript/aven/controllers"),
|
|
9
|
+
under: "aven/controllers",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
pin_all_from(
|
|
13
|
+
Aven::Engine.root.join("app/components/aven"),
|
|
14
|
+
under: "aven/components",
|
|
15
|
+
to: "aven"
|
|
16
|
+
)
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Aven::Engine.routes.draw do
|
|
2
|
+
# Logout route
|
|
3
|
+
get(:logout, to: "auth#logout", as: :logout)
|
|
4
|
+
|
|
5
|
+
# OAuth routes
|
|
6
|
+
namespace :oauth do
|
|
7
|
+
# Error page
|
|
8
|
+
get "error", to: "base#error", as: :error
|
|
9
|
+
|
|
10
|
+
# Google OAuth
|
|
11
|
+
get "google", to: "google#create", as: :google
|
|
12
|
+
get "google/callback", to: "google#callback", as: :google_callback
|
|
13
|
+
|
|
14
|
+
# GitHub OAuth
|
|
15
|
+
get "github", to: "github#create", as: :github
|
|
16
|
+
get "github/callback", to: "github#callback", as: :github_callback
|
|
17
|
+
|
|
18
|
+
# Auth0 OAuth
|
|
19
|
+
get "auth0", to: "auth0#create", as: :auth0
|
|
20
|
+
get "auth0/callback", to: "auth0#callback", as: :auth0_callback
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Workspace switching
|
|
24
|
+
post("/workspaces/:id/switch", to: "workspaces#switch", as: :switch_workspace)
|
|
25
|
+
|
|
26
|
+
# Agentic API routes
|
|
27
|
+
namespace :agentic do
|
|
28
|
+
# MCP endpoint
|
|
29
|
+
match "mcp", to: "mcp#handle", via: [:get, :post, :delete], as: :mcp
|
|
30
|
+
get "mcp/health", to: "mcp#health", as: :mcp_health
|
|
31
|
+
|
|
32
|
+
resources :agents, only: [:index, :show, :create, :update, :destroy]
|
|
33
|
+
resources :tools, only: [:index, :show, :update]
|
|
34
|
+
resources :documents, only: [:index, :show, :create, :destroy]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Chat API routes
|
|
38
|
+
namespace :chat do
|
|
39
|
+
resources :threads, only: [:index, :show, :create] do
|
|
40
|
+
member do
|
|
41
|
+
post :ask
|
|
42
|
+
post :ask_agent
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# AI text generation (SSE streaming)
|
|
48
|
+
namespace :ai do
|
|
49
|
+
post "text/generate", to: "text#generate", as: :text_generate
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Tags API (for tagging component)
|
|
53
|
+
resources :tags, only: [:index, :create]
|
|
54
|
+
|
|
55
|
+
# Articles
|
|
56
|
+
resources :articles
|
|
57
|
+
|
|
58
|
+
namespace(:admin) do
|
|
59
|
+
root(to: "dashboard#index")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
root(to: "static#index")
|
|
63
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CreateAvenUsers < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :aven_users do |t|
|
|
4
|
+
t.string(:email, default: "", null: false)
|
|
5
|
+
t.string(:encrypted_password, default: "", null: false)
|
|
6
|
+
t.string(:reset_password_token)
|
|
7
|
+
t.datetime(:reset_password_sent_at)
|
|
8
|
+
t.datetime(:remember_created_at)
|
|
9
|
+
t.string(:auth_tenant)
|
|
10
|
+
t.string(:remote_id)
|
|
11
|
+
t.string(:access_token)
|
|
12
|
+
t.boolean(:admin, default: false, null: false)
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index(:aven_users, [ :email, :auth_tenant ], unique: true)
|
|
17
|
+
add_index(:aven_users, :reset_password_token, unique: true)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateAvenWorkspaces < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :aven_workspaces do |t|
|
|
4
|
+
t.string :label
|
|
5
|
+
t.string :slug
|
|
6
|
+
t.text :description
|
|
7
|
+
t.string :domain
|
|
8
|
+
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
add_index :aven_workspaces, :slug, unique: true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateAvenWorkspaceUsers < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :aven_workspace_users do |t|
|
|
4
|
+
t.references :user, null: false, foreign_key: { to_table: :aven_users }
|
|
5
|
+
t.references :workspace, null: false, foreign_key: { to_table: :aven_workspaces }
|
|
6
|
+
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
add_index :aven_workspace_users, [ :user_id, :workspace_id ], unique: true, name: "idx_aven_workspace_users_on_user_workspace"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreateAvenWorkspaceRoles < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :aven_workspace_roles do |t|
|
|
4
|
+
t.references :workspace, foreign_key: { to_table: :aven_workspaces }
|
|
5
|
+
t.string :label, null: false
|
|
6
|
+
t.string :description
|
|
7
|
+
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :aven_workspace_roles, [ :workspace_id, :label ], unique: true, name: "idx_aven_workspace_roles_on_ws_label"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateAvenWorkspaceUserRoles < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :aven_workspace_user_roles do |t|
|
|
4
|
+
t.references :workspace_role, foreign_key: { to_table: :aven_workspace_roles }
|
|
5
|
+
t.references :workspace_user, foreign_key: { to_table: :aven_workspace_users }
|
|
6
|
+
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
add_index :aven_workspace_user_roles, [ :workspace_role_id, :workspace_user_id ], unique: true, name: "idx_aven_ws_user_roles_on_role_user"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CreateAvenLogs < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :aven_logs do |t|
|
|
4
|
+
t.string :level, null: false, default: "info"
|
|
5
|
+
t.string :loggable_type, null: false
|
|
6
|
+
t.bigint :loggable_id, null: false
|
|
7
|
+
t.text :message, null: false
|
|
8
|
+
t.jsonb :metadata
|
|
9
|
+
t.string :state
|
|
10
|
+
t.string :state_machine
|
|
11
|
+
t.string :run_id
|
|
12
|
+
t.references :workspace, null: false, foreign_key: { to_table: :aven_workspaces }
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :aven_logs, :created_at
|
|
17
|
+
add_index :aven_logs, :level
|
|
18
|
+
add_index :aven_logs, [ :loggable_type, :loggable_id ], name: "index_aven_logs_on_loggable"
|
|
19
|
+
add_index :aven_logs, [ :loggable_type, :loggable_id, :run_id, :state, :created_at ], name: "idx_aven_logs_on_loggable_run_state_created_at"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAvenItems < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :aven_items do |t|
|
|
6
|
+
t.references :workspace, null: false, foreign_key: { to_table: :aven_workspaces }
|
|
7
|
+
t.string :schema_slug, null: false
|
|
8
|
+
t.jsonb :data, null: false, default: {}
|
|
9
|
+
t.datetime :deleted_at
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :aven_items, :schema_slug
|
|
14
|
+
add_index :aven_items, :data, using: :gin
|
|
15
|
+
add_index :aven_items, :deleted_at
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAvenItemLinks < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :aven_item_links do |t|
|
|
6
|
+
t.references :source, null: false, foreign_key: { to_table: :aven_items }
|
|
7
|
+
t.references :target, null: false, foreign_key: { to_table: :aven_items }
|
|
8
|
+
t.string :relation, null: false
|
|
9
|
+
t.integer :position, default: 0
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :aven_item_links, [:source_id, :relation]
|
|
14
|
+
add_index :aven_item_links, [:target_id, :relation]
|
|
15
|
+
add_index :aven_item_links, [:source_id, :target_id, :relation], unique: true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAvenAgenticTools < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :aven_agentic_tools do |t|
|
|
6
|
+
t.references :workspace, foreign_key: { to_table: :aven_workspaces }
|
|
7
|
+
t.string :name, null: false
|
|
8
|
+
t.string :class_name, null: false
|
|
9
|
+
t.text :description
|
|
10
|
+
t.text :default_description
|
|
11
|
+
t.boolean :enabled, default: true, null: false
|
|
12
|
+
t.timestamps
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
add_index :aven_agentic_tools, [:workspace_id, :name], unique: true
|
|
16
|
+
add_index :aven_agentic_tools, [:workspace_id, :class_name], unique: true
|
|
17
|
+
add_index :aven_agentic_tools, :enabled
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAvenAgenticToolParameters < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :aven_agentic_tool_parameters do |t|
|
|
6
|
+
t.references :tool, null: false, foreign_key: { to_table: :aven_agentic_tools }
|
|
7
|
+
t.string :name, null: false
|
|
8
|
+
t.string :param_type, null: false
|
|
9
|
+
t.text :description
|
|
10
|
+
t.text :default_description
|
|
11
|
+
t.boolean :required, default: false, null: false
|
|
12
|
+
t.jsonb :constraints, default: {}
|
|
13
|
+
t.integer :position, default: 0, null: false
|
|
14
|
+
t.timestamps
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add_index :aven_agentic_tool_parameters, [:tool_id, :name], unique: true
|
|
18
|
+
add_index :aven_agentic_tool_parameters, [:tool_id, :position]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAvenAgenticDocuments < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :aven_agentic_documents do |t|
|
|
6
|
+
t.references :workspace, null: false, foreign_key: { to_table: :aven_workspaces }
|
|
7
|
+
t.string :filename, null: false
|
|
8
|
+
t.string :content_type, null: false
|
|
9
|
+
t.bigint :byte_size, null: false
|
|
10
|
+
t.string :ocr_status, default: "pending", null: false
|
|
11
|
+
t.text :ocr_content
|
|
12
|
+
t.string :embedding_status, default: "pending", null: false
|
|
13
|
+
t.jsonb :metadata, default: {}
|
|
14
|
+
t.datetime :processed_at
|
|
15
|
+
t.timestamps
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
add_index :aven_agentic_documents, :ocr_status
|
|
19
|
+
add_index :aven_agentic_documents, :embedding_status
|
|
20
|
+
add_index :aven_agentic_documents, :content_type
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAvenAgenticDocumentEmbeddings < ActiveRecord::Migration[8.0]
|
|
4
|
+
def change
|
|
5
|
+
# Enable pgvector extension for vector similarity search
|
|
6
|
+
enable_extension "vector"
|
|
7
|
+
|
|
8
|
+
create_table :aven_agentic_document_embeddings do |t|
|
|
9
|
+
t.references :document, null: false, foreign_key: { to_table: :aven_agentic_documents }
|
|
10
|
+
t.integer :chunk_index, null: false
|
|
11
|
+
t.text :content, null: false
|
|
12
|
+
t.vector :embedding, limit: 1536 # OpenAI ada-002 dimensions
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :aven_agentic_document_embeddings, [:document_id, :chunk_index], unique: true
|
|
17
|
+
end
|
|
18
|
+
end
|