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