rubyn 0.1.0
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/LICENSE +21 -0
- data/README.md +251 -0
- data/Rakefile +12 -0
- data/exe/rubyn +5 -0
- data/lib/generators/rubyn/install_generator.rb +16 -0
- data/lib/rubyn/cli.rb +85 -0
- data/lib/rubyn/client/api_client.rb +172 -0
- data/lib/rubyn/commands/agent.rb +191 -0
- data/lib/rubyn/commands/base.rb +60 -0
- data/lib/rubyn/commands/config.rb +51 -0
- data/lib/rubyn/commands/dashboard.rb +85 -0
- data/lib/rubyn/commands/index.rb +101 -0
- data/lib/rubyn/commands/init.rb +166 -0
- data/lib/rubyn/commands/refactor.rb +175 -0
- data/lib/rubyn/commands/review.rb +61 -0
- data/lib/rubyn/commands/spec.rb +72 -0
- data/lib/rubyn/commands/usage.rb +56 -0
- data/lib/rubyn/config/credentials.rb +39 -0
- data/lib/rubyn/config/project_config.rb +42 -0
- data/lib/rubyn/config/settings.rb +53 -0
- data/lib/rubyn/context/codebase_indexer.rb +195 -0
- data/lib/rubyn/context/context_builder.rb +36 -0
- data/lib/rubyn/context/file_resolver.rb +235 -0
- data/lib/rubyn/context/project_scanner.rb +132 -0
- data/lib/rubyn/engine/app/assets/images/rubyn/RubynLogo.png +0 -0
- data/lib/rubyn/engine/app/assets/javascripts/rubyn/application.js +579 -0
- data/lib/rubyn/engine/app/assets/stylesheets/rubyn/application.css +1073 -0
- data/lib/rubyn/engine/app/controllers/rubyn/agent_controller.rb +26 -0
- data/lib/rubyn/engine/app/controllers/rubyn/application_controller.rb +44 -0
- data/lib/rubyn/engine/app/controllers/rubyn/dashboard_controller.rb +19 -0
- data/lib/rubyn/engine/app/controllers/rubyn/feedback_controller.rb +17 -0
- data/lib/rubyn/engine/app/controllers/rubyn/files_controller.rb +54 -0
- data/lib/rubyn/engine/app/controllers/rubyn/refactor_controller.rb +56 -0
- data/lib/rubyn/engine/app/controllers/rubyn/reviews_controller.rb +32 -0
- data/lib/rubyn/engine/app/controllers/rubyn/settings_controller.rb +17 -0
- data/lib/rubyn/engine/app/controllers/rubyn/specs_controller.rb +33 -0
- data/lib/rubyn/engine/app/views/layouts/rubyn/application.html.erb +63 -0
- data/lib/rubyn/engine/app/views/rubyn/agent/show.html.erb +22 -0
- data/lib/rubyn/engine/app/views/rubyn/dashboard/index.html.erb +120 -0
- data/lib/rubyn/engine/app/views/rubyn/files/index.html.erb +45 -0
- data/lib/rubyn/engine/app/views/rubyn/refactor/show.html.erb +28 -0
- data/lib/rubyn/engine/app/views/rubyn/reviews/show.html.erb +28 -0
- data/lib/rubyn/engine/app/views/rubyn/settings/show.html.erb +42 -0
- data/lib/rubyn/engine/app/views/rubyn/specs/show.html.erb +28 -0
- data/lib/rubyn/engine/config/routes.rb +13 -0
- data/lib/rubyn/engine/engine.rb +18 -0
- data/lib/rubyn/output/diff_renderer.rb +106 -0
- data/lib/rubyn/output/formatter.rb +123 -0
- data/lib/rubyn/output/spinner.rb +26 -0
- data/lib/rubyn/tools/base_tool.rb +74 -0
- data/lib/rubyn/tools/bundle_add.rb +77 -0
- data/lib/rubyn/tools/create_file.rb +32 -0
- data/lib/rubyn/tools/delete_file.rb +29 -0
- data/lib/rubyn/tools/executor.rb +68 -0
- data/lib/rubyn/tools/find_files.rb +33 -0
- data/lib/rubyn/tools/find_references.rb +72 -0
- data/lib/rubyn/tools/git_commit.rb +65 -0
- data/lib/rubyn/tools/git_create_branch.rb +58 -0
- data/lib/rubyn/tools/git_diff.rb +42 -0
- data/lib/rubyn/tools/git_log.rb +43 -0
- data/lib/rubyn/tools/git_status.rb +26 -0
- data/lib/rubyn/tools/list_directory.rb +82 -0
- data/lib/rubyn/tools/move_file.rb +35 -0
- data/lib/rubyn/tools/patch_file.rb +47 -0
- data/lib/rubyn/tools/rails_generate.rb +40 -0
- data/lib/rubyn/tools/rails_migrate.rb +55 -0
- data/lib/rubyn/tools/rails_routes.rb +35 -0
- data/lib/rubyn/tools/read_file.rb +45 -0
- data/lib/rubyn/tools/registry.rb +28 -0
- data/lib/rubyn/tools/run_command.rb +48 -0
- data/lib/rubyn/tools/run_tests.rb +52 -0
- data/lib/rubyn/tools/search_files.rb +82 -0
- data/lib/rubyn/tools/write_file.rb +30 -0
- data/lib/rubyn/version.rb +5 -0
- data/lib/rubyn/version_checker.rb +74 -0
- data/lib/rubyn.rb +95 -0
- data/sig/rubyn.rbs +4 -0
- metadata +379 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class AgentController < ApplicationController
|
|
5
|
+
def show
|
|
6
|
+
@conversations = begin
|
|
7
|
+
api_client.list_conversations(project_token: project_token)
|
|
8
|
+
rescue Rubyn::Error
|
|
9
|
+
[]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
result = api_client.agent_message(
|
|
15
|
+
conversation_id: params[:conversation_id]&.to_i,
|
|
16
|
+
message: params[:message],
|
|
17
|
+
file_context: [],
|
|
18
|
+
project_token: project_token
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
render json: result
|
|
22
|
+
rescue Rubyn::Error => e
|
|
23
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class ApplicationController < ActionController::Base
|
|
5
|
+
layout "rubyn/application"
|
|
6
|
+
|
|
7
|
+
protect_from_forgery with: :null_session
|
|
8
|
+
|
|
9
|
+
before_action :check_for_gem_update
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def check_for_gem_update
|
|
14
|
+
@update_message = Rubyn::VersionChecker.update_message
|
|
15
|
+
rescue Rubyn::Error, Net::HTTPError, SocketError, Timeout::Error
|
|
16
|
+
@update_message = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def api_client
|
|
20
|
+
@api_client ||= Rubyn.api_client
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def project_config
|
|
24
|
+
@project_config ||= Config::ProjectConfig.read
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def project_id
|
|
28
|
+
project_config["project_id"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def project_token
|
|
32
|
+
project_config["project_token"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_context_files(file_path)
|
|
36
|
+
resolver = Context::FileResolver.new(Rails.root.to_s)
|
|
37
|
+
related = resolver.resolve(file_path)
|
|
38
|
+
builder = Context::ContextBuilder.new(Rails.root.to_s)
|
|
39
|
+
context = builder.build(file_path, related)
|
|
40
|
+
context.reject { |path, _| path == file_path }
|
|
41
|
+
.map { |path, content| { file_path: path, code: content } }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class DashboardController < ApplicationController
|
|
5
|
+
def index
|
|
6
|
+
@balance = begin
|
|
7
|
+
api_client.get_balance
|
|
8
|
+
rescue Rubyn::Error
|
|
9
|
+
{}
|
|
10
|
+
end
|
|
11
|
+
@history = begin
|
|
12
|
+
api_client.get_history(page: 1)
|
|
13
|
+
rescue Rubyn::Error
|
|
14
|
+
{}
|
|
15
|
+
end
|
|
16
|
+
@project = project_config
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class FeedbackController < ApplicationController
|
|
5
|
+
def create
|
|
6
|
+
result = api_client.submit_feedback(
|
|
7
|
+
interaction_id: params[:interaction_id].to_i,
|
|
8
|
+
rating: params[:rating],
|
|
9
|
+
feedback: params[:feedback]
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
render json: result
|
|
13
|
+
rescue Rubyn::Error => e
|
|
14
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class FilesController < ApplicationController
|
|
5
|
+
CATEGORIES = {
|
|
6
|
+
"Models" => "app/models/**/*.rb",
|
|
7
|
+
"Controllers" => "app/controllers/**/*.rb",
|
|
8
|
+
"Views" => "app/views/**/*.{erb,haml,slim}",
|
|
9
|
+
"Helpers" => "app/helpers/**/*.rb",
|
|
10
|
+
"Services" => "app/services/**/*.rb",
|
|
11
|
+
"Query Objects" => "app/queries/**/*.rb",
|
|
12
|
+
"Jobs" => "app/jobs/**/*.rb",
|
|
13
|
+
"Mailers" => "app/mailers/**/*.rb",
|
|
14
|
+
"Channels" => "app/channels/**/*.rb",
|
|
15
|
+
"Serializers" => "app/serializers/**/*.rb",
|
|
16
|
+
"Policies" => "app/policies/**/*.rb",
|
|
17
|
+
"Lib" => "lib/**/*.rb",
|
|
18
|
+
"Config" => "config/**/*.rb",
|
|
19
|
+
"Specs" => "spec/**/*.rb",
|
|
20
|
+
"Tests" => "test/**/*.rb"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
def index
|
|
24
|
+
all_files = scan_ruby_files
|
|
25
|
+
@categories = categorize(all_files)
|
|
26
|
+
@total_count = all_files.size
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def scan_ruby_files
|
|
32
|
+
Dir.glob(File.join(Rails.root, "**/*.{rb,erb,haml,slim}"))
|
|
33
|
+
.reject { |f| f.include?("/vendor/") || f.include?("/node_modules/") }
|
|
34
|
+
.map { |f| f.sub("#{Rails.root}/", "") }
|
|
35
|
+
.sort
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def categorize(files)
|
|
39
|
+
categorized = {}
|
|
40
|
+
remaining = files.dup
|
|
41
|
+
|
|
42
|
+
CATEGORIES.each do |label, pattern|
|
|
43
|
+
matched = remaining.select { |f| File.fnmatch?(pattern, f, File::FNM_PATHNAME) }
|
|
44
|
+
next if matched.empty?
|
|
45
|
+
|
|
46
|
+
categorized[label] = matched
|
|
47
|
+
remaining -= matched
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
categorized["Other"] = remaining if remaining.any?
|
|
51
|
+
categorized
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class RefactorController < ApplicationController
|
|
5
|
+
def show
|
|
6
|
+
@file = params[:file]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
file_path = params[:file]
|
|
11
|
+
full_path = Rails.root.join(file_path)
|
|
12
|
+
|
|
13
|
+
unless File.exist?(full_path)
|
|
14
|
+
render json: { error: "File not found: #{file_path}" }, status: :not_found
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
code = File.read(full_path)
|
|
19
|
+
context_files = build_context_files(file_path)
|
|
20
|
+
|
|
21
|
+
result = api_client.refactor(
|
|
22
|
+
file_path: file_path,
|
|
23
|
+
code: code,
|
|
24
|
+
context_files: context_files,
|
|
25
|
+
project_token: project_token
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
render json: result
|
|
29
|
+
rescue Rubyn::Error => e
|
|
30
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update
|
|
34
|
+
file_path = params[:file]
|
|
35
|
+
code = params[:code]
|
|
36
|
+
|
|
37
|
+
unless file_path.present? && code.present?
|
|
38
|
+
render json: { error: "Missing file path or code" }, status: :bad_request
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
full_path = Rails.root.join(file_path)
|
|
43
|
+
|
|
44
|
+
# Create directories for new files (e.g., app/services/orders/)
|
|
45
|
+
FileUtils.mkdir_p(File.dirname(full_path))
|
|
46
|
+
|
|
47
|
+
created = !File.exist?(full_path)
|
|
48
|
+
File.write(full_path, code)
|
|
49
|
+
|
|
50
|
+
action = created ? "Created" : "Updated"
|
|
51
|
+
render json: { success: true, message: "#{action} #{file_path}" }
|
|
52
|
+
rescue Errno::EACCES, Errno::ENOENT, Errno::ENOSPC => e
|
|
53
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class ReviewsController < ApplicationController
|
|
5
|
+
def show
|
|
6
|
+
@file = params[:file]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
file_path = params[:file]
|
|
11
|
+
full_path = Rails.root.join(file_path)
|
|
12
|
+
|
|
13
|
+
unless File.exist?(full_path)
|
|
14
|
+
render json: { error: "File not found: #{file_path}" }, status: :not_found
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
code = File.read(full_path)
|
|
19
|
+
context_files = build_context_files(file_path)
|
|
20
|
+
|
|
21
|
+
result = api_client.review(
|
|
22
|
+
files: [{ file_path: file_path, code: code }],
|
|
23
|
+
context_files: context_files,
|
|
24
|
+
project_token: project_token
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
render json: result
|
|
28
|
+
rescue Rubyn::Error => e
|
|
29
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class SettingsController < ApplicationController
|
|
5
|
+
def show
|
|
6
|
+
@settings = Config::Settings.all
|
|
7
|
+
@credentials_exist = Config::Credentials.exists?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def update
|
|
11
|
+
params.permit(:default_model, :auto_apply, :output_format).each do |key, value|
|
|
12
|
+
Config::Settings.set(key, value)
|
|
13
|
+
end
|
|
14
|
+
redirect_to rubyn.settings_path, notice: "Settings updated."
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyn
|
|
4
|
+
class SpecsController < ApplicationController
|
|
5
|
+
def show
|
|
6
|
+
@file = params[:file]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
file_path = params[:file]
|
|
11
|
+
full_path = Rails.root.join(file_path)
|
|
12
|
+
|
|
13
|
+
unless File.exist?(full_path)
|
|
14
|
+
render json: { error: "File not found: #{file_path}" }, status: :not_found
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
code = File.read(full_path)
|
|
19
|
+
context_files = build_context_files(file_path)
|
|
20
|
+
|
|
21
|
+
result = api_client.generate_spec(
|
|
22
|
+
file_path: file_path,
|
|
23
|
+
code: code,
|
|
24
|
+
context_files: context_files,
|
|
25
|
+
project_token: project_token
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
render json: result
|
|
29
|
+
rescue Rubyn::Error => e
|
|
30
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Rubyn - <%= yield(:title) || "Dashboard" %></title>
|
|
7
|
+
<%= stylesheet_link_tag "rubyn/application", media: "all" %>
|
|
8
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css" />
|
|
9
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
10
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/ruby.min.js"></script>
|
|
11
|
+
<%= javascript_include_tag "rubyn/application" %>
|
|
12
|
+
<%= csrf_meta_tags %>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div class="rubyn-shell">
|
|
16
|
+
<aside class="rubyn-sidebar">
|
|
17
|
+
<div class="rubyn-sidebar-brand">
|
|
18
|
+
<%= link_to rubyn.root_path do %>
|
|
19
|
+
<%= image_tag "rubyn/RubynLogo.png", class: "rubyn-logo-img", alt: "Rubyn" %> Rubyn
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<nav class="rubyn-sidebar-nav">
|
|
24
|
+
<%= link_to rubyn.root_path, class: ("active" if current_page?(rubyn.root_path)) do %>
|
|
25
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
|
26
|
+
Dashboard
|
|
27
|
+
<% end %>
|
|
28
|
+
<%= link_to rubyn.files_path, class: ("active" if current_page?(rubyn.files_path)) do %>
|
|
29
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>
|
|
30
|
+
Files
|
|
31
|
+
<% end %>
|
|
32
|
+
<%= link_to rubyn.agent_path, class: ("active" if current_page?(rubyn.agent_path)) do %>
|
|
33
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
34
|
+
Agent
|
|
35
|
+
<% end %>
|
|
36
|
+
<%= link_to rubyn.settings_path, class: ("active" if current_page?(rubyn.settings_path)) do %>
|
|
37
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
38
|
+
Settings
|
|
39
|
+
<% end %>
|
|
40
|
+
</nav>
|
|
41
|
+
|
|
42
|
+
<div class="rubyn-sidebar-footer">
|
|
43
|
+
Rubyn v<%= Rubyn::VERSION %>
|
|
44
|
+
</div>
|
|
45
|
+
</aside>
|
|
46
|
+
|
|
47
|
+
<div class="rubyn-content">
|
|
48
|
+
<main class="rubyn-main">
|
|
49
|
+
<% if @update_message %>
|
|
50
|
+
<div class="rubyn-update-banner">
|
|
51
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
52
|
+
<%= @update_message %>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% if notice %>
|
|
56
|
+
<div class="rubyn-notice"><%= notice %></div>
|
|
57
|
+
<% end %>
|
|
58
|
+
<%= yield %>
|
|
59
|
+
</main>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<% content_for(:title) { "Agent" } %>
|
|
2
|
+
|
|
3
|
+
<div class="rubyn-page-header">
|
|
4
|
+
<h1>Agent</h1>
|
|
5
|
+
<p>Ask anything about your codebase</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="rubyn-agent-container">
|
|
9
|
+
<div class="rubyn-chat" id="chat-container">
|
|
10
|
+
<div class="rubyn-messages" id="messages">
|
|
11
|
+
<div class="rubyn-empty-chat" id="empty-state">
|
|
12
|
+
<svg class="rubyn-empty-chat-icon" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.3"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
13
|
+
<p>Start a conversation with Rubyn</p>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<form class="rubyn-chat-input" id="chat-form">
|
|
18
|
+
<textarea id="message-input" placeholder="Ask Rubyn anything about your code..." rows="2"></textarea>
|
|
19
|
+
<button type="submit" class="rubyn-btn">Send</button>
|
|
20
|
+
</form>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<% content_for(:title) { "Dashboard" } %>
|
|
2
|
+
|
|
3
|
+
<div class="rubyn-page-header">
|
|
4
|
+
<h1>Dashboard</h1>
|
|
5
|
+
<p>Your project at a glance</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="rubyn-cards">
|
|
9
|
+
<div class="rubyn-card rubyn-card--credits">
|
|
10
|
+
<h3>Credits</h3>
|
|
11
|
+
<p class="rubyn-stat primary"><%= @balance["balance"] rescue "N/A" %></p>
|
|
12
|
+
<% if @balance["credit_allowance"] %>
|
|
13
|
+
<p class="rubyn-label">of <%= @balance["credit_allowance"] %> · resets <%= @balance["reset_period"] %></p>
|
|
14
|
+
<% else %>
|
|
15
|
+
<p class="rubyn-label">remaining</p>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="rubyn-card rubyn-card--plan">
|
|
20
|
+
<h3>Plan</h3>
|
|
21
|
+
<p class="rubyn-stat success"><%= @balance["plan"]&.capitalize rescue "N/A" %></p>
|
|
22
|
+
<% if @balance["resets_at"] %>
|
|
23
|
+
<p class="rubyn-label">resets <%= Date.parse(@balance["resets_at"]).strftime("%b %-d") rescue @balance["resets_at"] %></p>
|
|
24
|
+
<% end %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="rubyn-card rubyn-card--project">
|
|
28
|
+
<h3>Project</h3>
|
|
29
|
+
<p class="rubyn-stat"><%= @project["project_id"] rescue "Not configured" %></p>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<% interactions = @history["interactions"] || [] %>
|
|
34
|
+
<% if interactions.any? %>
|
|
35
|
+
<div class="rubyn-usage-section">
|
|
36
|
+
<h2>Recent Usage</h2>
|
|
37
|
+
<table class="rubyn-usage-table">
|
|
38
|
+
<thead>
|
|
39
|
+
<tr>
|
|
40
|
+
<th>Action</th>
|
|
41
|
+
<th>Credits</th>
|
|
42
|
+
<th>Time</th>
|
|
43
|
+
</tr>
|
|
44
|
+
</thead>
|
|
45
|
+
<tbody>
|
|
46
|
+
<% interactions.first(10).each do |i| %>
|
|
47
|
+
<tr>
|
|
48
|
+
<td><span class="rubyn-usage-command"><%= i["command"] %></span></td>
|
|
49
|
+
<td><%= i["credits_charged"] %></td>
|
|
50
|
+
<td class="rubyn-text-dim"><%= Time.parse(i["created_at"]).strftime("%b %-d, %-I:%M%P") rescue i["created_at"] %></td>
|
|
51
|
+
</tr>
|
|
52
|
+
<% end %>
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
|
|
58
|
+
<div class="rubyn-actions">
|
|
59
|
+
<h2>Quick Actions</h2>
|
|
60
|
+
<div class="rubyn-actions-grid">
|
|
61
|
+
<%= link_to rubyn.files_path, class: "rubyn-action-card" do %>
|
|
62
|
+
<span class="rubyn-action-icon rubyn-action-icon--files">
|
|
63
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>
|
|
64
|
+
</span>
|
|
65
|
+
Browse Files
|
|
66
|
+
<% end %>
|
|
67
|
+
<%= link_to rubyn.agent_path, class: "rubyn-action-card" do %>
|
|
68
|
+
<span class="rubyn-action-icon rubyn-action-icon--agent">
|
|
69
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
70
|
+
</span>
|
|
71
|
+
Start Agent
|
|
72
|
+
<% end %>
|
|
73
|
+
<%= link_to rubyn.settings_path, class: "rubyn-action-card" do %>
|
|
74
|
+
<span class="rubyn-action-icon rubyn-action-icon--settings">
|
|
75
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9"/></svg>
|
|
76
|
+
</span>
|
|
77
|
+
Settings
|
|
78
|
+
<% end %>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div class="rubyn-actions" id="recent-results" style="display:none;">
|
|
83
|
+
<h2>Pending Results</h2>
|
|
84
|
+
<p class="rubyn-label" style="margin-bottom: 1rem;">You have unsaved refactoring results. Click to review and apply.</p>
|
|
85
|
+
<div class="rubyn-actions-grid" id="recent-results-grid"></div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
90
|
+
var grid = document.getElementById("recent-results-grid");
|
|
91
|
+
var section = document.getElementById("recent-results");
|
|
92
|
+
var found = 0;
|
|
93
|
+
|
|
94
|
+
for (var i = 0; i < sessionStorage.length; i++) {
|
|
95
|
+
var key = sessionStorage.key(i);
|
|
96
|
+
if (!key.startsWith("rubyn:")) continue;
|
|
97
|
+
|
|
98
|
+
var parts = key.split(":");
|
|
99
|
+
var resource = parts[1];
|
|
100
|
+
var file = parts.slice(2).join(":");
|
|
101
|
+
|
|
102
|
+
var label = resource.charAt(0).toUpperCase() + resource.slice(1);
|
|
103
|
+
var mountPath = "<%= rubyn.root_path.chomp('/') %>";
|
|
104
|
+
var href = mountPath + "/" + resource + "?file=" + encodeURIComponent(file);
|
|
105
|
+
|
|
106
|
+
var card = document.createElement("a");
|
|
107
|
+
card.href = href;
|
|
108
|
+
card.className = "rubyn-action-card";
|
|
109
|
+
card.innerHTML = '<span class="rubyn-action-icon rubyn-action-icon--files">' +
|
|
110
|
+
'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>' +
|
|
111
|
+
'</span>' +
|
|
112
|
+
'<div><strong>' + label + '</strong><br><span style="font-size:0.75rem;opacity:0.7;font-family:monospace;">' + file + '</span></div>';
|
|
113
|
+
|
|
114
|
+
grid.appendChild(card);
|
|
115
|
+
found++;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (found > 0) section.style.display = "block";
|
|
119
|
+
});
|
|
120
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<% content_for(:title) { "Files" } %>
|
|
2
|
+
|
|
3
|
+
<div class="rubyn-page-header">
|
|
4
|
+
<h1>Project Files</h1>
|
|
5
|
+
<p><%= @total_count %> files found across <%= @categories.size %> categories</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<% @categories.each do |category, files| %>
|
|
9
|
+
<div class="rubyn-file-category">
|
|
10
|
+
<button class="rubyn-file-category-header" onclick="toggleCategory(this)">
|
|
11
|
+
<span class="rubyn-file-category-title">
|
|
12
|
+
<svg class="rubyn-file-category-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
|
13
|
+
<%= category %>
|
|
14
|
+
</span>
|
|
15
|
+
<span class="rubyn-file-category-count"><%= files.size %></span>
|
|
16
|
+
</button>
|
|
17
|
+
|
|
18
|
+
<div class="rubyn-file-list">
|
|
19
|
+
<% files.each do |file| %>
|
|
20
|
+
<div class="rubyn-file-item">
|
|
21
|
+
<span class="rubyn-file-name">
|
|
22
|
+
<svg class="rubyn-file-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
23
|
+
<%= file %>
|
|
24
|
+
</span>
|
|
25
|
+
<% if file.end_with?(".rb") %>
|
|
26
|
+
<div class="rubyn-file-actions">
|
|
27
|
+
<%= link_to "Refactor", rubyn.refactor_path(file: file), class: "rubyn-btn-sm", target: "_blank" %>
|
|
28
|
+
<%= link_to "Spec", rubyn.specs_path(file: file), class: "rubyn-btn-sm", target: "_blank" %>
|
|
29
|
+
<%= link_to "Review", rubyn.reviews_path(file: file), class: "rubyn-btn-sm", target: "_blank" %>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<% end %>
|
|
37
|
+
|
|
38
|
+
<script>
|
|
39
|
+
function toggleCategory(button) {
|
|
40
|
+
var list = button.nextElementSibling;
|
|
41
|
+
var chevron = button.querySelector(".rubyn-file-category-chevron");
|
|
42
|
+
list.classList.toggle("rubyn-file-list--collapsed");
|
|
43
|
+
chevron.classList.toggle("rubyn-file-category-chevron--collapsed");
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<% content_for(:title) { "Refactor" } %>
|
|
2
|
+
|
|
3
|
+
<div class="rubyn-page-header">
|
|
4
|
+
<h1>Refactor</h1>
|
|
5
|
+
<p>AI-powered refactoring suggestions</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<% if @file %>
|
|
9
|
+
<div class="rubyn-tool-container">
|
|
10
|
+
<div class="rubyn-tool-header">
|
|
11
|
+
<span class="rubyn-tool-filepath">
|
|
12
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
13
|
+
<%= @file %>
|
|
14
|
+
</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="rubyn-tool-body" id="diff-container">
|
|
17
|
+
<div class="rubyn-loading">
|
|
18
|
+
<span class="rubyn-spinner"></span>
|
|
19
|
+
Generating refactoring suggestions<span class="rubyn-dots"><span></span><span></span><span></span></span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<% else %>
|
|
24
|
+
<div class="rubyn-tool-empty">
|
|
25
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.3"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
26
|
+
<p>Select a file from the <%= link_to "file browser", rubyn.files_path %>.</p>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<% content_for(:title) { "Review" } %>
|
|
2
|
+
|
|
3
|
+
<div class="rubyn-page-header">
|
|
4
|
+
<h1>Code Review</h1>
|
|
5
|
+
<p>AI-powered code analysis</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<% if @file %>
|
|
9
|
+
<div class="rubyn-tool-container">
|
|
10
|
+
<div class="rubyn-tool-header">
|
|
11
|
+
<span class="rubyn-tool-filepath">
|
|
12
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
13
|
+
<%= @file %>
|
|
14
|
+
</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="rubyn-tool-body" id="review-container">
|
|
17
|
+
<div class="rubyn-loading">
|
|
18
|
+
<span class="rubyn-spinner"></span>
|
|
19
|
+
Reviewing code<span class="rubyn-dots"><span></span><span></span><span></span></span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<% else %>
|
|
24
|
+
<div class="rubyn-tool-empty">
|
|
25
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.3"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
|
26
|
+
<p>Select a file from the <%= link_to "file browser", rubyn.files_path %>.</p>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<% content_for(:title) { "Settings" } %>
|
|
2
|
+
|
|
3
|
+
<div class="rubyn-page-header">
|
|
4
|
+
<h1>Settings</h1>
|
|
5
|
+
<p>Configure your Rubyn installation</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="rubyn-settings-grid">
|
|
9
|
+
<div class="rubyn-setting-card">
|
|
10
|
+
<h3>API Key</h3>
|
|
11
|
+
<p>Authentication status for the Rubyn API</p>
|
|
12
|
+
<% if @credentials_exist %>
|
|
13
|
+
<span class="rubyn-status-badge rubyn-status-badge--ok">
|
|
14
|
+
<span class="rubyn-status-dot"></span> Configured
|
|
15
|
+
</span>
|
|
16
|
+
<% else %>
|
|
17
|
+
<span class="rubyn-status-badge rubyn-status-badge--warn">
|
|
18
|
+
<span class="rubyn-status-dot"></span> Not configured
|
|
19
|
+
</span>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="rubyn-setting-card">
|
|
24
|
+
<h3>Preferences</h3>
|
|
25
|
+
|
|
26
|
+
<%= form_tag rubyn.settings_path, method: :patch do %>
|
|
27
|
+
<div class="rubyn-form-group">
|
|
28
|
+
<label>Output Format</label>
|
|
29
|
+
<%= select_tag :output_format, options_for_select(["diff", "full"], @settings["output_format"]) %>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="rubyn-form-group">
|
|
33
|
+
<label>
|
|
34
|
+
<%= check_box_tag :auto_apply, "true", @settings["auto_apply"] %>
|
|
35
|
+
Auto-apply changes
|
|
36
|
+
</label>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<%= submit_tag "Save", class: "rubyn-btn" %>
|
|
40
|
+
<% end %>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|