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,110 @@
|
|
|
1
|
+
<%= ui("page", title: article.title) do |page| %>
|
|
2
|
+
<% page.with_actions_area do %>
|
|
3
|
+
<%= ui("button", label: "Edit", href: routes.edit_article_path(article), variant: :outline) %>
|
|
4
|
+
<%= button_to "Delete",
|
|
5
|
+
routes.article_path(article),
|
|
6
|
+
method: :delete,
|
|
7
|
+
form: { data: { turbo_confirm: "Are you sure you want to delete this article?" } },
|
|
8
|
+
class: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-red-300 text-red-700 bg-white hover:bg-red-50" %>
|
|
9
|
+
<%= ui("button", label: "Back to Articles", href: routes.articles_path, variant: :outline) %>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<div class="max-w-4xl space-y-8">
|
|
13
|
+
<%# Status and metadata %>
|
|
14
|
+
<div class="flex items-center gap-4 text-sm text-gray-500">
|
|
15
|
+
<% badge = status_badge %>
|
|
16
|
+
<%= ui("badge", label: badge[:label], variant: badge[:variant]) %>
|
|
17
|
+
|
|
18
|
+
<% if article.author %>
|
|
19
|
+
<span>By <%= article.author.name %></span>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<% if article.published_at %>
|
|
23
|
+
<span><%= format_date(article.published_at) %></span>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<%# Tags %>
|
|
28
|
+
<% if article.tag_list.any? %>
|
|
29
|
+
<div class="flex flex-wrap gap-2">
|
|
30
|
+
<% article.tag_list.each do |tag| %>
|
|
31
|
+
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
|
|
32
|
+
<%= tag %>
|
|
33
|
+
</span>
|
|
34
|
+
<% end %>
|
|
35
|
+
</div>
|
|
36
|
+
<% end %>
|
|
37
|
+
|
|
38
|
+
<%# Main visual %>
|
|
39
|
+
<% if article.main_visual.attached? %>
|
|
40
|
+
<div class="rounded-lg overflow-hidden">
|
|
41
|
+
<%= image_tag article.main_visual, class: "w-full h-auto" %>
|
|
42
|
+
</div>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
45
|
+
<%# Introduction %>
|
|
46
|
+
<% if article.intro.present? %>
|
|
47
|
+
<div class="text-xl text-gray-600 leading-relaxed border-l-4 border-blue-500 pl-4">
|
|
48
|
+
<%= article.intro %>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
51
|
+
|
|
52
|
+
<%# Content %>
|
|
53
|
+
<% if article.description.present? %>
|
|
54
|
+
<div class="prose prose-lg max-w-none">
|
|
55
|
+
<%= rendered_description %>
|
|
56
|
+
</div>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<%# Attachments %>
|
|
60
|
+
<% if article.article_attachments.any? %>
|
|
61
|
+
<div class="border-t pt-8">
|
|
62
|
+
<h3 class="text-lg font-semibold mb-4">Attachments</h3>
|
|
63
|
+
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
|
|
64
|
+
<% article.sorted_attachments.each do |attachment| %>
|
|
65
|
+
<% if attachment.file.attached? %>
|
|
66
|
+
<div class="relative group">
|
|
67
|
+
<% if attachment.file.image? %>
|
|
68
|
+
<%= link_to url_for(attachment.file), target: "_blank", class: "block" do %>
|
|
69
|
+
<%= image_tag attachment.file.variant(resize_to_limit: [200, 200]),
|
|
70
|
+
class: "w-full h-32 object-cover rounded-lg" %>
|
|
71
|
+
<% end %>
|
|
72
|
+
<% else %>
|
|
73
|
+
<%= link_to url_for(attachment.file), target: "_blank",
|
|
74
|
+
class: "flex items-center justify-center w-full h-32 bg-gray-100 rounded-lg hover:bg-gray-200" do %>
|
|
75
|
+
<div class="text-center">
|
|
76
|
+
<svg class="mx-auto h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
77
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
|
78
|
+
</svg>
|
|
79
|
+
<span class="text-xs text-gray-500 mt-1 block truncate px-2">
|
|
80
|
+
<%= attachment.file.filename %>
|
|
81
|
+
</span>
|
|
82
|
+
</div>
|
|
83
|
+
<% end %>
|
|
84
|
+
<% end %>
|
|
85
|
+
</div>
|
|
86
|
+
<% end %>
|
|
87
|
+
<% end %>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<% end %>
|
|
91
|
+
|
|
92
|
+
<%# Related articles %>
|
|
93
|
+
<% if article.related_articles.any? %>
|
|
94
|
+
<div class="border-t pt-8">
|
|
95
|
+
<h3 class="text-lg font-semibold mb-4">Related Articles</h3>
|
|
96
|
+
<div class="grid gap-4">
|
|
97
|
+
<% article.related_articles.each do |related| %>
|
|
98
|
+
<%= link_to routes.article_path(related),
|
|
99
|
+
class: "block p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition" do %>
|
|
100
|
+
<h4 class="font-medium text-gray-900"><%= related.title %></h4>
|
|
101
|
+
<% if related.intro.present? %>
|
|
102
|
+
<p class="text-sm text-gray-500 mt-1 line-clamp-2"><%= related.intro %></p>
|
|
103
|
+
<% end %>
|
|
104
|
+
<% end %>
|
|
105
|
+
<% end %>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
<% end %>
|
|
109
|
+
</div>
|
|
110
|
+
<% end %>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven::Views::Articles::Show
|
|
4
|
+
class Component < Aven::ApplicationViewComponent
|
|
5
|
+
option :article
|
|
6
|
+
option :current_user, optional: true
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def routes
|
|
11
|
+
Aven::Engine.routes.url_helpers
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def status_badge
|
|
15
|
+
if article.published?
|
|
16
|
+
{ label: "Published", variant: :success }
|
|
17
|
+
elsif article.scheduled?
|
|
18
|
+
{ label: "Scheduled", variant: :warning }
|
|
19
|
+
else
|
|
20
|
+
{ label: "Draft", variant: :secondary }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def format_date(date)
|
|
25
|
+
return nil unless date
|
|
26
|
+
date.strftime("%B %d, %Y at %l:%M %p")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def rendered_description
|
|
30
|
+
# Simple markdown rendering - could use a proper markdown renderer
|
|
31
|
+
simple_format(article.description)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
|
2
|
+
<div class="max-w-md w-full space-y-8">
|
|
3
|
+
<div>
|
|
4
|
+
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
|
5
|
+
Authentication Failed
|
|
6
|
+
</h2>
|
|
7
|
+
<div class="mt-4 bg-red-50 border border-red-200 rounded-md p-4">
|
|
8
|
+
<div class="flex">
|
|
9
|
+
<div class="flex-shrink-0">
|
|
10
|
+
<svg class="h-5 w-5 text-red-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
11
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
|
12
|
+
</svg>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="ml-3">
|
|
15
|
+
<h3 class="text-sm font-medium text-red-800">
|
|
16
|
+
<%= error_message %>
|
|
17
|
+
</h3>
|
|
18
|
+
<% if error_class.present? %>
|
|
19
|
+
<p class="mt-2 text-xs text-red-700">
|
|
20
|
+
Error type: <%= error_class %>
|
|
21
|
+
</p>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="space-y-4">
|
|
29
|
+
<% provider_links.each do |provider| %>
|
|
30
|
+
<%= link_to(
|
|
31
|
+
"Try again with #{provider.to_s.capitalize}",
|
|
32
|
+
oauth_path_for(provider),
|
|
33
|
+
class: "group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
34
|
+
) %>
|
|
35
|
+
<% end %>
|
|
36
|
+
|
|
37
|
+
<%= link_to(
|
|
38
|
+
"Back to home",
|
|
39
|
+
home_path,
|
|
40
|
+
class: "group relative w-full flex justify-center py-2 px-4 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
41
|
+
) %>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Aven::Views::Oauth::Error
|
|
2
|
+
class Component < Aven::ApplicationViewComponent
|
|
3
|
+
option :error_message
|
|
4
|
+
option :error_class, optional: true
|
|
5
|
+
option :current_user, optional: true
|
|
6
|
+
|
|
7
|
+
def provider_links
|
|
8
|
+
Aven.configuration.oauth_providers.keys
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def oauth_path_for(provider)
|
|
12
|
+
case provider.to_sym
|
|
13
|
+
when :github
|
|
14
|
+
Aven::Engine.routes.url_helpers.oauth_github_path
|
|
15
|
+
when :google
|
|
16
|
+
Aven::Engine.routes.url_helpers.oauth_google_path
|
|
17
|
+
else
|
|
18
|
+
"#"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def home_path
|
|
23
|
+
if helpers.respond_to?(:main_app) && helpers.main_app.respond_to?(:root_path)
|
|
24
|
+
helpers.main_app.root_path
|
|
25
|
+
else
|
|
26
|
+
Aven::Engine.routes.url_helpers.root_path
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<%= ui("button", label: "ok") %>
|
|
2
|
+
|
|
3
|
+
<div data-controller="<%= controller_name %>"></div>
|
|
4
|
+
|
|
5
|
+
<% if current_user %>
|
|
6
|
+
<div><%= current_user.id %></div>
|
|
7
|
+
<%= link_to("Logout", Aven::Engine.routes.url_helpers.logout_path) %>
|
|
8
|
+
<% else %>
|
|
9
|
+
<% Aven.configuration.oauth_providers.each do |provider, config| %>
|
|
10
|
+
<%=
|
|
11
|
+
link_to(
|
|
12
|
+
"Login with #{provider.to_s.capitalize}",
|
|
13
|
+
oauth_path_for(provider)
|
|
14
|
+
)
|
|
15
|
+
%>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Aven::Views::Static::Index
|
|
2
|
+
class Component < Aven::ApplicationViewComponent
|
|
3
|
+
option(:current_user, optional: true)
|
|
4
|
+
|
|
5
|
+
def oauth_path_for(provider)
|
|
6
|
+
case provider.to_sym
|
|
7
|
+
when :github
|
|
8
|
+
Aven::Engine.routes.url_helpers.oauth_github_path
|
|
9
|
+
when :google
|
|
10
|
+
Aven::Engine.routes.url_helpers.oauth_google_path
|
|
11
|
+
else
|
|
12
|
+
"#"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Aven
|
|
2
|
+
module Admin
|
|
3
|
+
class Base < ApplicationController
|
|
4
|
+
layout("aven/admin")
|
|
5
|
+
before_action(:authenticate_admin!)
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def authenticate_admin!
|
|
10
|
+
unless current_user&.admin
|
|
11
|
+
redirect_to(root_path)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module Agentic
|
|
5
|
+
class AgentsController < Aven::ApplicationController
|
|
6
|
+
before_action :authenticate_user!
|
|
7
|
+
before_action :set_agent, only: [:show, :update, :destroy]
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@agents = current_workspace.aven_agentic_agents.enabled.order(:label)
|
|
11
|
+
render json: @agents
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show
|
|
15
|
+
render json: @agent.as_json(include: [:tools, :documents])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create
|
|
19
|
+
@agent = current_workspace.aven_agentic_agents.build(agent_params)
|
|
20
|
+
|
|
21
|
+
if @agent.save
|
|
22
|
+
render json: @agent, status: :created
|
|
23
|
+
else
|
|
24
|
+
render json: { errors: @agent.errors }, status: :unprocessable_entity
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update
|
|
29
|
+
if @agent.update(agent_params)
|
|
30
|
+
render json: @agent
|
|
31
|
+
else
|
|
32
|
+
render json: { errors: @agent.errors }, status: :unprocessable_entity
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def destroy
|
|
37
|
+
@agent.destroy
|
|
38
|
+
head :no_content
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def set_agent
|
|
44
|
+
@agent = current_workspace.aven_agentic_agents.find(params[:id])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def agent_params
|
|
48
|
+
params.require(:agent).permit(
|
|
49
|
+
:label, :system_prompt, :user_facing_question, :enabled,
|
|
50
|
+
agent_tools_attributes: [:id, :tool_id, :_destroy],
|
|
51
|
+
agent_documents_attributes: [:id, :document_id, :_destroy]
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module Agentic
|
|
5
|
+
class DocumentsController < Aven::ApplicationController
|
|
6
|
+
before_action :authenticate_user!
|
|
7
|
+
before_action :set_document, only: [:show, :destroy]
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@documents = current_workspace.aven_agentic_documents.recent
|
|
11
|
+
render json: @documents
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show
|
|
15
|
+
render json: @document
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create
|
|
19
|
+
@document = current_workspace.aven_agentic_documents.build(document_params)
|
|
20
|
+
|
|
21
|
+
if params[:file].present?
|
|
22
|
+
@document.file.attach(params[:file])
|
|
23
|
+
@document.filename = params[:file].original_filename
|
|
24
|
+
@document.content_type = params[:file].content_type
|
|
25
|
+
@document.byte_size = params[:file].size
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if @document.save
|
|
29
|
+
render json: @document, status: :created
|
|
30
|
+
else
|
|
31
|
+
render json: { errors: @document.errors }, status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy
|
|
36
|
+
@document.destroy
|
|
37
|
+
head :no_content
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_document
|
|
43
|
+
@document = current_workspace.aven_agentic_documents.find(params[:id])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def document_params
|
|
47
|
+
params.permit(:filename)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module Agentic
|
|
5
|
+
class McpController < Aven::ApplicationController
|
|
6
|
+
include ActionController::Live
|
|
7
|
+
|
|
8
|
+
skip_before_action :verify_authenticity_token
|
|
9
|
+
before_action :authenticate_mcp_request
|
|
10
|
+
|
|
11
|
+
# Single endpoint handling all MCP methods
|
|
12
|
+
def handle
|
|
13
|
+
case request.method
|
|
14
|
+
when "POST" then handle_post
|
|
15
|
+
when "GET" then handle_sse
|
|
16
|
+
when "DELETE" then handle_delete
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Health check endpoint
|
|
21
|
+
def health
|
|
22
|
+
render json: {
|
|
23
|
+
status: "ok",
|
|
24
|
+
server: Mcp::ServerFactory::SERVER_NAME,
|
|
25
|
+
version: Mcp::ServerFactory::SERVER_VERSION,
|
|
26
|
+
timestamp: Time.current.iso8601
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def handle_post
|
|
33
|
+
server = build_server
|
|
34
|
+
return render_mcp_error("Server not available", -32603) unless server
|
|
35
|
+
|
|
36
|
+
request_body = request.body.read
|
|
37
|
+
Rails.logger.debug { "[Aven::MCP] Request: #{request_body}" }
|
|
38
|
+
|
|
39
|
+
result_json = server.handle_json(request_body)
|
|
40
|
+
|
|
41
|
+
if result_json.nil?
|
|
42
|
+
return head :no_content
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Rails.logger.debug { "[Aven::MCP] Response: #{result_json}" }
|
|
46
|
+
|
|
47
|
+
result_hash = JSON.parse(result_json)
|
|
48
|
+
if initialization_response?(result_hash)
|
|
49
|
+
response.headers["Mcp-Session-Id"] = SecureRandom.uuid
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
render json: result_json, status: :ok
|
|
53
|
+
rescue JSON::ParserError => e
|
|
54
|
+
render_mcp_error("Parse error: #{e.message}", -32700, status: :bad_request)
|
|
55
|
+
rescue => e
|
|
56
|
+
Rails.logger.error { "[Aven::MCP] Error: #{e.message}" }
|
|
57
|
+
render_mcp_error("Internal error", -32603, status: :internal_server_error)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def handle_sse
|
|
61
|
+
response.headers["Content-Type"] = "text/event-stream"
|
|
62
|
+
response.headers["Cache-Control"] = "no-cache"
|
|
63
|
+
response.headers["Connection"] = "keep-alive"
|
|
64
|
+
|
|
65
|
+
sse = ActionController::Live::SSE.new(response.stream, event: "message")
|
|
66
|
+
session_id = request.headers["Mcp-Session-Id"]
|
|
67
|
+
|
|
68
|
+
loop do
|
|
69
|
+
sse.write({ type: "ping", timestamp: Time.current.iso8601 })
|
|
70
|
+
sleep 30
|
|
71
|
+
end
|
|
72
|
+
rescue ActionController::Live::ClientDisconnected
|
|
73
|
+
Rails.logger.info { "[Aven::MCP] SSE client disconnected" }
|
|
74
|
+
ensure
|
|
75
|
+
sse&.close
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def handle_delete
|
|
79
|
+
head :ok
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_server
|
|
83
|
+
Mcp::ServerFactory.build(server_context: {
|
|
84
|
+
workspace: @workspace,
|
|
85
|
+
api_token: @api_token
|
|
86
|
+
})
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def authenticate_mcp_request
|
|
90
|
+
token = extract_token
|
|
91
|
+
return render_mcp_error("Unauthorized", -32001, status: :unauthorized) if token.blank?
|
|
92
|
+
|
|
93
|
+
@api_token = validate_token(token)
|
|
94
|
+
render_mcp_error("Unauthorized", -32001, status: :unauthorized) unless @api_token
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def extract_token
|
|
98
|
+
auth_header = request.headers["Authorization"]
|
|
99
|
+
return auth_header.delete_prefix("Bearer ").strip if auth_header&.start_with?("Bearer ")
|
|
100
|
+
|
|
101
|
+
request.headers["X-Api-Key"]&.strip || params[:token]&.strip
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def validate_token(token)
|
|
105
|
+
env_token = ENV.fetch("AVEN_MCP_API_TOKEN", nil)
|
|
106
|
+
if env_token.present? && ActiveSupport::SecurityUtils.secure_compare(token, env_token)
|
|
107
|
+
OpenStruct.new(valid: true, source: :env)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def render_mcp_error(message, code, status: :ok)
|
|
112
|
+
render json: {
|
|
113
|
+
jsonrpc: "2.0",
|
|
114
|
+
error: { code:, message: },
|
|
115
|
+
id: nil
|
|
116
|
+
}, status:
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def initialization_response?(result)
|
|
120
|
+
result.dig("result", "protocolVersion").present?
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module Agentic
|
|
5
|
+
class ToolsController < Aven::ApplicationController
|
|
6
|
+
before_action :authenticate_user!
|
|
7
|
+
before_action :set_tool, only: [:show, :update]
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@tools = Aven::Agentic::Tool.for_workspace(current_workspace).enabled.order(:name)
|
|
11
|
+
render json: @tools.as_json(include: :parameters)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show
|
|
15
|
+
render json: @tool.as_json(include: :parameters)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update
|
|
19
|
+
if @tool.update(tool_params)
|
|
20
|
+
render json: @tool
|
|
21
|
+
else
|
|
22
|
+
render json: { errors: @tool.errors }, status: :unprocessable_entity
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def set_tool
|
|
29
|
+
@tool = Aven::Agentic::Tool.for_workspace(current_workspace).find(params[:id])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tool_params
|
|
33
|
+
params.require(:tool).permit(:description, :enabled)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aven
|
|
4
|
+
module Ai
|
|
5
|
+
class TextController < Aven::ApplicationController
|
|
6
|
+
include Aven::Authentication
|
|
7
|
+
include ActionController::Live
|
|
8
|
+
|
|
9
|
+
before_action :authenticate_user!
|
|
10
|
+
|
|
11
|
+
def generate
|
|
12
|
+
response.headers["Content-Type"] = "text/event-stream"
|
|
13
|
+
response.headers["Cache-Control"] = "no-cache"
|
|
14
|
+
response.headers["X-Accel-Buffering"] = "no"
|
|
15
|
+
|
|
16
|
+
model = params[:model] || "gpt-4o-mini"
|
|
17
|
+
chat = RubyLLM.chat(model:)
|
|
18
|
+
|
|
19
|
+
# Apply system prompts if provided
|
|
20
|
+
if params[:system_prompts].present?
|
|
21
|
+
Array(params[:system_prompts]).each do |system_prompt|
|
|
22
|
+
chat.with_instructions(system_prompt)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Stream the response
|
|
27
|
+
chat.ask(params[:prompt]) do |chunk|
|
|
28
|
+
content = chunk.respond_to?(:content) ? chunk.content : chunk.to_s
|
|
29
|
+
response.stream.write "data: #{content.to_json}\n\n"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
response.stream.write "data: [DONE]\n\n"
|
|
33
|
+
rescue => e
|
|
34
|
+
Rails.logger.error "AI generation error: #{e.message}"
|
|
35
|
+
response.stream.write "data: #{JSON.generate(error: e.message)}\n\n"
|
|
36
|
+
ensure
|
|
37
|
+
response.stream.close
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Aven
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
include Aven::ApplicationHelper
|
|
4
|
+
|
|
5
|
+
helper_method :current_workspace
|
|
6
|
+
|
|
7
|
+
# Get the current workspace from session
|
|
8
|
+
def current_workspace
|
|
9
|
+
return @current_workspace if defined?(@current_workspace)
|
|
10
|
+
|
|
11
|
+
@current_workspace = if session[:workspace_id].present? && current_user
|
|
12
|
+
current_user.workspaces.find_by(id: session[:workspace_id])
|
|
13
|
+
elsif current_user
|
|
14
|
+
# Auto-select first workspace if none selected
|
|
15
|
+
workspace = current_user.workspaces.first
|
|
16
|
+
session[:workspace_id] = workspace&.id
|
|
17
|
+
workspace
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Set the current workspace
|
|
22
|
+
def current_workspace=(workspace)
|
|
23
|
+
@current_workspace = workspace
|
|
24
|
+
session[:workspace_id] = workspace&.id
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|