ai_error_clip 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 +49 -0
- data/app/views/ai_error_clip/simple_error.html.erb +160 -0
- data/lib/ai_error_clip/controller_extension.rb +101 -0
- data/lib/ai_error_clip/railtie.rb +16 -0
- data/lib/ai_error_clip/version.rb +3 -0
- data/lib/ai_error_clip.rb +6 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 95724784f3d9f42e22d4b99a3d8f9859c7ed2c0f63f6cf944d5e59b84a7f9c1a
|
4
|
+
data.tar.gz: cb608730af7c7deaaf719e4d252da25f298826f51b25f14ea374662a815e8eef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6714d9ff1ea31fcd00ace552c8140ce54408554da504b958e8f369412727b86ba65304bebde1212bd14abd20f0268475234458482c39d110921fa0f8298fb3d6
|
7
|
+
data.tar.gz: f617f5416c954f6e1a8225d7ce8e94055e309bce68f0647690404def5a522812c11894d857572ea7e11aa408565d788afc081d18d427508427d4e2d188048f69
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 utarage
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# ai_error_clip
|
2
|
+
|
3
|
+
`ai_error_clip` is a small Rails plugin that replaces the default development
|
4
|
+
error page with an AI-friendly layout. The page gathers the request, parameters,
|
5
|
+
context, and the first ten lines of the backtrace into one screen with a single
|
6
|
+
button that copies the information for pasting into an AI assistant.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add the gem to your Rails application's `Gemfile`. When developing locally you
|
11
|
+
can use a `path:` reference:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
group :development do
|
15
|
+
gem "ai_error_clip", path: "vendor/ai_error_clip"
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
Then install the dependencies inside your application (for example via Bundler):
|
20
|
+
|
21
|
+
```sh
|
22
|
+
bundle install
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Once the gem is loaded, it automatically attaches to `ActionController::Base`
|
28
|
+
in development mode. You do not need to change controller code manually.
|
29
|
+
|
30
|
+
When an exception occurs, Rails renders the gem's `Error Details` page instead
|
31
|
+
of the default stack trace. Use the **Copy details for AI** button to copy
|
32
|
+
everything required for a support conversation.
|
33
|
+
|
34
|
+
## Development
|
35
|
+
|
36
|
+
To modify the gem locally:
|
37
|
+
|
38
|
+
1. Adjust files under `lib/ai_error_clip` or `app/views/ai_error_clip`.
|
39
|
+
2. Run tests or manual checks in your Rails application.
|
40
|
+
3. Build the gem for distribution with:
|
41
|
+
|
42
|
+
```sh
|
43
|
+
gem build ai_error_clip.gemspec
|
44
|
+
```
|
45
|
+
|
46
|
+
## License
|
47
|
+
|
48
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for
|
49
|
+
details.
|
@@ -0,0 +1,160 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Error</title>
|
5
|
+
<style>
|
6
|
+
body {
|
7
|
+
font-family: monospace;
|
8
|
+
background: #f5f5f5;
|
9
|
+
padding: 12px;
|
10
|
+
margin: 0;
|
11
|
+
}
|
12
|
+
.error-container {
|
13
|
+
background: white;
|
14
|
+
border: 1px solid #ddd;
|
15
|
+
padding: 16px;
|
16
|
+
max-width: 960px;
|
17
|
+
margin: 0 auto;
|
18
|
+
}
|
19
|
+
.error-header {
|
20
|
+
display: flex;
|
21
|
+
justify-content: space-between;
|
22
|
+
align-items: flex-start;
|
23
|
+
gap: 12px;
|
24
|
+
margin-bottom: 12px;
|
25
|
+
}
|
26
|
+
.error-actions {
|
27
|
+
display: flex;
|
28
|
+
flex-direction: column;
|
29
|
+
align-items: flex-end;
|
30
|
+
gap: 6px;
|
31
|
+
flex-shrink: 0;
|
32
|
+
}
|
33
|
+
.copy-status {
|
34
|
+
display: flex;
|
35
|
+
align-items: center;
|
36
|
+
gap: 12px;
|
37
|
+
}
|
38
|
+
h1 {
|
39
|
+
color: #d00;
|
40
|
+
font-size: 24px;
|
41
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
42
|
+
text-transform: uppercase;
|
43
|
+
margin: 0 0 12px 0;
|
44
|
+
}
|
45
|
+
.error-grid {
|
46
|
+
display: grid;
|
47
|
+
grid-template-columns: 150px 1fr;
|
48
|
+
gap: 8px 12px;
|
49
|
+
}
|
50
|
+
.error-label {
|
51
|
+
font-weight: bold;
|
52
|
+
color: #666;
|
53
|
+
font-size: 12px;
|
54
|
+
padding-top: 2px;
|
55
|
+
}
|
56
|
+
.error-content {
|
57
|
+
background: #f9f9f9;
|
58
|
+
border: 1px solid #e0e0e0;
|
59
|
+
padding: 8px;
|
60
|
+
white-space: pre-wrap;
|
61
|
+
word-wrap: break-word;
|
62
|
+
font-size: 13px;
|
63
|
+
overflow: hidden;
|
64
|
+
}
|
65
|
+
.error-content--scroll {
|
66
|
+
max-height: 200px;
|
67
|
+
overflow: auto;
|
68
|
+
}
|
69
|
+
.copy-button {
|
70
|
+
background: #0066cc;
|
71
|
+
color: white;
|
72
|
+
border: none;
|
73
|
+
padding: 8px 16px;
|
74
|
+
font-size: 13px;
|
75
|
+
cursor: pointer;
|
76
|
+
border-radius: 4px;
|
77
|
+
white-space: nowrap;
|
78
|
+
}
|
79
|
+
.copy-button:hover {
|
80
|
+
background: #0052a3;
|
81
|
+
}
|
82
|
+
.copy-button:active {
|
83
|
+
background: #004080;
|
84
|
+
}
|
85
|
+
.copied-message {
|
86
|
+
display: none;
|
87
|
+
color: #080;
|
88
|
+
font-size: 12px;
|
89
|
+
}
|
90
|
+
.copy-hint {
|
91
|
+
color: #666;
|
92
|
+
font-size: 11px;
|
93
|
+
margin-top: 6px;
|
94
|
+
}
|
95
|
+
</style>
|
96
|
+
<script>
|
97
|
+
function copyErrorContent() {
|
98
|
+
const errorPath = document.getElementById('error-path').innerText;
|
99
|
+
const errorClass = document.getElementById('error-class').innerText;
|
100
|
+
const errorMessage = document.getElementById('error-message').innerText;
|
101
|
+
const errorBacktrace = document.getElementById('error-backtrace').innerText;
|
102
|
+
const errorBodyElement = document.getElementById('error-body');
|
103
|
+
const errorBody = errorBodyElement ? errorBodyElement.innerText : null;
|
104
|
+
const errorContextElement = document.getElementById('error-context');
|
105
|
+
const errorContext = errorContextElement ? errorContextElement.innerText : null;
|
106
|
+
const contextSection = errorContext ? `\n\nContext:\n${errorContext}` : '';
|
107
|
+
const bodySection = errorBody ? `\n\nPayload / Parameters:\n${errorBody}` : '';
|
108
|
+
const fullContent = `Request Path:\n${errorPath}\n\nError Class:\n${errorClass}\n\nError Message:\n${errorMessage}${contextSection}${bodySection}\n\nBacktrace:\n${errorBacktrace}`;
|
109
|
+
|
110
|
+
navigator.clipboard.writeText(fullContent).then(function() {
|
111
|
+
const message = document.getElementById('copied-message');
|
112
|
+
message.style.display = 'inline';
|
113
|
+
setTimeout(function() {
|
114
|
+
message.style.display = 'none';
|
115
|
+
}, 2000);
|
116
|
+
});
|
117
|
+
}
|
118
|
+
</script>
|
119
|
+
</head>
|
120
|
+
<body>
|
121
|
+
<div class="error-container">
|
122
|
+
<div class="error-header">
|
123
|
+
<h1>Error Details</h1>
|
124
|
+
<div class="error-actions">
|
125
|
+
<div class="copy-status">
|
126
|
+
<span class="copied-message" id="copied-message">✓ Copied</span>
|
127
|
+
<button class="copy-button" onclick="copyErrorContent()">Copy details for AI</button>
|
128
|
+
</div>
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
|
132
|
+
<div class="error-grid">
|
133
|
+
<div class="error-label">Request Path</div>
|
134
|
+
<div class="error-content" id="error-path"><%= @ai_error_clip_error[:method] %> <%= @ai_error_clip_error[:path] %></div>
|
135
|
+
|
136
|
+
<div class="error-label">Error Class</div>
|
137
|
+
<div class="error-content" id="error-class"><%= @ai_error_clip_error[:class] %></div>
|
138
|
+
|
139
|
+
<div class="error-label">Error Message</div>
|
140
|
+
<div class="error-content" id="error-message"><%= @ai_error_clip_error[:message] %></div>
|
141
|
+
|
142
|
+
<% if @ai_error_clip_error[:context].present? %>
|
143
|
+
<div class="error-label">Context</div>
|
144
|
+
<div class="error-content error-content--scroll" id="error-context"><% @ai_error_clip_error[:context].each do |key, value| %><%= "#{key}: #{value}" %>
|
145
|
+
<% end %></div>
|
146
|
+
<% end %>
|
147
|
+
|
148
|
+
<% if @ai_error_clip_error[:body].present? %>
|
149
|
+
<div class="error-label">Payload / Parameters</div>
|
150
|
+
<div class="error-content error-content--scroll" id="error-body"><%= h @ai_error_clip_error[:body] %></div>
|
151
|
+
<% end %>
|
152
|
+
|
153
|
+
<div class="error-label">Backtrace</div>
|
154
|
+
<div class="error-content error-content--scroll" id="error-backtrace"><% @ai_error_clip_error[:backtrace].each do |line| %><%= line %>
|
155
|
+
<% end %></div>
|
156
|
+
</div>
|
157
|
+
|
158
|
+
</div>
|
159
|
+
</body>
|
160
|
+
</html>
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module AiErrorClip
|
5
|
+
module ControllerExtension
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
if Rails.env.development?
|
10
|
+
rescue_from StandardError, with: :ai_error_clip_handle_standard_error
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# シンプルエラー画面を表示するか通常のエラーに任せるか判断する
|
17
|
+
def ai_error_clip_handle_standard_error(exception)
|
18
|
+
if Rails.env.development?
|
19
|
+
ai_error_clip_render_simple_error(exception)
|
20
|
+
else
|
21
|
+
raise exception
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# エラー内容を整形してAI向けのテンプレートを描画する
|
26
|
+
def ai_error_clip_render_simple_error(exception)
|
27
|
+
# GETクエリも含めた送信データをまとめる
|
28
|
+
request_payload = ai_error_clip_formatted_request_body
|
29
|
+
|
30
|
+
@ai_error_clip_error = {
|
31
|
+
class: exception.class.name,
|
32
|
+
message: exception.message,
|
33
|
+
backtrace: Array(exception.backtrace).first(10),
|
34
|
+
path: request.path,
|
35
|
+
method: request.method,
|
36
|
+
body: request_payload,
|
37
|
+
context: ai_error_clip_request_context
|
38
|
+
}
|
39
|
+
|
40
|
+
render template: "ai_error_clip/simple_error", status: :internal_server_error, layout: false
|
41
|
+
end
|
42
|
+
|
43
|
+
# リクエストに関する追加情報をまとめる
|
44
|
+
def ai_error_clip_request_context
|
45
|
+
{
|
46
|
+
environment: Rails.env,
|
47
|
+
rails_version: Rails.version,
|
48
|
+
ruby_version: RUBY_VERSION,
|
49
|
+
request_id: request.request_id,
|
50
|
+
controller: controller_name,
|
51
|
+
action: action_name,
|
52
|
+
referer: request.referer,
|
53
|
+
user_agent: request.user_agent
|
54
|
+
}.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
# リクエストパラメーターをJSON風に整形する(GETも含む)
|
58
|
+
def ai_error_clip_formatted_request_body
|
59
|
+
raw_params = ai_error_clip_capture_raw_params
|
60
|
+
return request.raw_post.presence unless raw_params.present?
|
61
|
+
|
62
|
+
begin
|
63
|
+
JSON.pretty_generate(raw_params)
|
64
|
+
rescue StandardError
|
65
|
+
raw_params.inspect
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Strong Parametersを安全なハッシュに変換する
|
70
|
+
def ai_error_clip_capture_raw_params
|
71
|
+
params_hash = params.to_unsafe_h.deep_dup
|
72
|
+
%w[controller action utf8 authenticity_token commit].each do |key|
|
73
|
+
params_hash.delete(key)
|
74
|
+
params_hash.delete(key.to_sym)
|
75
|
+
end
|
76
|
+
ai_error_clip_sanitize_param_values(params_hash)
|
77
|
+
rescue StandardError
|
78
|
+
request.filtered_parameters.except(:controller, :action, "controller", "action")
|
79
|
+
end
|
80
|
+
|
81
|
+
# ファイルなどの値を文字列情報にそろえる
|
82
|
+
def ai_error_clip_sanitize_param_values(value)
|
83
|
+
case value
|
84
|
+
when ActionDispatch::Http::UploadedFile
|
85
|
+
{
|
86
|
+
name: value.original_filename,
|
87
|
+
type: value.content_type,
|
88
|
+
size: value.size
|
89
|
+
}
|
90
|
+
when Hash
|
91
|
+
value.transform_values { |v| ai_error_clip_sanitize_param_values(v) }
|
92
|
+
when ActionController::Parameters
|
93
|
+
ai_error_clip_sanitize_param_values(value.to_unsafe_h)
|
94
|
+
when Array
|
95
|
+
value.map { |v| ai_error_clip_sanitize_param_values(v) }
|
96
|
+
else
|
97
|
+
value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rails/railtie"
|
2
|
+
require_relative "controller_extension"
|
3
|
+
|
4
|
+
module AiErrorClip
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
initializer "ai_error_clip.configure_controller" do
|
7
|
+
ActiveSupport.on_load(:action_controller_base) do
|
8
|
+
# Railsコントローラーでgemの機能を使えるようにする
|
9
|
+
include AiErrorClip::ControllerExtension
|
10
|
+
|
11
|
+
# gem内のビューを描画対象に追加する
|
12
|
+
append_view_path File.expand_path("../../app/views", __dir__)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ai_error_clip
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- utarage
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: ai_error_clip replaces the Rails development exception page with an AI-focused
|
13
|
+
layout that gathers request details, parameters, context, and a short backtrace
|
14
|
+
in one place.
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- LICENSE
|
20
|
+
- README.md
|
21
|
+
- app/views/ai_error_clip/simple_error.html.erb
|
22
|
+
- lib/ai_error_clip.rb
|
23
|
+
- lib/ai_error_clip/controller_extension.rb
|
24
|
+
- lib/ai_error_clip/railtie.rb
|
25
|
+
- lib/ai_error_clip/version.rb
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubygems_version: 3.6.7
|
44
|
+
specification_version: 4
|
45
|
+
summary: AI-friendly Rails development error page with one-click copy
|
46
|
+
test_files: []
|